164 lines
6.0 KiB
PHP
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);
|
|
}
|
|
}
|