bổ sung đủ 57 trường hợp test
This commit is contained in:
@@ -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 10–15 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user