This commit is contained in:
Saufi
2026-06-03 08:51:22 +08:00
commit a14d43fe34
347 changed files with 38197 additions and 0 deletions

View File

@@ -0,0 +1,347 @@
<?php
namespace Tests\Feature;
use App\Models\Application;
use App\Models\Election;
use App\Models\JkmRepresentative;
use App\Models\Position;
use App\Models\PusatMengundi;
use App\Models\StaffAssignment;
use App\Models\SystemNote;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class AdminManagementTest extends TestCase
{
use RefreshDatabase;
public function test_admin_can_create_manual_application(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
$this->actingAs($admin)
->post(route('admin.management.applications.store'), [
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'name' => 'Manual Admin',
'ic_number' => '900101105555',
'phone_number' => '0123456789',
'email' => 'manual@example.test',
'bank_name' => 'Maybank',
'bank_account_number' => '1234567890',
])
->assertRedirect();
$application = Application::query()->where('ic_number', '900101105555')->firstOrFail();
$this->assertSame('admin_manual', $application->source);
$this->assertSame('submitted', $application->status);
$this->assertTrue($application->bankVerification()->exists());
}
public function test_admin_edit_after_registration_closed_requires_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
$application = Application::factory()->create([
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'ic_number' => '900101105556',
]);
$this->closeRegistration($pusat->election);
$payload = [
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'status' => 'submitted',
'name' => 'Manual Edit',
'ic_number' => '900101105556',
'phone_number' => '0123456789',
'email' => 'manual-edit@example.test',
];
$this->actingAs($admin)
->patch(route('admin.management.applications.update', $application->public_uuid), $payload)
->assertSessionHasErrors('note');
$this->actingAs($admin)
->patch(route('admin.management.applications.update', $application->public_uuid), [
...$payload,
'note' => 'Pembetulan selepas tutup pendaftaran.',
])
->assertRedirect();
$this->assertTrue(SystemNote::query()
->where('noteable_type', Application::class)
->where('noteable_id', $application->id)
->where('note_type', 'admin_application_edit_after_close')
->exists());
}
public function test_admin_can_direct_assign_application_after_close_with_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
$application = Application::factory()->create([
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'ic_number' => '900101105557',
]);
$this->closeRegistration($pusat->election);
$this->actingAs($admin)
->post(route('admin.management.applications.assign', $application->public_uuid), [
'position_id' => $position->id,
'pusat_mengundi_id' => $pusat->id,
'status' => 'active',
])
->assertSessionHasErrors('note');
$this->actingAs($admin)
->post(route('admin.management.applications.assign', $application->public_uuid), [
'position_id' => $position->id,
'pusat_mengundi_id' => $pusat->id,
'status' => 'active',
'note' => 'Lantikan manual selepas tutup.',
])
->assertRedirect();
$application->refresh();
$this->assertSame('assigned', $application->status);
$this->assertTrue(StaffAssignment::query()
->where('application_id', $application->id)
->where('position_id', $position->id)
->where('source', 'admin_direct')
->exists());
$this->assertTrue(SystemNote::query()->where('note_type', 'admin_assignment_after_close')->exists());
}
public function test_admin_can_create_representative_record(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$this->actingAs($admin)
->post(route('admin.management.representatives.store'), [
'type' => 'jkm',
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'name' => 'Wakil JKM',
'ic_number' => '900101105558',
'phone_number' => '0123456789',
'agency' => 'JKM',
])
->assertRedirect(route('admin.management.representatives.index'));
$this->assertTrue(JkmRepresentative::query()->where('name', 'Wakil JKM')->exists());
}
public function test_admin_kewangan_cannot_access_admin_management_routes(): void
{
$this->seed(DatabaseSeeder::class);
$finance = User::query()->where('email', 'kewangan@prn.local')->firstOrFail();
$this->actingAs($finance)
->get(route('admin.management.assignments.index'))
->assertForbidden();
}
public function test_admin_create_application_after_registration_closed_requires_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
$this->closeRegistration($pusat->election);
$payload = [
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'name' => 'Manual Post-Close',
'ic_number' => '900101105560',
'phone_number' => '0123456789',
'email' => 'post-close@example.test',
'bank_name' => 'Maybank',
'bank_account_number' => '1234567890',
];
// Without note — both middleware and service should reject
$this->actingAs($admin)
->post(route('admin.management.applications.store'), $payload)
->assertSessionHasErrors('note');
$this->assertDatabaseMissing('applications', ['ic_number' => '900101105560']);
// With note — must succeed
$this->actingAs($admin)
->post(route('admin.management.applications.store'), [
...$payload,
'note' => 'Manual entry selepas tutup pendaftaran.',
])
->assertRedirect();
$this->assertDatabaseHas('applications', ['ic_number' => '900101105560']);
}
public function test_admin_update_assignment_after_registration_closed_requires_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
$assignment = StaffAssignment::query()
->where('election_id', $pusat->election_id)
->where('pusat_mengundi_id', $pusat->id)
->where('status', 'active')
->firstOrFail();
$this->closeRegistration($pusat->election);
$payload = [
'position_id' => $assignment->position_id,
'pusat_mengundi_id' => $pusat->id,
'status' => 'active',
];
// Without note
$this->actingAs($admin)
->patch(route('admin.management.assignments.update', $assignment), $payload)
->assertSessionHasErrors('note');
// With note
$this->actingAs($admin)
->patch(route('admin.management.assignments.update', $assignment), [
...$payload,
'note' => 'Kemaskini tugasan selepas tutup.',
])
->assertRedirect();
$this->assertTrue(SystemNote::query()
->where('note_type', 'admin_assignment_edit_after_close')
->exists());
}
public function test_admin_create_representative_after_registration_closed_requires_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$this->closeRegistration($pusat->election);
$payload = [
'type' => 'kkm',
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'name' => 'Wakil KKM Post-Close',
];
// Without note
$this->actingAs($admin)
->post(route('admin.management.representatives.store'), $payload)
->assertSessionHasErrors('note');
// With note
$this->actingAs($admin)
->post(route('admin.management.representatives.store'), [
...$payload,
'note' => 'Tambah wakil selepas tutup.',
])
->assertRedirect();
$this->assertDatabaseHas('kkm_representatives', ['name' => 'Wakil KKM Post-Close']);
}
public function test_admin_delete_representative_after_registration_closed_requires_catatan(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$jkm = JkmRepresentative::query()->create([
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'name' => 'JKM Untuk Padam',
]);
$this->closeRegistration($pusat->election);
// Without note — middleware blocks before service runs
$this->actingAs($admin)
->delete(route('admin.management.representatives.destroy', ['type' => 'jkm', 'id' => $jkm->id]))
->assertSessionHasErrors('note');
$this->assertDatabaseHas('jkm_representatives', ['id' => $jkm->id]);
// With note — service enforces its own check too
$this->actingAs($admin)
->delete(route('admin.management.representatives.destroy', ['type' => 'jkm', 'id' => $jkm->id]), [
'note' => 'Padam wakil selepas tutup.',
])
->assertRedirect();
$this->assertSoftDeleted('jkm_representatives', ['id' => $jkm->id]);
}
public function test_middleware_does_not_block_when_registration_is_open(): void
{
$this->seed(DatabaseSeeder::class);
$admin = User::query()->where('email', 'admin@prn.local')->firstOrFail();
$pusat = PusatMengundi::query()->where('code', 'PM001')->firstOrFail();
$position = Position::query()->where('code', 'PAPM')->firstOrFail();
// Registration is open by default in seed; no note required
$this->actingAs($admin)
->post(route('admin.management.applications.store'), [
'election_id' => $pusat->election_id,
'pusat_mengundi_id' => $pusat->id,
'requested_position_id' => $position->id,
'name' => 'Admin Open',
'ic_number' => '900101105561',
'phone_number' => '0123456789',
'email' => 'admin-open@example.test',
'bank_name' => 'Maybank',
'bank_account_number' => '1234567890',
])
->assertRedirect()
->assertSessionMissing('errors');
$this->assertDatabaseHas('applications', ['ic_number' => '900101105561']);
}
private function closeRegistration(Election $election): void
{
$election->settings()->update([
'registration_start_date' => now()->subWeeks(4)->toDateString(),
'registration_end_date' => now()->subDay()->toDateString(),
'is_registration_open' => true,
'is_registration_open_override' => null,
]);
}
}