bổ sung đủ 57 trường hợp test

This commit is contained in:
2026-04-08 08:36:52 +07:00
parent c026e4cc2c
commit 2fd2c6ddb2
10 changed files with 857 additions and 7 deletions
+227
View File
@@ -0,0 +1,227 @@
<?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]);
}
}
}
}