Files
cabutan-bertuah-koipb/tests/Feature/DrawTest.php
2026-06-24 18:30:00 +08:00

164 lines
6.0 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\AttendanceRecord;
use App\Models\DrawResult;
use App\Models\DrawSession;
use App\Models\Member;
use App\Models\Prize;
use App\Models\User;
use App\Services\DrawService;
use Database\Seeders\RoleUserSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class DrawTest extends TestCase
{
use RefreshDatabase;
private DrawService $service;
private DrawSession $session;
private User $petugas;
protected function setUp(): void
{
parent::setUp();
$this->seed(RoleUserSeeder::class);
$this->service = app(DrawService::class);
$this->session = DrawSession::active();
$this->petugas = User::where('email', 'cabutan@koipb.test')->first();
}
private function attendedMember(array $attrs = []): Member
{
$member = Member::factory()->create($attrs);
AttendanceRecord::create(['member_id' => $member->id, 'attended_at' => now()]);
return $member;
}
public function test_only_attended_members_are_eligible(): void
{
$hadir = $this->attendedMember();
$tidakHadir = Member::factory()->create();
$pool = $this->service->eligibleMembers($this->session);
$this->assertTrue($pool->contains('id', $hadir->id));
$this->assertFalse($pool->contains('id', $tidakHadir->id));
}
public function test_spin_creates_pending_result_and_marks_prize(): void
{
$this->attendedMember();
$prize = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$result = $this->service->spin($prize, $this->session, $this->petugas);
$this->assertEquals(DrawResult::STATUS_PENDING, $result->status);
$this->assertEquals(Prize::STATUS_SEDANG, $prize->fresh()->status);
}
public function test_confirmed_winner_cannot_win_again(): void
{
$member = $this->attendedMember();
$prize1 = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$prize2 = Prize::create(['nama_hadiah' => 'Radio', 'draw_order' => 2]);
$r1 = $this->service->spin($prize1, $this->session, $this->petugas);
$this->service->confirm($r1, $this->petugas);
// Member sudah menang -> tidak lagi dalam pool
$this->assertFalse($this->service->eligibleMembers($this->session)->contains('id', $member->id));
// Tiada peserta layak lain -> spin prize2 patut gagal
$this->expectException(\RuntimeException::class);
$this->service->spin($prize2, $this->session, $this->petugas);
}
public function test_db_blocks_duplicate_confirmed_winner(): void
{
$member = $this->attendedMember();
$prize1 = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$prize2 = Prize::create(['nama_hadiah' => 'Radio', 'draw_order' => 2]);
$r1 = $this->service->spin($prize1, $this->session, $this->petugas);
$this->service->confirm($r1, $this->petugas);
// Cuba paksa member sama menang prize2 (langkau pool) -> unique index DB halang
$r2 = DrawResult::create([
'draw_session_id' => $this->session->id,
'prize_id' => $prize2->id,
'member_id' => $member->id,
'status' => DrawResult::STATUS_PENDING,
'spun_at' => now(),
]);
$this->expectException(\Illuminate\Database\UniqueConstraintViolationException::class);
$r2->update([
'status' => DrawResult::STATUS_CONFIRMED,
'confirmed_member_id' => $member->id,
'confirmed_prize_id' => $prize2->id,
]);
}
public function test_cancelled_draw_allows_redraw(): void
{
$member1 = $this->attendedMember();
$member2 = $this->attendedMember();
$prize = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$r1 = $this->service->spin($prize, $this->session, $this->petugas);
$this->service->cancel($r1, $this->petugas, 'Tiada di dewan');
// Hadiah jadi redraw_required tetapi masih available
$prize->refresh();
$this->assertEquals(Prize::STATUS_REDRAW, $prize->status);
$this->assertTrue($prize->isAvailable());
$this->assertEquals(1, $prize->redraw_count);
// Boleh spin semula
$r2 = $this->service->spin($prize, $this->session, $this->petugas);
$this->assertEquals(DrawResult::STATUS_PENDING, $r2->status);
$this->assertEquals(2, $r2->attempt_number);
// Default setting (return_cancelled_to_pool = true): peserta dibatalkan kekal dalam pool
$this->assertTrue($this->service->eligibleMembers($this->session)->contains('id', $member1->id));
}
public function test_setting_can_remove_cancelled_member_from_pool(): void
{
// Tetapan admin: keluarkan peserta dibatalkan dari pool (tiada peluang kedua)
$this->session->update(['return_cancelled_to_pool' => false]);
$member1 = $this->attendedMember();
$member2 = $this->attendedMember();
$prize = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$r1 = $this->service->spin($prize, $this->session, $this->petugas);
$cancelledId = $r1->member_id;
$this->service->cancel($r1, $this->petugas, 'Tiada di dewan');
$pool = $this->service->eligibleMembers($this->session->fresh());
// Peserta yang dibatalkan TIDAK lagi dalam pool; peserta lain masih ada
$this->assertFalse($pool->contains('id', $cancelledId));
$remainingId = $cancelledId === $member1->id ? $member2->id : $member1->id;
$this->assertTrue($pool->contains('id', $remainingId));
}
public function test_cannot_spin_already_confirmed_prize(): void
{
$this->attendedMember();
$this->attendedMember();
$prize = Prize::create(['nama_hadiah' => 'TV', 'draw_order' => 1]);
$r1 = $this->service->spin($prize, $this->session, $this->petugas);
$this->service->confirm($r1, $this->petugas);
$this->expectException(\RuntimeException::class);
$this->service->spin($prize, $this->session, $this->petugas);
}
}