Files
-S.I.S-QuaySo_v2/tests/Browser/GameLogicTest.php
T

228 lines
8.9 KiB
PHP
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.
<?php
namespace Tests\Browser;
use App\Models\Prize;
use App\Models\PrizeUser;
use App\Models\User;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
/**
* GameLogicTest — Kiểm thử logic trò chơi vòng quay may mắn.
*
* Yêu cầu: Server đang chạy trên APP_URL (php artisan serve --port=8001)
*
* [TC-50] test_prevent_double_click_spam_on_spin_button
* [TC-51] test_complete_spin_reward_flow
* [TC-52] test_spin_when_out_of_stock
*/
class GameLogicTest extends DuskTestCase
{
private User $gameUser;
private Prize $testPrize;
/**
* Tạo user chưa quay (spined=0) và một giải thưởng chắc chắn có sẵn trước mỗi test.
*/
protected function setUp(): void
{
parent::setUp();
$phone = '09' . rand(10000000, 99999999);
$this->gameUser = User::create([
'name' => 'DUSK GAME LOGIC USER',
'phone' => $phone,
'email' => $phone . '@phone.local',
'password' => bcrypt('test123'),
'address' => 'Cần Thơ',
'known_sis' => 1,
'job' => 'Tester',
'status' => 1,
'spined' => 0,
]);
// Tạo giải thưởng với prize_number rất lớn để wheel luôn có ít nhất 1 segment
$this->testPrize = Prize::create([
'prize_name' => 'Dusk Logic Test Prize',
'prize_status' => 1,
'prize_number' => 9999,
'prize_order' => 99,
'user_id' => 0,
]);
}
/**
* Dọn dẹp tất cả dữ liệu test sau mỗi test method.
*/
protected function tearDown(): void
{
PrizeUser::where('user_id', $this->gameUser->id)->delete();
$this->gameUser->delete();
$this->testPrize->delete();
parent::tearDown();
}
// =========================================================================
// [TC-50] Ngăn click spam lên nút quay
// =========================================================================
/**
* Click liên tục vào #spinButton phải bị chặn bởi cờ isSpinning trong game.js —
* chỉ tạo đúng 1 bản ghi PrizeUser dù kích nhiều lần.
*
* Cơ chế bảo vệ trong luckywheel.blade.php:
* let isSpinning = false;
* spinButton.addEventListener("click", (event) => {
* if (!isSpinning) {
* isSpinning = true; // khóa ngay sau click đầu
* spinWheel();
* }
* });
*
* LƯU Ý THỜI GIAN: Spin thực tế mất 1015 giây.
* waitForText timeout = 30 giây để bao phủ.
*/
public function test_prevent_double_click_spam_on_spin_button(): void
{
$this->browse(function (Browser $browser) {
$browser->loginAs($this->gameUser)
->visit('/game')
->pause(800); // Chờ wheel canvas render xong
// Kích 4 lần click liên tiếp trong cùng một JS execution tick.
// Click #1 → isSpinning = true, vòng quay bắt đầu.
// Click #2, #3, #4 → bị block bởi guard `if (!isSpinning)`.
$browser->script("
var btn = document.getElementById('spinButton');
btn.click();
btn.click();
btn.click();
btn.click();
");
// Chờ popup kết quả xuất hiện (AJAX /updateprizeuser trả về luckywheel_prize view)
$browser->waitForText('XIN CHÚC MỪNG', 30);
// Buffer thêm để DB transaction commit hoàn tất sau AJAX success callback
$browser->pause(1500);
});
// Assertion: chỉ đúng 1 bản ghi PrizeUser được tạo
// → Chứng minh 3 click sau bị isSpinning guard ngăn thành công
$count = PrizeUser::where('user_id', $this->gameUser->id)->count();
$this->assertEquals(
1,
$count,
"Phải có đúng 1 bản ghi PrizeUser — click spam bị ngăn bởi cờ isSpinning"
);
}
// =========================================================================
// [TC-51] Luồng quay hoàn chỉnh
// =========================================================================
/**
* Luồng quay đầy đủ: click #spinButton → animation → popup kết quả →
* DB cập nhật user.spined = 1 và tạo bản ghi PrizeUser có user_id khớp.
*
* LƯU Ý PLACEHOLDER: Selector `.reward-popup-class` trong yêu cầu ban đầu
* ánh xạ tới `#result` + text "XIN CHÚC MỪNG" trong app này.
* Nếu UI thay đổi, cập nhật waitForText() cho phù hợp.
*/
public function test_complete_spin_reward_flow(): void
{
$this->browse(function (Browser $browser) {
$browser->loginAs($this->gameUser)
->visit('/game')
->pause(800); // Chờ wheel canvas render xong
// Xác nhận nút quay ở trạng thái sẵn sàng (user chưa quay)
$browser->assertAttribute('#spinButton', 'data-check', 'true');
// Click nút quay (1 lần duy nhất)
$browser->click('#spinButton');
// Chờ div#result hiển thị popup kết quả (AJAX response từ /updateprizeuser)
// → View luckywheel_prize.blade.php render text "XIN CHÚC MỪNG"
$browser->waitForText('XIN CHÚC MỪNG', 30);
// Buffer để đảm bảo DB write hoàn tất sau AJAX success handler
$browser->pause(1500);
});
// DB assertion 1: user.spined phải là 1 sau khi quay xong
$user = User::find($this->gameUser->id);
$this->assertEquals(
1,
$user->spined,
"user.spined phải được cập nhật thành 1 sau khi quay thành công"
);
// DB assertion 2: bản ghi PrizeUser phải tồn tại với user_id khớp
$prizeUser = PrizeUser::where('user_id', $this->gameUser->id)->first();
$this->assertNotNull(
$prizeUser,
"Phải có bản ghi PrizeUser trong DB sau khi quay thành công"
);
$this->assertEquals(
$this->gameUser->id,
$prizeUser->user_id,
"PrizeUser.user_id phải khớp với người dùng vừa quay"
);
}
// =========================================================================
// [TC-52] Vòng quay khi hết giải thưởng (prize_number = 0)
// =========================================================================
/**
* Kiểm tra trạng thái trang game khi TẤT CẢ giải thưởng đã hết (prize_number = 0).
*
* HÀNH VI HIỆN TẠI được kiểm tra:
* - Controller không thêm segment nào vào prize_arr → game_data = "[]"
* - Wheel canvas render rỗng nhưng #spinButton vẫn hiển thị
*
* TODO — HÀNH VI MONG MUỐN (cần implement thêm vào frontend):
* Option A: Disable #spinButton với thông báo "Hết giải thưởng"
* Option B: Hiển thị element có class .no-prize-message
* Khi implement xong, bỏ comment các assertions phía dưới.
*
* Dữ liệu được KHÔI PHỤC trong finally block dù test pass hay fail.
*/
public function test_spin_when_out_of_stock(): void
{
// Lưu trữ prize_number gốc của tất cả giải thưởng
$originalNumbers = Prize::pluck('prize_number', 'id')->toArray();
// Đặt prize_number = 0 cho TẤT CẢ giải thưởng → mô phỏng "hết hàng"
Prize::query()->update(['prize_number' => 0]);
try {
$this->browse(function (Browser $browser) {
$browser->loginAs($this->gameUser)
->visit('/game')
->pause(500);
// HÀNH VI HIỆN TẠI: page source chứa game_data rỗng
// vì IndexController không có prize nào thỏa count_user < prize_number (= 0)
$browser->assertSourceHas('game_data = "[]"');
// Trang vẫn tải bình thường (không crash)
$browser->assertPresent('#wheelCanvas');
$browser->assertPresent('#spinButton');
// TODO: Bỏ comment khi đã thêm xử lý "hết giải" vào frontend:
// $browser->assertSee('Đã hết giải thưởng');
// $browser->assertPresent('.no-prize-message');
// $browser->assertAttribute('#spinButton', 'disabled', 'disabled');
});
} finally {
// LUÔN khôi phục dữ liệu gốc — kể cả khi test fail
foreach ($originalNumbers as $id => $number) {
Prize::where('id', $id)->update(['prize_number' => $number]);
}
}
}
}