seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $assignment = $this->ktmAssignmentFor($ktm); $this->actingAs($ktm) ->get(route('ktm.applications.index')) ->assertOk() ->assertSee('Saluran '.$assignment->saluranMengundi?->number) ->assertSee('Team KP'); } public function test_ktm_can_register_kp_only_and_email_applicant(): void { Mail::fake(); $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $assignment = $this->ktmAssignmentFor($ktm); $kpPosition = Position::query()->where('code', 'KP')->firstOrFail(); $this->actingAs($ktm) ->post(route('ktm.applications.store'), [ 'ktm_assignment_id' => $assignment->id, 'name' => 'KP Didaftar KTM', 'ic_number' => '880101101111', 'phone_number' => '0123456789', 'email' => 'kp-created@example.test', 'address' => 'Alamat KP', 'requested_position_code' => 'KTM', ]) ->assertRedirect(); $application = Application::query()->where('ic_number', '880101101111')->firstOrFail(); $this->assertSame('created_by_ktm', $application->source); $this->assertSame('submitted', $application->status); $this->assertSame($kpPosition->id, $application->requested_position_id); $this->assertSame($assignment->id, $application->selected_ktm_assignment_id); Mail::assertQueued(KtmCreatedApplicantMail::class, fn (KtmCreatedApplicantMail $mail): bool => $mail->application->is($application)); } public function test_ktm_cannot_register_kp_after_registration_period(): void { Mail::fake(); $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $assignment = $this->ktmAssignmentFor($ktm); $assignment->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, ]); $this->actingAs($ktm) ->post(route('ktm.applications.store'), [ 'ktm_assignment_id' => $assignment->id, 'name' => 'KP Lewat', 'ic_number' => '880101101112', 'phone_number' => '0123456789', 'email' => 'kp-late@example.test', ]) ->assertSessionHasErrors('registration'); $this->assertDatabaseMissing('applications', [ 'ic_number' => '880101101112', ]); Mail::assertNothingQueued(); } public function test_ktm_can_approve_kp_application_under_own_team(): void { $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $assignment = $this->ktmAssignmentFor($ktm); $application = $this->kpApplicationFor($assignment, 'KP Untuk Approve', '880101101113'); $this->actingAs($ktm) ->post(route('ktm.applications.approve', $application->public_uuid)) ->assertRedirect(route('ktm.applications.show', $application->public_uuid)); $application->refresh(); $kpPosition = Position::query()->where('code', 'KP')->firstOrFail(); $this->assertSame('assigned', $application->status); $this->assertSame($kpPosition->id, $application->approved_position_id); $this->assertTrue(StaffAssignment::query() ->where('application_id', $application->id) ->where('reports_to_assignment_id', $assignment->id) ->where('position_id', $kpPosition->id) ->where('status', 'active') ->exists()); } public function test_ktm_cannot_approve_non_kp_or_other_team_application(): void { $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $ownAssignment = $this->ktmAssignmentFor($ktm); $otherKtm = User::query()->where('email', 'ppm@prn.local')->firstOrFail(); $otherAssignment = $this->ktmAssignmentFor($otherKtm); $papmPosition = Position::query()->where('code', 'PAPM')->firstOrFail(); $otherTeamApplication = $this->kpApplicationFor($otherAssignment, 'KP Team Lain', '880101101114'); $nonKpApplication = Application::query()->create([ 'election_id' => $ownAssignment->election_id, 'pusat_mengundi_id' => $ownAssignment->pusat_mengundi_id, 'selected_ktm_assignment_id' => $ownAssignment->id, 'public_uuid' => (string) Str::uuid(), 'source' => 'public', 'status' => 'submitted', 'name' => 'Bukan KP', 'ic_number' => '880101101115', 'phone_number' => '0123456789', 'email' => 'not-kp@example.test', 'requested_position_id' => $papmPosition->id, ]); $this->actingAs($ktm) ->post(route('ktm.applications.approve', $otherTeamApplication->public_uuid)) ->assertSessionHasErrors('application'); $this->actingAs($ktm) ->post(route('ktm.applications.approve', $nonKpApplication->public_uuid)) ->assertSessionHasErrors('requested_position_id'); } public function test_ktm_can_delete_own_created_applicant_to_allow_public_registration(): void { Mail::fake(); $this->seed(DatabaseSeeder::class); $ktm = User::query()->where('email', 'ktm@prn.local')->firstOrFail(); $assignment = $this->ktmAssignmentFor($ktm); $application = $this->kpApplicationFor($assignment, 'KP Padam', '880101101116', 'created_by_ktm'); $this->actingAs($ktm) ->delete(route('ktm.applications.destroy', $application->public_uuid)) ->assertRedirect(route('ktm.applications.index')); $this->assertSoftDeleted('applications', [ 'id' => $application->id, 'status' => 'deleted_by_ktm', ]); } private function ktmAssignmentFor(User $user): StaffAssignment { $ktmPosition = Position::query()->where('code', 'KTM')->firstOrFail(); return StaffAssignment::query() ->with(['election.settings', 'pusatMengundi', 'saluranMengundi']) ->where('user_id', $user->id) ->where('position_id', $ktmPosition->id) ->where('status', 'active') ->whereNotNull('saluran_mengundi_id') ->firstOrFail(); } private function kpApplicationFor(StaffAssignment $assignment, string $name, string $icNumber, string $source = 'public'): Application { $kpPosition = Position::query()->where('code', 'KP')->firstOrFail(); return Application::query()->create([ 'election_id' => $assignment->election_id, 'pusat_mengundi_id' => $assignment->pusat_mengundi_id, 'selected_ktm_assignment_id' => $assignment->id, 'public_uuid' => (string) Str::uuid(), 'source' => $source, 'status' => 'submitted', 'name' => $name, 'ic_number' => $icNumber, 'phone_number' => '0123456789', 'email' => Str::slug($name).'@example.test', 'requested_position_id' => $kpPosition->id, ]); } }