Files
portal-api/tests/Feature/Api/Admin/PopupBannerTest.php
baodev0andClaude Sonnet 5 12d5b4951c feat: popup quảng cáo, đặt lại mật khẩu bằng token 80 ký tự, 2FA email, vá lỗ hổng bảo mật
- Popup/banner quảng cáo cấu hình từ admin (loại, link, lịch hiển thị)
- Đặt lại mật khẩu: token 80 ký tự kèm check digit Luhn thay cho token+email lộ trong URL, chỉ lưu bản băm, một lần dùng, hết hạn 60 phút
- Xác thực 2 lớp qua email khi đăng nhập (mã 6 số, ghi nhớ thiết bị 90 ngày)
- ApiAuthCheck chặn ngay tài khoản bị khoá dù JWT còn hạn (trước đây dùng được tới hết TTL)
- Handler trả 404 thay vì 500 khi ModelNotFoundException không bọc HttpException
- Test ClientTamperingTest: xác nhận IDOR/giả mạo dữ liệu từ client đều bị chặn

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-13 13:23:33 +07:00

102 lines
3.8 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin;
use App\Models\PopupBanner;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class PopupBannerTest extends TestCase
{
use RefreshDatabase;
private function admin(): User
{
$role = Role::create(['name' => 'role-' . uniqid(), 'guard_name' => 'admin']);
$role->givePermissionTo(Permission::firstOrCreate(['name' => 'setting.manager', 'guard_name' => 'admin']));
$admin = User::factory()->create();
$admin->assignRole($role);
return $admin;
}
public function test_admin_creates_replaces_image_and_deletes_popup(): void
{
Storage::fake(PopupBanner::DISK);
$admin = $this->admin();
$id = $this->actingAs($admin, 'admin')
->post('/api/v1/administrator/popup-banners', [
'title' => 'Sale 11.11',
'type' => 'promotion',
'image' => UploadedFile::fake()->image('a.png'),
'link_url' => 'https://tgs.com.vn/khuyen-mai',
'open_in_new_tab' => '1',
], ['Accept' => 'application/json'])
->assertCreated()
->json('data.id');
$first = PopupBanner::findOrFail($id)->image_path;
Storage::disk(PopupBanner::DISK)->assertExists($first);
$this->actingAs($admin, 'admin')
->post("/api/v1/administrator/popup-banners/{$id}", [
'title' => 'Sale 12.12',
'type' => 'advertisement',
'image' => UploadedFile::fake()->image('b.png'),
], ['Accept' => 'application/json'])
->assertOk()
->assertJsonPath('data.type', 'advertisement')
->assertJsonPath('data.link_url', 'https://tgs.com.vn/khuyen-mai');
Storage::disk(PopupBanner::DISK)->assertMissing($first);
$second = PopupBanner::findOrFail($id)->image_path;
$this->actingAs($admin, 'admin')
->deleteJson("/api/v1/administrator/popup-banners/{$id}")
->assertOk();
Storage::disk(PopupBanner::DISK)->assertMissing($second);
$this->assertDatabaseMissing('popup_banners', ['id' => $id]);
}
public function test_rejects_unsafe_link_and_unknown_type(): void
{
Storage::fake(PopupBanner::DISK);
$this->actingAs($this->admin(), 'admin')
->post('/api/v1/administrator/popup-banners', [
'title' => 'X',
'type' => 'spam',
'image' => UploadedFile::fake()->image('a.png'),
'link_url' => 'javascript:alert(1)',
], ['Accept' => 'application/json'])
->assertStatus(422)
->assertJsonValidationErrors(['type', 'link_url']);
}
public function test_client_only_sees_active_popups_in_schedule(): void
{
$base = ['type' => 'advertisement', 'image_path' => 'popup-banners/x.png'];
PopupBanner::create($base + ['title' => 'Visible', 'link_url' => '/nap-tien']);
PopupBanner::create($base + ['title' => 'Off', 'is_active' => false]);
PopupBanner::create($base + ['title' => 'Future', 'starts_at' => now()->addDay()]);
PopupBanner::create($base + ['title' => 'Past', 'ends_at' => now()->subDay()]);
$this->withHeaders(['x-access-token' => $this->buildClientAccessToken()])
->getJson('/v1/setting/popups')
->assertOk()
->assertJsonCount(1, 'data')
->assertJsonPath('data.0.title', 'Visible')
->assertJsonPath('data.0.link_url', '/nap-tien')
->assertJsonMissingPath('data.0.image_path');
}
}