Files
-S.I.S-QuaySo_v2/docs/testcase.txt
T

669 lines
39 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
================================================================================
DANH SÁCH 49 KỊCH BẢN KIỂM THỬ - LARAVEL DUSK BROWSER TESTS
Dự án: Quay Số May Mắn - quayso.sisvietnam.vn
Tổng kết: 49 tests PASSED | 90 assertions | ~103 giây
================================================================================
--------------------------------------------------------------------------------
FILE 1: tests/Browser/ExampleTest.php (1 test)
--------------------------------------------------------------------------------
[TC-01] test_basic_example
Mô tả : Kiểm tra cơ bản trang chủ tải được vòng quay may mắn.
Bước : 1. Truy cập URL gốc "/".
Kết quả: Phần tử #wheelCanvas (canvas vòng quay) hiển thị trên trang.
--------------------------------------------------------------------------------
FILE 2: tests/Browser/GamePageTest.php (12 tests)
--------------------------------------------------------------------------------
[TC-02] test_game_page_loads_when_guest
Mô tả : Trang game tải được khi người dùng chưa đăng nhập.
Bước : 1. Truy cập "/game" với vai trò khách (guest).
Kết quả: URL path là "/game" (trang không bị redirect).
[TC-03] test_root_redirects_to_game
Mô tả : URL gốc "/" hiển thị cùng nội dung với "/game".
Bước : 1. Truy cập "/".
Kết quả: Phần tử #wheelCanvas (canvas vòng quay) hiển thị.
[TC-04] test_game_page_shows_lucky_wheel_canvas
Mô tả : Canvas vòng quay may mắn hiển thị trên trang game.
Bước : 1. Truy cập "/game".
Kết quả: Phần tử #wheelCanvas hiển thị trong trang.
[TC-05] test_spin_button_is_present
Mô tả : Nút "Quay" hiển thị trên trang game.
Bước : 1. Truy cập "/game".
Kết quả: Phần tử #spinButton hiển thị trong trang.
[TC-06] test_game_rule_ribbon_is_present
Mô tả : Banner "Thể lệ tham dự" hiển thị trên trang game.
Bước : 1. Truy cập "/game".
Kết quả: Phần tử #game_rule hiển thị trong trang.
[TC-07] test_guest_sees_login_prompt_on_game
Mô tả : Khách chưa đăng nhập nhìn thấy thông báo yêu cầu đăng nhập.
Bước : 1. Đăng xuất ("/logout").
2. Truy cập "/game".
Kết quả: Thông báo ".alert_wheel_spin" hiển thị;
Link đăng nhập (href chứa "login") hiển thị.
[TC-08] test_logged_in_user_can_see_spin_button_enabled
Mô tả : Người dùng đã đăng nhập và chưa quay thấy nút quay ở trạng thái sẵn sàng.
Bước : 1. Đăng xuất và vào "/login".
2. Nhập thông tin cá nhân (họ tên, SĐT mới, địa chỉ, nguồn biết SIS, nghề nghiệp).
3. Nhấn "Gửi thông tin" và chờ redirect về "/game".
Kết quả: Thuộc tính data-check của #spinButton = "true" (nút quay được bật).
Dọn dẹp: Xóa user vừa tạo khỏi DB.
[TC-09] test_already_spined_user_sees_spin_button_disabled
Mô tả : Người dùng đã quay (spined=1) thấy nút quay bị khóa.
Chuẩn bị: Tạo user với spined=1 trực tiếp trong DB.
Bước : 1. Đăng nhập bằng user có spined=1.
2. Truy cập "/game".
Kết quả: Thuộc tính data-check của #spinButton = "false" (nút quay bị khóa).
Dọn dẹp: Xóa user test khỏi DB.
[TC-10] test_game_rule_popup_loads
Mô tả : Popup thể lệ tham dự tải nội dung khi nhấn banner.
Bước : 1. Truy cập "/game".
2. Xóa các element ".alert" bằng JavaScript (để không che banner).
3. Nhấn vào #game_rule bằng JavaScript click.
4. Chờ 800ms.
Kết quả: Phần tử #gamerule (popup) hiển thị trong trang.
[TC-11] test_game_rule_api_returns_html
Mô tả : API /game/rule trả về nội dung HTML (dùng cho AJAX).
Bước : 1. Truy cập "/game/rule" trực tiếp trên trình duyệt.
Kết quả: Source HTML của trang chứa chuỗi "class=" (là fragment HTML hợp lệ).
[TC-12] test_checkspined_returns_error_for_guest
Mô tả : API /checkspined trả lỗi khi người dùng chưa đăng nhập.
Bước : 1. Đăng xuất ("/logout").
2. Truy cập "/checkspined".
Kết quả: Trang hiển thị chuỗi "error".
[TC-13] test_checkspined_returns_zero_for_new_user
Mô tả : API /checkspined trả spined=0 cho người dùng mới chưa quay.
Chuẩn bị: Tạo user mới với spined=0 trong DB.
Bước : 1. Đăng nhập bằng user đó.
2. Truy cập "/checkspined".
Kết quả: Trang hiển thị chuỗi '"spined":0'.
Dọn dẹp: Xóa user test khỏi DB.
--------------------------------------------------------------------------------
FILE 3: tests/Browser/CreateAccountTest.php (5 tests)
--------------------------------------------------------------------------------
[TC-14] test_login_page_shows_form
Mô tả : Trang đăng ký/đăng nhập hiển thị đủ các trường nhập liệu.
Bước : 1. Đăng xuất để đảm bảo trạng thái guest.
2. Truy cập "/login".
Kết quả: Tiêu đề "Nhập thông tin cá nhân" hiển thị;
Các trường input[name="name"], input[name="phone"],
input[name="address"], input[name="job"] đều có mặt.ss
[TC-15] test_empty_form_shows_validation_errors
Mô tả : Gửi form rỗng phải hiện lỗi validation từ server.
Bước : 1. Đăng xuất và truy cập "/login".
2. Nhấn "Gửi thông tin" mà không nhập gì.
3. Chờ phần tử ".text-danger" xuất hiện.
Kết quả: Ít nhất một phần tử ".text-danger" (thông báo lỗi) hiển thị.
[TC-16] test_invalid_phone_shows_error
Mô tả : Số điện thoại sai định dạng phải hiện thông báo lỗi.
Bước : 1. Truy cập "/login".
2. Nhập tên hợp lệ, nhập SĐT = "12345" (sai định dạng).
3. Điền các trường còn lại và nhấn "Gửi thông tin".
Kết quả: Trang hiển thị "Số điện thoại không đúng định dạng".
[TC-17] test_successful_registration_redirects_to_game
Mô tả : Đăng ký thành công tạo tài khoản mới và chuyển về trang game.
Bước : 1. Truy cập "/login".
2. Nhập: tên="Test Dusk User", SĐT=số ngẫu nhiên (09xxxxxxxx),
địa chỉ="Cần Thơ", nguồn biết SIS=1, nghề nghiệp="Kỹ sư".
3. Nhấn "Gửi thông tin" và chờ thông báo "thành công".
Kết quả: URL path là "/game" (đăng ký thành công, redirect về game).
DB Check: User có phone tương ứng tồn tại trong DB;
name = "TEST DUSK USER" (đã uppercase);
address = "Cần Thơ"; known_sis = 1; job = "Kỹ sư";
email = "<phone>@phone.local"; spined = 0; status = 1.
Dọn dẹp: Xóa user test khỏi DB.
[TC-18] test_authenticated_user_sees_account_page
Mô tả : Người dùng đã đăng nhập truy cập /login sẽ thấy trang tài khoản của họ.
Bước : 1. Đăng ký tài khoản mới (SĐT ngẫu nhiên).
2. Sau khi đăng nhập, truy cập lại "/login".
Kết quả: Trường phone được điền sẵn số điện thoại của người dùng
(chứng tỏ đang hiển thị form cập nhật tài khoản, không phải form đăng ký).
DB Check: User tồn tại; spined = 0; status = 1.
Dọn dẹp: Xóa user test khỏi DB.
--------------------------------------------------------------------------------
FILE 4: tests/Browser/AccountManagementTest.php (5 tests)
--------------------------------------------------------------------------------
[TC-19] test_update_account_successfully
Mô tả : Người dùng cập nhật thông tin tài khoản thành công.
Bước : 1. Đăng ký và đăng nhập tài khoản mới.
2. Truy cập "/login" (trang tài khoản).
3. Xóa và nhập lại: tên="Tên Mới Test", địa chỉ="Hà Nội",
nguồn biết SIS=0, nghề nghiệp="Giáo viên".
4. Nhấn "Cập nhật" và chờ thông báo "thành công".
Kết quả: URL path là "/game" (cập nhật thành công).
DB Check: name = "TÊN MỚI TEST" (đã uppercase); address = "Hà Nội";
known_sis = 0; job = "Giáo viên".
Dọn dẹp: Xóa user test khỏi DB.
[TC-20] test_update_account_with_empty_name_shows_error
Mô tả : Cập nhật tài khoản với tên rỗng phải hiện lỗi validation.
Bước : 1. Đăng ký và đăng nhập tài khoản mới.
2. Truy cập "/login", xóa trường tên.
3. Nhấn "Cập nhật".
Kết quả: Phần tử ".text-danger" (thông báo lỗi) hiển thị.
[TC-21] test_update_account_with_invalid_phone_shows_error
Mô tả : Cập nhật tài khoản với số điện thoại sai định dạng phải hiện lỗi.
Bước : 1. Đăng ký và đăng nhập tài khoản mới.
2. Truy cập "/login", xóa và nhập SĐT = "abcde".
3. Nhấn "Cập nhật".
Kết quả: Trang hiển thị "Số điện thoại không đúng định dạng".
[TC-22] test_history_section_visible_when_authenticated
Mô tả : Phần lịch sử hoạt động hiển thị khi người dùng đã đăng nhập.
Bước : 1. Đăng ký và đăng nhập tài khoản mới.
2. Truy cập "/login" (trang tài khoản).
Kết quả: Trang hiển thị "LỊCH SỬ HOẠT ĐỘNG".
[TC-23] test_logout_works
Mô tả : Đăng xuất thành công xóa session người dùng.
Bước : 1. Đăng ký và đăng nhập tài khoản mới.
2. Truy cập "/logout".
Kết quả: Người dùng ở trạng thái guest (assertGuest).
--------------------------------------------------------------------------------
FILE 5: tests/Browser/AdminLoginTest.php (7 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Trước mỗi test tạo admin user với đầy đủ roles trong DB.
Dọn dẹp chung: Sau mỗi test xóa UserRole và admin user khỏi DB.
[TC-24] test_admin_login_page_shows_form
Mô tả : Trang quản trị "/admin" hiển thị form đăng nhập.
Bước : 1. Truy cập "/admin".
Kết quả: Các trường input[name="email"], input[name="password"],
input[type="submit"] đều hiển thị.
[TC-25] test_admin_login_with_wrong_credentials_shows_error
Mô tả : Đăng nhập admin với thông tin sai phải hiện thông báo lỗi.
Bước : 1. Truy cập "/admin".
2. Nhập email="wrong@email.com", password="wrongpassword".
3. Nhấn "Login".
Kết quả: Phần tử ".alert-danger" (hoặc tương đương) hiển thị.
[TC-26] test_admin_login_with_empty_fields_shows_validation
Mô tả : Gửi form đăng nhập admin rỗng phải hiện lỗi validation server-side.
Bước : 1. Truy cập "/admin".
2. Dùng JavaScript xóa thuộc tính "required" khỏi tất cả input
(để bypass validation HTML5 của trình duyệt).
3. Nhấn "Login" mà không nhập gì.
Kết quả: Phần tử ".text-danger" hoặc ".alert-danger" hiển thị.
[TC-27] test_admin_login_successful
Mô tả : Đăng nhập admin thành công và chuyển vào trang quản lý.
Bước : 1. Đăng xuất admin ("/admin/logout").
2. Truy cập "/admin", nhập email và password đúng.
3. Nhấn "Login" và chờ redirect.
Kết quả: URL path là "/admin/account" (đăng nhập thành công).
[TC-28] test_authenticated_admin_visiting_login_sees_form
Mô tả : Admin đã đăng nhập truy cập "/admin" vẫn thấy form login (không redirect).
Bước : 1. Đăng nhập bằng loginAs(adminUser).
2. Truy cập "/admin".
Kết quả: URL path vẫn là "/admin"; form input[name="email"] hiển thị.
[TC-29] test_unauthenticated_access_to_admin_account_redirects
Mô tả : Truy cập trang admin khi chưa đăng nhập bị redirect về trang login.
Bước : 1. Đăng xuất admin.
2. Truy cập "/admin/account" trực tiếp.
Kết quả: URL path là "/admin" (bị redirect về trang đăng nhập admin).
[TC-30] test_admin_logout
Mô tả : Đăng xuất admin thành công — sau đó không thể vào trang quản lý.
Bước : 1. Đăng xuất admin (clear session cũ).
2. Đăng nhập thực qua form với email/password đúng.
3. Chờ redirect vào "/admin/account".
4. Đăng xuất qua "/logout" (frontend route).
5. Truy cập "/admin/account".
Kết quả: URL path là "/admin" (bị redirect về login, xác nhận đã logout).
Lưu ý : Dùng đăng nhập form thực (không dùng loginAs) để tránh Dusk
tự động re-authenticate sau khi logout.
--------------------------------------------------------------------------------
FILE 6: tests/Browser/AdminPrizeTest.php (9 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo admin user với đầy đủ roles trong DB.
Dọn dẹp chung: Xóa UserRole và admin user khỏi DB.
[TC-31] test_prize_list_page_loads
Mô tả : Trang danh sách giải thưởng tải thành công.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/prizes".
Kết quả: Phần tử "table" hoặc ".table" hiển thị.
[TC-32] test_add_prize_form_loads
Mô tả : Trang thêm mới giải thưởng hiển thị đầy đủ form.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/prize/update?id=0".
Kết quả: Các trường input[name="prize_name"], input[name="prize_order"],
select[name="prize_status"] đều hiển thị.
[TC-33] test_add_prize_with_empty_name_shows_error
Mô tả : Gửi form thêm giải thưởng với tên rỗng phải hiện lỗi validation.
Bước : 1. Đăng nhập admin, vào "/admin/prize/update?id=0".
2. Dùng JavaScript xóa thuộc tính "required" của các input.
3. Nhấn "Thêm mới" mà không nhập tên.
Kết quả: Phần tử ".text-danger", ".alert-danger" hoặc tương đương hiển thị.
[TC-34] test_create_prize_successfully
Mô tả : Thêm mới giải thưởng thành công và dữ liệu được lưu vào DB.
Bước : 1. Đăng nhập admin, vào "/admin/prize/update?id=0".
2. Nhập: prize_name=tên ngẫu nhiên, prize_number=100,
prize_order=99, prize_status=1.
3. Nhấn "Thêm mới" và chờ ".alert-success".
Kết quả: Thông báo thành công hiển thị.
DB Check: Prize với tên vừa nhập tồn tại trong DB;
prize_status = 1; prize_number = 100; prize_order = 99.
Dọn dẹp: Xóa prize test khỏi DB.
[TC-35] test_update_prize_successfully
Mô tả : Cập nhật tên giải thưởng thành công.
Chuẩn bị: Tạo prize "Dusk Prize To Update" trực tiếp trong DB.
Bước : 1. Đăng nhập admin, vào "/admin/prize/update?id={prizeId}".
2. Xóa và nhập tên mới vào prize_name.
3. Nhấn "Cập nhật" và chờ ".alert-success".
Kết quả: Thông báo thành công hiển thị.
DB Check: Prize::find(id)->prize_name bằng tên mới vừa nhập.
Dọn dẹp: Xóa prize test khỏi DB.
[TC-36] test_delete_prize_successfully
Mô tả : Xóa giải thưởng thành công khỏi hệ thống.
Chuẩn bị: Tạo prize "Dusk Prize To Delete" trực tiếp trong DB.
Bước : 1. Đăng nhập admin, vào "/admin/prizes".
2. Truy cập "/admin/prize/delete/{prizeId}" trực tiếp.
3. Chờ 1000ms (hệ thống redirect sau khi xóa).
Kết quả: Prize::find(prizeId) trả về null (đã bị xóa khỏi DB).
[TC-37] test_prize_winner_list_loads
Mô tả : Trang danh sách người trúng giải tải thành công.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/prizeuser".
Kết quả: Phần tử "table" hoặc ".table" hiển thị.
[TC-38] test_prize_winner_list_has_date_filter
Mô tả : Trang danh sách người trúng giải có bộ lọc theo ngày.
Bước : 1. Đăng nhập admin, truy cập "/admin/prizeuser".
Kết quả: Phần tử #fromDateTime và #toDateTime hiển thị.
[TC-39] test_prize_winner_export_link_present
Mô tả : Trang danh sách người trúng giải có nút/link xuất Excel.
Bước : 1. Đăng nhập admin, truy cập "/admin/prizeuser".
Kết quả: Phần tử #excel_export hiển thị.
--------------------------------------------------------------------------------
FILE 7: tests/Browser/AdminUserTest.php (5 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo admin user (với đầy đủ roles) và target user trong DB.
Dọn dẹp chung: Xóa UserRole, admin user và target user khỏi DB.
[TC-40] test_user_list_page_loads
Mô tả : Trang danh sách người dùng trong admin tải thành công.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/users".
Kết quả: Phần tử "table" hoặc ".table" hiển thị.
[TC-41] test_user_list_shows_target_user
Mô tả : User mới tạo xuất hiện trong danh sách quản lý người dùng.
Bước : 1. Truy vấn DB: User với status != '' (điều kiện của controller).
2. Đăng nhập admin, truy cập "/admin/users", chờ #table.
Kết quả: Target user tồn tại trong query DB (email khớp);
Bảng #table hiển thị trên trang.
Lưu ý : DataTables có phân trang; dùng DB assertion thay vì tìm kiếm text
trực tiếp trên trình duyệt.
[TC-42] test_edit_user_form_shows_existing_data
Mô tả : Form cập nhật user điền sẵn thông tin hiện tại.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/users/update?id={targetUserId}".
Kết quả: Trường input "name" có giá trị = tên hiện tại của user;
Trường input "email" có giá trị = email hiện tại của user.
[TC-43] test_update_user_name_successfully
Mô tả : Admin cập nhật tên người dùng thành công (không uppercase).
Bước : 1. Đăng nhập admin, vào "/admin/users/update?id={targetUserId}".
2. Xóa và nhập tên mới = "Updated Dusk Name".
3. Nhấn "Cập nhật" và chờ ".alert-success".
Kết quả: Thông báo thành công hiển thị.
DB Check: User::find(id)->name = "Updated Dusk Name"
(Admin controller KHÔNG uppercase tên, khác với frontend controller).
[TC-44] test_admin_account_page_shows_user_info
Mô tả : Trang tài khoản admin hiển thị đúng thông tin người dùng đang đăng nhập.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/account".
Kết quả: Trường input "email" có giá trị = email của admin đang đăng nhập.
--------------------------------------------------------------------------------
FILE 8: tests/Browser/AdminParameterTest.php (5 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo admin user với đầy đủ roles trong DB.
Dọn dẹp chung: Xóa UserRole và admin user khỏi DB.
[TC-45] test_parameter_page_loads
Mô tả : Trang cấu hình hệ thống (parameter) tải thành công.
Bước : 1. Đăng nhập admin.
2. Truy cập "/admin/parameter".
Kết quả: Phần tử "form" hiển thị trong trang.
[TC-46] test_parameter_page_shows_game_rule_field
Mô tả : Trang cấu hình có trường nhập "Thể lệ tham dự" (game_rule).
Bước : 1. Đăng nhập admin, truy cập "/admin/parameter".
Kết quả: Phần tử [name="game_rule"] hoặc textarea hiển thị.
[TC-47] test_update_game_rule_successfully
Mô tả : Cập nhật nội dung thể lệ tham dự thành công và lưu vào DB.
Chuẩn bị: Đọc và lưu giá trị game_rule hiện tại từ DB.
Bước : 1. Đăng nhập admin, truy cập "/admin/parameter".
2. Dùng JavaScript gán giá trị mới vào [name="game_rule"]
(hỗ trợ cả CKEditor nếu có).
3. Nhấn "Cập nhật" và chờ ".alert-success".
Kết quả: Thông báo thành công hiển thị.
DB Check: Parameter với key="game_rule" tồn tại;
parameter_value chứa chuỗi vừa nhập (assertStringContainsString).
Dọn dẹp: Khôi phục giá trị game_rule gốc trong DB.
[TC-48] test_parameter_page_has_image_upload_fields
Mô tả : Trang cấu hình có trường upload ảnh logo.
Bước : 1. Đăng nhập admin, truy cập "/admin/parameter".
Kết quả: Phần tử input[type="file"][name="logo_image"] hoặc
input[name="logo_image"] hiển thị.
[TC-49] test_parameter_page_requires_auth
Mô tả : Truy cập "/admin/parameter" khi chưa đăng nhập bị redirect.
Bước : 1. Đăng xuất admin ("/admin/logout").
2. Truy cập "/admin/parameter" trực tiếp.
Kết quả: URL path là "/admin" (bị redirect về trang đăng nhập admin).
--------------------------------------------------------------------------------
FILE 9: tests/Browser/GameLogicTest.php (3 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo user mới (spined=0) và một prize có prize_number=9999 trong DB.
Dọn dẹp chung: Xóa PrizeUser, gameUser và testPrize khỏi DB sau mỗi test.
[TC-50] test_prevent_double_click_spam_on_spin_button
Mô tả : Click liên tiếp nhiều lần lên #spinButton chỉ tạo đúng 1 lần quay
nhờ cờ bảo vệ isSpinning trong game.js.
Cơ chế : let isSpinning = false; → click đầu tiên set isSpinning = true;
các click sau bị block bởi guard if (!isSpinning).
Bước : 1. Đăng nhập user chưa quay (spined=0), truy cập "/game".
2. Chờ 800ms để wheel canvas render xong.
3. Dùng JavaScript gọi spinButton.click() 4 lần liên tiếp.
4. Chờ popup kết quả "XIN CHÚC MỪNG" xuất hiện (timeout 30s).
5. Chờ thêm 1500ms để DB transaction commit hoàn tất.
Kết quả: Browser: popup "XIN CHÚC MỪNG" hiển thị.
DB Check: PrizeUser::where('user_id', ...) count = 1
(chứng minh 3 click sau bị ngăn — chỉ 1 lần quay được ghi nhận).
Lưu ý : Spin thực mất 1015s; waitForText timeout = 30s.
[TC-51] test_complete_spin_reward_flow
Mô tả : Luồng quay đầy đủ: click → animation → popup kết quả → DB cập nhật.
Bước : 1. Đăng nhập user chưa quay, vào "/game".
2. Chờ 800ms để wheel render.
3. Xác nhận #spinButton có data-check="true".
4. Click #spinButton (1 lần).
5. Chờ text "XIN CHÚC MỪNG" trong div#result (AJAX response từ
/updateprizeuser, render view luckywheel_prize.blade.php).
6. Chờ thêm 1500ms để DB write hoàn tất.
Kết quả: Browser: div#result hiển thị "XIN CHÚC MỪNG".
DB Check: User::find(id)->spined = 1 (đã được cập nhật sau khi quay);
PrizeUser::where('user_id', id)->first() != null;
PrizeUser.user_id khớp với id của người dùng vừa quay.
Lưu ý : Selector tương ứng ".reward-popup-class" trong yêu cầu ban đầu
ánh xạ tới div#result + text "XIN CHÚC MỪNG" trong app này.
[TC-52] test_spin_when_out_of_stock
Mô tả : Kiểm tra trạng thái trang game khi TẤT CẢ giải thưởng hết hàng
(prize_number = 0 cho mọi prize).
Chuẩn bị: Lưu prize_number gốc của tất cả prizes → update prize_number = 0.
Bước : 1. Đăng nhập user, vào "/game".
2. Chờ 500ms.
Kết quả: Source HTML chứa 'game_data = "[]"' (IndexController không thêm
segment nào vì count_user >= prize_number với mọi prize_number = 0).
Trang vẫn tải bình thường: #wheelCanvas và #spinButton hiển thị.
Dọn dẹp: Khôi phục tất cả prize_number về giá trị gốc trong finally block.
TODO : Khi implement xử lý "hết giải" vào frontend (assertSee "Đã hết giải
thưởng" / assertPresent ".no-prize-message"), bỏ comment assertions
tương ứng trong test.
--------------------------------------------------------------------------------
FILE 10: tests/Browser/SecurityTest.php (2 tests)
--------------------------------------------------------------------------------
Chuẩn bị chung: Tạo regularUser (không có role) và adminUser (đầy đủ roles).
Dọn dẹp chung: Xóa UserRole, adminUser, regularUser khỏi DB sau mỗi test.
[TC-53] test_regular_user_cannot_access_admin_area
Mô tả : Người dùng đã đăng nhập nhưng không có role admin bị redirect
khỏi mọi route trong middleware UserRole.
Cơ chế : Middleware UserRole → constructMenuLeft() → menu rỗng nếu không
có UserRole record → mọi admin route bị chặn → redirect('admin').
Bước : 1. Đăng nhập bằng regularUser (loginAs).
2. Truy cập "/admin/account".
3. Truy cập "/admin/users".
4. Truy cập "/admin/prizes".
Kết quả: Cả 3 lần truy cập đều redirect về path "/admin"
(trang đăng nhập admin, không phải trang quản lý).
[TC-54] test_xss_injection_on_registration_form
Mô tả : Nhập XSS payload vào trường name khi đăng ký — ứng dụng phải
escape đúng cách, không thực thi JavaScript.
Payload: name = <script>alert('XSS')</script>
Bước : 1. Đăng ký tài khoản mới với name = payload XSS.
2. Đăng xuất.
3. Đăng nhập admin → vào "/admin/users" → chờ #table.
4. Dismiss mọi alert dialog (DataTables warnings) nếu có.
Kết quả: Source HTML KHÔNG chứa "<script>alert('XSS')</script>" (unescaped);
Source HTML KHÔNG chứa "<SCRIPT>ALERT('XSS')</SCRIPT>" (unescaped);
Source HTML CÓ CHỨA "&lt;SCRIPT&gt;" (Blade {{ }} đã HTML-escape).
Cơ chế : Blade {{ }} tự escape → Controller mb_strtoupper → lưu DB → render
escaped. Payload thực tế trong DB: <SCRIPT>ALERT('XSS')</SCRIPT>.
Output trong HTML: &lt;SCRIPT&gt;ALERT(&#039;XSS&#039;)&lt;/SCRIPT&gt;.
Lưu ý : Dùng helper dismissAnyAlert() trước khi assert để tránh Chrome
bị lock bởi DataTables errMode alert dialog còn mở.
Sửa đi kèm: public/backend/js/main.js → DataTable.ext.errMode = 'none'
(tắt alert dialog của DataTables, dùng silent fail thay thế).
Dọn dẹp: Xóa user XSS test khỏi DB trong finally block.
--------------------------------------------------------------------------------
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ị
server từ chối bằng validation error.
Sửa app: PrizeController::updatePrize() thêm 'prize_number' => 'nullable|numeric|min:0'
+ message 'Số lượng giải thưởng không được âm'.
Bước : 1. Đăng nhập admin, vào "/admin/prize/update?id=0".
2. Dùng JavaScript xóa thuộc tính required và min khỏi input
(bypass HTML5 client-side validation).
3. Nhập prize_name hợp lệ, prize_number=-5, prize_order=10.
4. Nhấn "Thêm mới".
Kết quả: Phần tử ".text-danger" hoặc ".alert-danger" hiển thị (validation error).
DB Check: Prize::where('prize_name', testName)->first() = null
(Prize với số âm KHÔNG được tạo trong DB).
[TC-56] test_invalid_logo_upload_is_rejected
Mô tả : Upload file PDF vào trường logo_image phải bị server từ chối.
Sửa app: ParameterController::update() thêm $request->validate([
'logo_image' => 'nullable|mimes:jpg,png,jpeg,webp,jfif|max:2048']).
Fix bảo mật OWASP A05: Unrestricted File Upload.
Bước : 1. Tạo file PDF giả tạm thời (sys_get_temp_dir()).
2. Đăng nhập admin, vào "/admin/parameter".
3. Attach file PDF vào input[name="logo_image"].
4. Nhấn "Cập nhật".
Kết quả: Phần tử ".text-danger" hoặc ".alert-danger" hiển thị.
Trang KHÔNG hiển thị "Cập nhật thành công".
Dọn dẹp: Xóa file PDF tạm trong finally block.
[TC-57] test_prize_winner_export_excel_response
Mô tả : Nút export Excel (#excel_export) tồn tại và href khi click trỏ
đúng route admin/prizeuser/export; route tồn tại trong hệ thống.
Lưu ý : #excel_export là <a> KHÔNG có static href attribute. Href được tạo
động trong JS onclick: document.createElement("a"); a.href = exportUrl.
→ Không thể dùng attribute('#excel_export', 'href') để kiểm tra.
Bước A : 1. Đăng nhập admin, vào "/admin/prizeuser".
2. Xác nhận #excel_export tồn tại.
3. Dùng JavaScript override document.createElement để capture
href được gán cho thẻ <a> khi #excel_export.click().
Kết quả A: capturedHref != null;
capturedHref chứa chuỗi 'prizeuser/export'.
Bước B : Gọi route('admin/prizeuser/export') bằng PHP helper.
Kết quả B: URL trả về chứa '/admin/prizeuser/export'
(xác nhận route đã được đăng ký trong hệ thống).
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 : 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:
┌──────────────────────────────────────────┬────────┐
│ Chức năng │ Tests │
├──────────────────────────────────────────┼────────┤
│ ExampleTest (sanity check) │ 1 │
│ Trang game & API vòng quay │ 12 │
│ Đăng ký tài khoản │ 5 │
│ Quản lý tài khoản người dùng │ 5 │
│ Đăng nhập / đăng xuất admin │ 7 │
│ Quản lý giải thưởng (admin) │ 9 │
│ Quản lý người dùng (admin) │ 5 │
│ Cấu hình hệ thống (admin) │ 5 │
│ 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 │ 58 │
└──────────────────────────────────────────┴────────┘
Công nghệ kiểm thử:
- Framework : Laravel Dusk v8.5 (Browser Automation Testing)
- Browser : Google Chrome 146 (non-snap, /usr/bin/google-chrome)
- Driver : ChromeDriver 146
- 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ử:
┌──────────────────────────────────────────────────────────────────────┐
│ 1. PrizeController — thiếu min:0 trong prize_number validation │
│ → Cho phép tạo giải thưởng với số lượng âm (bug logic) │
│ 2. ParameterController — không validate MIME type khi upload ảnh │
│ → OWASP A05: Unrestricted File Upload (lỗ hổng bảo mật) │
│ 3. DataTables errMode = "alert" trong main.js │
│ → Alert dialog blocking Chrome khi render HTML content trong cell│
└──────────────────────────────────────────────────────────────────────┘
================================================================================