348 lines
13 KiB
PHP
348 lines
13 KiB
PHP
<?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,
|
|
]);
|
|
}
|
|
}
|