- 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>
102 lines
3.8 KiB
PHP
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');
|
|
}
|
|
}
|