regularUser = User::create([
'name' => 'DUSK REGULAR USER',
'phone' => $phone,
'email' => $phone . '@phone.local',
'password' => bcrypt('test123'),
'address' => 'TP.HCM',
'known_sis' => 0,
'job' => 'Người dùng thường',
'status' => 1,
'spined' => 0,
]);
// Admin user — có đầy đủ roles
$this->adminUser = User::create([
'name' => 'DUSK SECURE ADMIN',
'email' => 'dusk_secure_admin@test.local',
'phone' => '09' . rand(10000000, 99999999),
'password' => bcrypt('dusk_admin_pwd_2026'),
'status' => 1,
'address' => 'Test',
'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]);
}
}
protected function tearDown(): void
{
UserRole::where('user_id', $this->adminUser->id)->delete();
$this->adminUser->delete();
$this->regularUser->delete();
parent::tearDown();
}
// =========================================================================
// [TC-53] Người dùng thường không thể truy cập khu vực admin
// =========================================================================
/**
* Người dùng đã đăng nhập nhưng KHÔNG có role admin bị redirect về /admin
* khi cố truy cập bất kỳ route nào trong middleware UserRole.
*
* Middleware UserRole (app/Http/Middleware/UserRole.php):
* - Kiểm tra constructMenuLeft() → lấy danh sách route được phép từ roles
* - Nếu route hiện tại không trong danh sách → redirect('admin')
* - User thường không có UserRole record → menu rỗng → TẤT CẢ admin route bị chặn
*/
public function test_regular_user_cannot_access_admin_area(): void
{
$this->browse(function (Browser $browser) {
// Đăng nhập là user thường (không có role nào)
$browser->loginAs($this->regularUser);
// Thử truy cập /admin/account → phải bị redirect về /admin (trang login admin)
$browser->visit('/admin/account')
->assertPathIs('/admin');
// Thử truy cập /admin/users → tương tự bị redirect
$browser->visit('/admin/users')
->assertPathIs('/admin');
// Thử truy cập /admin/prizes → bị redirect
$browser->visit('/admin/prizes')
->assertPathIs('/admin');
});
}
// =========================================================================
// [TC-54] XSS Injection vào form đăng ký — output phải được escape đúng cách
// =========================================================================
/**
* Nhập XSS payload vào trường `name` khi đăng ký. Ứng dụng phải:
* 1. Lưu dữ liệu an toàn (không thực thi script)
* 2. Render escaped output trong admin — Blade {{ }} tự escape HTML entities
*
* Luồng kiểm tra:
* - Frontend: đăng ký với name =
* - Controller: uppercase → rồi lưu DB
* - Admin view (table.blade.php): {{ $item->name ?? '' }} → render escaped
* - Kết quả: source HTML chứa <SCRIPT> thay vì tag ";
// NOTE: mb_strtoupper converts the name to .
// 'ALERT' is NOT a valid JS function (case-sensitive) → no JS dialog fires from the payload.
// The only alerts that CAN appear are from other sources (e.g. DataTables warnings).
// This test checks XSS protection via HTML source inspection, not via alert detection.
try {
$this->browse(function (Browser $browser) use ($xssPhone, $xssPayload) {
// ----------------------------------------------------------------
// BƯỚC 1: Đăng ký tài khoản với XSS payload trong trường name
// ----------------------------------------------------------------
$browser->visit('/logout')
->visit('/login')
->type('name', $xssPayload)
->type('phone', $xssPhone)
->type('address', 'Test City')
->radio('known_sis', '0')
->type('job', 'Tester')
->press('Gửi thông tin')
->waitForText('thành công', 10)
->assertPathIs('/game');
$browser->visit('/logout');
// ----------------------------------------------------------------
// BƯỚC 2: Đăng nhập admin và mở trang danh sách user
// ----------------------------------------------------------------
$browser->visit('/admin')
->type('email', 'dusk_secure_admin@test.local')
->type('password', 'dusk_admin_pwd_2026')
->press('Login')
->waitForLocation('/admin/account', 10);
// Dismiss any alert that appears on the account page (e.g. DataTables warnings).
// CRITICAL: Always dismiss before proceeding — leaving an alert open crashes ChromeDriver.
$this->dismissAnyAlert($browser);
$browser->visit('/admin/users')
->waitFor('#table', 5);
// Dismiss any alert on the users page (DataTables may fire errMode alerts
// if HTML content in cells triggers a parsing/column-count error).
$this->dismissAnyAlert($browser);
// ----------------------------------------------------------------
// ASSERTION: HTML source không chứa tag thành:
// <SCRIPT>ALERT('XSS')</SCRIPT>
// Nên source page KHÔNG chứa literal ")
->assertSourceMissing("");
// Kiểm tra dương: source phải chứa phiên bản đã được HTML-escape
$browser->assertSourceHas('<SCRIPT>');
});
} finally {
User::where('phone', $xssPhone)->delete();
}
}
/**
* Helper: Dismiss any open alert dialog silently.
* Prevents Chrome lockup when an unexpected alert (e.g. DataTables warning) appears.
*
* @param Browser $browser
* @param string|null &$alertText Will be populated with the alert text if one was found.
*/
private function dismissAnyAlert(Browser $browser, ?string &$alertText = null): void
{
try {
$alert = $browser->driver->switchTo()->alert();
$alertText = $alert->getText();
$alert->accept();
} catch (\Facebook\WebDriver\Exception\NoSuchAlertException $e) {
$alertText = null; // No alert present — expected
}
}
}