hoàn thành 58 test case

This commit is contained in:
2026-04-08 15:52:56 +07:00
parent c138adb108
commit 18ec75071c
13 changed files with 588 additions and 6 deletions
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"version":1,"defects":[],"times":{"Tests\\Browser\\BulkWorkflowTest::test_1000_users_full_workflow_from_excel":33.781}}
+89 -5
View File
@@ -488,6 +488,8 @@ FILE 11: tests/Browser/AdminEdgeCaseTest.php (3 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo adminUser với đầy đủ roles trong DB.
Dọn dẹp chung: Xóa UserRole, adminUser khỏi DB; xóa prize test nếu còn sót.
Chuẩn bị chung: Tạo adminUser với đầy đủ roles trong DB.
Dọn dẹp chung: Xóa UserRole, adminUser khỏi DB; xóa prize test nếu còn sót.
[TC-55] test_prize_number_cannot_be_negative
Mô tả : Nhập prize_number âm (-5) vào form thêm giải thưởng phải bị
@@ -534,14 +536,94 @@ Dọn dẹp chung: Xóa UserRole, adminUser khỏi DB; xóa prize test nếu cò
Lưu ý : Không dùng actingAs()->get(exportRoute) vì excel.blade.php gọi exit()
sau khi xuất file — điều này sẽ terminate PHPUnit process.
--------------------------------------------------------------------------------
FILE 12: tests/Browser/BulkWorkflowTest.php (1 test)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo adminUser (đầy đủ roles), tạo prize có prize_number=2000,
tạo file Excel tạm chứa 1000 bản ghi người dùng bằng PhpSpreadsheet.
Dọn dẹp chung: Xóa 1000 users + PrizeUser (theo chunk 200), xóa admin, prize, file Excel.
[TC-58] test_1000_users_full_workflow_from_excel
Mô tả : Kiểm tra toàn bộ workflow hệ thống (end-to-end) với 1000 người dùng
được cung cấp từ file Excel — từ import dữ liệu đến quay số đến
xác nhận trên giao diện admin.
Luồng :
[A] Đọc file Excel → Xác nhận file có đúng 1001 dòng (1 header + 1000 data).
[B] Bulk-import 1000 users vào DB (đọc từng ô Excel, insert theo chunk 100).
[C] Simulate 1000 lượt quay số (bulk insert prize_users + update spined=1).
[D] 1 lượt quay thực qua HTTP POST /updateprizeuser để kiểm tra API layer.
[E] Browser: Admin mở /admin/users → bảng #table hiển thị.
Browser: Admin mở /admin/prizeuser → bảng + bộ lọc ngày + nút export hiển thị.
[F] Browser: Xác nhận href của #excel_export chứa "prizeuser/export";
PHP route('admin/prizeuser/export') trả URL hợp lệ.
Chi tiết từng bước:
[A] Tạo & Đọc Excel:
Chuẩn bị: generateTestExcel() dùng PhpOffice\PhpSpreadsheet tạo file .xlsx
với 6 cột: STT | HỌ TÊN | SỐ ĐIỆN THOẠI | ĐỊA CHỈ | NGHỀ NGHIỆP | NGUỒN BIẾT SIS.
Phone dùng dải riêng 0900000001..0900001000 (tránh đụng dữ liệu thật).
Bước : XlsxReader::load() → getHighestDataRow() = 1001.
Kết quả : File Excel hợp lệ, đọc được 1001 dòng.
[B] Bulk-import 1000 users:
Bước : Đọc từng row (2..1001) → lấy phone, name (mb_strtoupper), address,
job, known_sis → build $importBatch[].
DB::table('users')->insert() theo chunk 100.
Lấy IDs: User::whereIn('phone', ...)->pluck('id').
Kết quả : assertCount(1000, $bulkUserIds) — 1000 users được import.
[C] Simulate 1000 lần quay:
Bước : Build $spinBatch[] với user_id + prize_id cho 1000 user.
DB::table('prize_users')->insert() theo chunk 100.
User::whereIn('id', chunk)->update(['spined' => 1]) theo chunk 200.
Kết quả : PrizeUser::whereIn('user_id', $bulkUserIds)->count() = 1000.
User::whereIn(...)->where('spined', 1)->count() = 1000.
[D] Kiểm tra luồng spin thực qua Browser (1 user mới, giao diện game):
Chuẩn bị: Tạo 1 user riêng (phone='0900000000') ngoài dải bulk, spined=0.
Bước : browser->loginAs(spinUser) → visit('/game') → pause(800ms)
→ assertAttribute('#spinButton','data-check','true')
→ click('#spinButton') → waitForText('XIN CHÚC MỪNG', 30s)
→ pause(1500ms) để DB commit.
Kết quả : spinUser->refresh()->spined = 1.
PrizeUser::where('user_id', spinUserId)->first() != null.
Lợi thế : Dùng Browser thực → CSRF token có trong session → không bị lỗi 419.
Đây là cách kiểm tra API /updateprizeuser trung thực nhất.
Dọn dẹp : Xóa spinUser và PrizeUser của spinUser ngay sau khi assert.
[E] Browser admin xác nhận:
Bước : browser->loginAs($adminUser).
Truy cập /admin/users → waitFor('#table', 15) → assertPresent('#table').
Truy cập /admin/prizeuser → waitFor('table, .table', 15)
→ assertPresent('#fromDateTime', '#toDateTime', '#excel_export').
Kết quả : Bảng quản lý người dùng và bảng người trúng giải đều hiển thị đúng.
[F] Xác nhận export Excel:
Bước : JS override document.createElement để capture href khi click #excel_export.
PHP: route('admin/prizeuser/export') kiểm tra URL.
Kết quả : capturedHref chứa 'prizeuser/export' (nếu JS capture thành công).
route('admin/prizeuser/export') chứa '/admin/prizeuser/export'.
Lưu ý : Không gọi actingAs()->get(exportUrl) vì excel.blade.php gọi exit()
sau khi xuất file → terminate PHPUnit process.
Dữ liệu test:
- 1000 users với phone 0900000001..0900001000
- 1 prize "Giải Thưởng Bulk Test" (prize_number=2000, prize_order=98)
- 1 admin "DUSK BULK ADMIN" (email: dusk_bulk_admin@test.local)
- 1 file Excel tạm (sys_get_temp_dir()/dusk_bulk_{timestamp}.xlsx)
Assertions: 15 assertions
Thời gian : ~37 giây
================================================================================
TỔNG KẾT
================================================================================
Số file test : 11 files
Số test cases : 57 test cases
Số assertions : 114 assertions
Thời gian : ~178 giây
Số file test : 12 files
Số test cases : 58 test cases
Số assertions : 129 assertions
Thời gian : ~215 giây
Kết quả : ALL PASSED ✓
Phân bổ theo chức năng:
@@ -559,8 +641,9 @@ TỔNG KẾT
│ Logic game nâng cao │ 3 │
│ Bảo mật & phân quyền │ 2 │
│ Admin edge cases │ 3 │
│ Bulk workflow 1000 users từ Excel │ 1 │
├──────────────────────────────────────────┼────────┤
│ TỔNG CỘNG │ 57
│ TỔNG CỘNG │ 58
└──────────────────────────────────────────┴────────┘
Công nghệ kiểm thử:
@@ -570,6 +653,7 @@ TỔNG KẾT
- Môi trường : WSL2 + WSLg, DUSK_HEADLESS_DISABLED=true
- Server : php artisan serve --port=8001
- DB : MySQL (fsg3kct926qj_quaysoDB)
- Excel lib : PhpOffice\PhpSpreadsheet (có sẵn trong vendor/phpoffice/)
Sửa lỗi ứng dụng phát hiện qua quá trình kiểm thử:
┌──────────────────────────────────────────────────────────────────────┐
Executable
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
# ============================================================
# dusk — Wrapper chạy Laravel Dusk tests
#
# Cách dùng:
# ./dusk # Có giao diện Chrome (mặc định)
# ./dusk --headless # Chạy headless (không mở cửa sổ)
# ./dusk --headless --filter=BulkWorkflowTest
# ./dusk --filter=AdminPrizeTest
#
# Tất cả tham số còn lại được chuyển thẳng sang php artisan dusk.
# ============================================================
set -e
HEADLESS=0
ARTISAN_ARGS=()
for arg in "$@"; do
if [[ "$arg" == "--headless" ]]; then
HEADLESS=1
else
ARTISAN_ARGS+=("$arg")
fi
done
if [[ $HEADLESS -eq 1 ]]; then
echo "[dusk] Chạy HEADLESS (không mở cửa sổ Chrome)"
# Set DUSK_HEADLESS=true trước khi PHP khởi động.
# Laravel dùng Dotenv::createImmutable() → OS env var có độ ưu tiên cao hơn .env
# → DUSK_HEADLESS_DISABLED trong .env sẽ bị bỏ qua bởi hasHeadlessDisabled()
exec env DUSK_HEADLESS=true php artisan dusk "${ARTISAN_ARGS[@]}"
else
echo "[dusk] Chạy có giao diện Chrome (DUSK_HEADLESS_DISABLED=true)"
exec php artisan dusk "${ARTISAN_ARGS[@]}"
fi
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
beStrictAboutTestsThatDoNotTestAnything="false"
colors="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">
<testsuites>
<testsuite name="Browser Test Suite">
<directory suffix="Test.php">./tests/Browser</directory>
</testsuite>
</testsuites>
</phpunit>
+5
View File
@@ -24,6 +24,11 @@ class AdminEdgeCaseTest extends DuskTestCase
protected function setUp(): void
{
parent::setUp();
$stale = User::where('email', 'dusk_edge_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
$this->adminUser = User::create([
'name' => 'DUSK EDGE ADMIN',
'email' => 'dusk_edge_admin@test.local',
+8
View File
@@ -15,6 +15,14 @@ class AdminLoginTest extends DuskTestCase
protected function setUp(): void
{
parent::setUp();
// Bảo vệ chống duplicate từ lần chạy bị interrupt trước đó
$stale = User::where('email', 'dusk_admin_test@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
// Tạo admin test user với đầy đủ roles
$this->adminUser = User::create([
'name' => 'DUSK ADMIN TEST',
+5
View File
@@ -16,6 +16,11 @@ class AdminParameterTest extends DuskTestCase
protected function setUp(): void
{
parent::setUp();
$stale = User::where('email', 'dusk_param_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
$this->adminUser = User::create([
'name' => 'DUSK PARAM ADMIN',
'email' => 'dusk_param_admin@test.local',
+8
View File
@@ -17,6 +17,14 @@ class AdminPrizeTest extends DuskTestCase
protected function setUp(): void
{
parent::setUp();
// Bảo vệ chống duplicate từ lần chạy bị interrupt trước đó
$stale = User::where('email', 'dusk_prize_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
$this->adminUser = User::create([
'name' => 'DUSK PRIZE ADMIN',
'email' => 'dusk_prize_admin@test.local',
+5
View File
@@ -16,6 +16,11 @@ class AdminUserTest extends DuskTestCase
protected function setUp(): void
{
parent::setUp();
$stale = User::where('email', 'dusk_user_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
$this->adminUser = User::create([
'name' => 'DUSK USER ADMIN',
'email' => 'dusk_user_admin@test.local',
+393
View File
@@ -0,0 +1,393 @@
<?php
namespace Tests\Browser;
use App\Models\Prize;
use App\Models\PrizeUser;
use App\Models\Role;
use App\Models\User;
use App\Models\UserRole;
use Illuminate\Support\Facades\DB;
use Laravel\Dusk\Browser;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
use Tests\DuskTestCase;
/**
* BulkWorkflowTest — Kiểm thử toàn bộ workflow hệ thống với 1000 người dùng từ file Excel.
*
* Luồng kiểm thử (end-to-end):
* [A] Tạo file Excel 1000 người dùng bằng PhpSpreadsheet
* [B] Đọc Excel → bulk-import 1000 users vào DB
* [C] Simulate 1000 lần quay (bulk PrizeUser + spined=1)
* [D] 1 lần quay thực qua HTTP endpoint /updateprizeuser (kiểm tra API thực)
* [E] Browser: Admin xác nhận danh sách users và danh sách người trúng giải
* [F] Browser: Xác nhận nút export Excel tồn tại và route hợp lệ
*
* [TC-58] test_1000_users_full_workflow_from_excel
*/
class BulkWorkflowTest extends DuskTestCase
{
private User $adminUser;
private Prize $bulkPrize;
private array $bulkUserIds = [];
private string $excelPath = '';
const BULK_COUNT = 1000;
// =========================================================================
// Số điện thoại dải riêng để không đụng dữ liệu thật trong DB
// Format: 0900XXXXXX (10 chữ số), XXXXXX = 000001..001000
// =========================================================================
private function makeTestPhone(int $index): string
{
return '0900' . str_pad($index, 6, '0', STR_PAD_LEFT);
}
// =========================================================================
// setUp / tearDown
// =========================================================================
protected function setUp(): void
{
parent::setUp();
// Bảo vệ chống duplicate từ lần chạy bị interrupt trước đó
$stale = User::where('email', 'dusk_bulk_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
Prize::where('prize_name', 'Giải Thưởng Bulk Test')->each(function ($p) {
\App\Models\PrizeUser::where('prize_id', $p->id)->delete();
$p->delete();
});
// Xóa bulk users tồn đọng (phone 0900000000..0900001000)
$staleUsers = User::whereBetween('phone', ['0900000000', '0900001000'])->pluck('id');
if ($staleUsers->isNotEmpty()) {
\App\Models\PrizeUser::whereIn('user_id', $staleUsers)->delete();
User::whereIn('id', $staleUsers)->delete();
}
// Tạo admin với đầy đủ roles
$this->adminUser = User::create([
'name' => 'DUSK BULK ADMIN',
'email' => 'dusk_bulk_admin@test.local',
'phone' => '0900999999',
'password' => bcrypt('dusk_bulk_admin_2026'),
'status' => 1,
'address' => 'Cần Thơ',
'known_sis' => 0,
'job' => 'Admin',
'spined' => 0,
]);
$roles = Role::where('role_status', 1)->get();
foreach ($roles as $role) {
UserRole::create(['user_id' => $this->adminUser->id, 'role_id' => $role->id]);
}
// Tạo giải thưởng với prize_number đủ lớn cho 1001 lượt (1000 bulk + 1 HTTP)
$this->bulkPrize = Prize::create([
'prize_name' => 'Giải Thưởng Bulk Test',
'prize_status' => 1,
'prize_number' => 2000,
'prize_order' => 98,
'user_id' => 0,
]);
// Tạo file Excel tạm chứa 1000 bản ghi người dùng
$this->excelPath = sys_get_temp_dir() . '/dusk_bulk_' . time() . '.xlsx';
$this->generateTestExcel($this->excelPath, self::BULK_COUNT);
}
protected function tearDown(): void
{
// Dọn dẹp theo thứ tự: PrizeUser → Users → Prize → AdminUser
if (!empty($this->bulkUserIds)) {
foreach (array_chunk($this->bulkUserIds, 200) as $chunk) {
PrizeUser::whereIn('user_id', $chunk)->delete();
User::whereIn('id', $chunk)->delete();
}
}
// Xóa spin verify test user nếu còn
$spinUser = User::where('phone', $this->makeTestPhone(0))->first();
if ($spinUser) {
PrizeUser::where('user_id', $spinUser->id)->delete();
$spinUser->delete();
}
UserRole::where('user_id', $this->adminUser->id)->delete();
$this->adminUser->delete();
$this->bulkPrize->delete();
if ($this->excelPath && file_exists($this->excelPath)) {
unlink($this->excelPath);
}
parent::tearDown();
}
// =========================================================================
// Helper: Tạo file Excel 1000 bản ghi người dùng
// =========================================================================
private function generateTestExcel(string $path, int $count): void
{
$spreadsheet = new Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
$sheet->setTitle('Danh Sách Người Dùng');
// Header row
$sheet->setCellValue('A1', 'STT');
$sheet->setCellValue('B1', 'HỌ TÊN');
$sheet->setCellValue('C1', 'SỐ ĐIỆN THOẠI');
$sheet->setCellValue('D1', 'ĐỊA CHỈ');
$sheet->setCellValue('E1', 'NGHỀ NGHIỆP');
$sheet->setCellValue('F1', 'NGUỒN BIẾT SIS');
// Data rows (index 1..1000 → phone 0900000001..0900001000)
for ($i = 1; $i <= $count; $i++) {
$row = $i + 1;
$sheet->setCellValue('A' . $row, $i);
$sheet->setCellValue('B' . $row, 'Người Dùng Bulk ' . $i);
$sheet->setCellValue('C' . $row, $this->makeTestPhone($i));
$sheet->setCellValue('D' . $row, ($i % 2 === 0) ? 'Hà Nội' : 'Cần Thơ');
$sheet->setCellValue('E' . $row, ($i % 3 === 0) ? 'Giáo viên' : 'Kỹ sư');
$sheet->setCellValue('F' . $row, $i % 2); // 0 hoặc 1
}
$writer = new XlsxWriter($spreadsheet);
$writer->save($path);
}
// =========================================================================
// [TC-58] Toàn bộ workflow 1000 người dùng từ file Excel
// =========================================================================
/**
* [BƯỚC A] Đọc 1000 bản ghi từ file Excel.
* [BƯỚC B] Bulk-import 1000 users vào DB (giả lập chức năng admin import từ Excel).
* [BƯỚC C] Simulate 1000 lượt quay số (DB bulk insert + cập nhật spined).
* [BƯỚC D] 1 lượt quay thực qua HTTP POST /updateprizeuser (kiểm tra API layer).
* [BƯỚC E] Browser: Admin mở /admin/users và /admin/prizeuser, xác nhận bảng hiển thị.
* [BƯỚC F] Browser: Xác nhận #excel_export tồn tại; route export hợp lệ.
*/
public function test_1000_users_full_workflow_from_excel(): void
{
// ==================================================================
// [A] Đọc file Excel
// ==================================================================
$this->assertFileExists($this->excelPath, 'File Excel test phải được tạo thành công');
$reader = new XlsxReader();
$spreadsheet = $reader->load($this->excelPath);
$sheet = $spreadsheet->getActiveSheet();
$highestRow = $sheet->getHighestDataRow(); // phải là 1001 (1 header + 1000 data)
$this->assertEquals(
self::BULK_COUNT + 1,
$highestRow,
'File Excel phải có ' . (self::BULK_COUNT + 1) . ' dòng (1 header + 1000 data)'
);
// ==================================================================
// [B] Bulk-import 1000 users vào DB từ dữ liệu trong Excel
// ==================================================================
$now = now();
$importBatch = [];
for ($row = 2; $row <= $highestRow; $row++) {
$phone = (string) $sheet->getCell('C' . $row)->getValue();
$name = mb_strtoupper(
(string) $sheet->getCell('B' . $row)->getValue(), 'UTF-8'
);
$address = (string) $sheet->getCell('D' . $row)->getValue();
$job = (string) $sheet->getCell('E' . $row)->getValue();
$knownSis = (int) $sheet->getCell('F' . $row)->getValue();
$importBatch[] = [
'name' => $name,
'phone' => $phone,
'email' => $phone . '@phone.local',
'password' => bcrypt('bulk2026'),
'address' => $address,
'known_sis' => $knownSis,
'job' => $job,
'status' => 1,
'spined' => 0,
'created_at' => $now,
'updated_at' => $now,
];
}
// Chia chunks 100 để tránh quá nhiều placeholders trong 1 query
foreach (array_chunk($importBatch, 100) as $chunk) {
DB::table('users')->insert($chunk);
}
// Lấy IDs của 1000 users vừa import
$importedPhones = array_column($importBatch, 'phone');
$this->bulkUserIds = User::whereIn('phone', $importedPhones)
->pluck('id')
->toArray();
$this->assertCount(
self::BULK_COUNT,
$this->bulkUserIds,
'1000 users phải được import thành công từ file Excel'
);
// ==================================================================
// [C] Simulate 1000 lượt quay số (bulk PrizeUser + update spined)
// ==================================================================
$prizeId = $this->bulkPrize->id;
$spinBatch = [];
foreach ($this->bulkUserIds as $userId) {
$spinBatch[] = [
'user_id' => $userId,
'prize_id' => $prizeId,
'created_at' => $now,
'updated_at' => $now,
];
}
foreach (array_chunk($spinBatch, 100) as $chunk) {
DB::table('prize_users')->insert($chunk);
}
// Bulk update spined = 1 cho tất cả users vừa import
foreach (array_chunk($this->bulkUserIds, 200) as $chunk) {
User::whereIn('id', $chunk)->update(['spined' => 1]);
}
// DB assertions sau khi spin
$prizeUserCount = PrizeUser::whereIn('user_id', $this->bulkUserIds)->count();
$this->assertEquals(
self::BULK_COUNT,
$prizeUserCount,
'1000 bản ghi PrizeUser phải được tạo — mỗi user có đúng 1 lần quay'
);
$spinedCount = User::whereIn('id', $this->bulkUserIds)
->where('spined', 1)
->count();
$this->assertEquals(
self::BULK_COUNT,
$spinedCount,
'1000 users phải có spined = 1 sau khi quay thành công'
);
// ==================================================================
// [D] 1 lần quay thực qua Browser (spin 1 user chưa quay qua trang game)
// Dùng browser + loginAs thay vì HTTP test client để tránh vấn đề CSRF.
// Luồng: user mới (spined=0) → /game → click #spinButton → waitFor kết quả
// → DB: PrizeUser + spined=1
// ==================================================================
$spinTestPhone = $this->makeTestPhone(0); // '0900000000' — ngoài dải bulk
// Cleanup nếu còn tồn đọng từ lần chạy trước
$staleSpinUser = User::where('phone', $spinTestPhone)->first();
if ($staleSpinUser) {
PrizeUser::where('user_id', $staleSpinUser->id)->delete();
$staleSpinUser->delete();
}
$spinUser = User::create([
'name' => 'DUSK SPIN VERIFY USER',
'phone' => $spinTestPhone,
'email' => $spinTestPhone . '@phone.local',
'password' => bcrypt('spin_verify_2026'),
'address' => 'Cần Thơ',
'known_sis' => 1,
'job' => 'Tester',
'status' => 1,
'spined' => 0,
]);
$spinUserId = $spinUser->id;
$this->browse(function (Browser $browser) use ($spinUserId, $prizeId) {
$spinUser = User::find($spinUserId);
$browser->loginAs($spinUser)
->visit('/game')
->pause(800); // Chờ wheel canvas render
$browser->assertAttribute('#spinButton', 'data-check', 'true');
$browser->click('#spinButton');
$browser->waitForText('XIN CHÚC MỪNG', 30); // Spin mất ~10-15s
$browser->pause(1500); // Chờ DB transaction commit
});
// DB assertions sau khi spin thực
$spinUser->refresh();
$this->assertEquals(1, $spinUser->spined,
'/game spin thực: user.spined phải = 1 sau khi quay thành công');
$spinPrizeUser = PrizeUser::where('user_id', $spinUserId)->first();
$this->assertNotNull($spinPrizeUser,
'/game spin thực: bản ghi PrizeUser phải tồn tại sau khi quay');
// Cleanup spin test user
PrizeUser::where('user_id', $spinUserId)->delete();
$spinUser->delete();
// ==================================================================
// [E] Browser: Admin xác nhận giao diện quản trị hiển thị đúng
// ==================================================================
$this->browse(function (Browser $browser) {
$browser->loginAs($this->adminUser);
// Trang danh sách người dùng
$browser->visit('/admin/users')
->waitFor('#table', 15)
->assertPresent('#table');
// Trang danh sách người trúng giải
$browser->visit('/admin/prizeuser')
->waitFor('table, .table', 15)
->assertPresent('#fromDateTime')
->assertPresent('#toDateTime')
->assertPresent('#excel_export');
// ==================================================================
// [F] Xác nhận route export Excel hợp lệ (không click để tránh exit())
// ==================================================================
$capturedHref = $browser->script("
var captured = null;
var _orig = document.createElement.bind(document);
document.createElement = function(tag) {
var el = _orig(tag);
if (tag.toLowerCase() === 'a') {
Object.defineProperty(el, 'href', {
set: function(v) { captured = v; },
get: function() { return captured || ''; }
});
}
return el;
};
try {
document.getElementById('excel_export').click();
} catch(e) {}
document.createElement = _orig;
return captured;
")[0];
// capturedHref có thể là null nếu JS build URL khác cách — kiểm tra fallback
if ($capturedHref !== null) {
$browser->assertScript(
"return ('" . addslashes($capturedHref) . "').indexOf('prizeuser/export') !== -1",
true
);
}
// Xác nhận route export đã được đăng ký trong hệ thống (PHP route helper)
$exportUrl = route('admin/prizeuser/export');
$this->assertStringContainsString(
'/admin/prizeuser/export',
$exportUrl,
'Route admin/prizeuser/export phải tồn tại trong hệ thống'
);
});
}
}
+5
View File
@@ -38,6 +38,11 @@ class SecurityTest extends DuskTestCase
]);
// Admin user — có đầy đủ roles
$stale = User::where('email', 'dusk_secure_admin@test.local')->first();
if ($stale) {
UserRole::where('user_id', $stale->id)->delete();
$stale->delete();
}
$this->adminUser = User::create([
'name' => 'DUSK SECURE ADMIN',
'email' => 'dusk_secure_admin@test.local',
+17
View File
@@ -11,6 +11,23 @@ use PHPUnit\Framework\Attributes\BeforeClass;
abstract class DuskTestCase extends BaseTestCase
{
/**
* Kiểm tra xem có nên chạy Chrome ở chế độ headless không.
*
* Ưu tiên:
* 1. Nếu DUSK_HEADLESS=true (set từ shell trước khi PHP chạy) → headless
* 2. Ngược lại fallback về DUSK_HEADLESS_DISABLED trong .env
*/
protected function hasHeadlessDisabled(): bool
{
if (isset($_SERVER['DUSK_HEADLESS']) || isset($_ENV['DUSK_HEADLESS'])) {
return false; // Buộc headless dù .env có DUSK_HEADLESS_DISABLED
}
return isset($_SERVER['DUSK_HEADLESS_DISABLED']) ||
isset($_ENV['DUSK_HEADLESS_DISABLED']);
}
/**
* Prepare for Dusk test execution.
*/