96e9d944dd
API CI/CD / Validate (composer + pint) (push) Successful in 2m6s
API CI/CD / Test (PHPUnit) (push) Failing after 2m27s
API CI/CD / Build frontend assets (push) Successful in 2m23s
API CI/CD / Security audit (push) Successful in 32s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been skipped
Mockery's lifecycle (created in setUp, closed in tearDown) may interfere with the container binding across tests. Using a plain anonymous class avoids this issue entirely.
355 lines
10 KiB
PHP
355 lines
10 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Api\V1\AttendanceTracking;
|
|
|
|
use App\Services\AttendanceTracking\AttendanceMailerService;
|
|
use App\Services\AttendanceTracking\AttendanceTrackingService;
|
|
use Mockery;
|
|
use Mockery\MockInterface;
|
|
use Tests\TestCase;
|
|
|
|
class AttendanceTrackingControllerTest extends TestCase
|
|
{
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
// Bind the AttendanceMailerService interface to a no-op implementation
|
|
// so the controller can be instantiated during route middleware gathering
|
|
// even when the full service chain is not mocked.
|
|
$this->app->instance(
|
|
AttendanceMailerService::class,
|
|
new class implements AttendanceMailerService {
|
|
public function send(string $to, string $subject, string $html): bool
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public function queueAttendanceEvent(array $payload): void {}
|
|
}
|
|
);
|
|
}
|
|
|
|
protected function mockService(): MockInterface
|
|
{
|
|
$service = Mockery::mock(AttendanceTrackingService::class);
|
|
$this->app->instance(AttendanceTrackingService::class, $service);
|
|
|
|
return $service;
|
|
}
|
|
|
|
public function test_pending_violations_returns_json_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('getPendingViolations')
|
|
->once()
|
|
->with('2025-2026', 'Fall')
|
|
->andReturn([
|
|
'students' => [
|
|
['id' => 1, 'name' => 'John Doe'],
|
|
],
|
|
'school_year' => '2025-2026',
|
|
'semester' => 'Fall',
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/pending-violations?school_year=2025-2026&semester=Fall');
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'school_year' => '2025-2026',
|
|
'semester' => 'Fall',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_notified_violations_returns_json_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('getNotifiedViolations')
|
|
->once()
|
|
->with('2025-2026', 'Fall')
|
|
->andReturn([
|
|
'students' => [],
|
|
'school_year' => '2025-2026',
|
|
'semester' => 'Fall',
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/notified-violations?school_year=2025-2026&semester=Fall');
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'school_year' => '2025-2026',
|
|
'semester' => 'Fall',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_student_case_returns_service_payload(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('getStudentCase')
|
|
->once()
|
|
->andReturn([
|
|
'success' => true,
|
|
'data' => [
|
|
'student' => ['id' => 10, 'name' => 'John Doe'],
|
|
'attendance' => [],
|
|
],
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/student-case/10');
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'student' => ['id' => 10, 'name' => 'John Doe'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_student_case_returns_service_status_code_when_not_found(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('getStudentCase')
|
|
->once()
|
|
->andReturn([
|
|
'success' => false,
|
|
'message' => 'Student not found',
|
|
'status' => 404,
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/student-case/999');
|
|
|
|
$response->assertStatus(404)
|
|
->assertJson([
|
|
'success' => false,
|
|
'message' => 'Student not found',
|
|
]);
|
|
}
|
|
|
|
public function test_record_validates_and_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$payload = [
|
|
'student_id' => 10,
|
|
'date' => '2026-03-01',
|
|
'parent_email' => 'parent@example.com',
|
|
'parent_name' => 'Jane Doe',
|
|
'subject_type' => 'Absent',
|
|
];
|
|
|
|
$service->shouldReceive('record')
|
|
->once()
|
|
->with($payload)
|
|
->andReturn([
|
|
'success' => true,
|
|
'message' => 'Notification queued.',
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/record', $payload);
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'Notification queued.',
|
|
]);
|
|
}
|
|
|
|
public function test_record_returns_validation_errors_for_bad_payload(): void
|
|
{
|
|
$this->mockService();
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/record', [
|
|
'student_id' => 0,
|
|
'date' => 'bad-date',
|
|
]);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['student_id', 'date']);
|
|
}
|
|
|
|
public function test_send_auto_emails_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('sendAutoEmails')
|
|
->once()
|
|
->with('default')
|
|
->andReturn([
|
|
'success' => true,
|
|
'sent' => 2,
|
|
'skipped' => 1,
|
|
'errors' => 0,
|
|
'details' => [],
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/send-auto-emails', [
|
|
'variant' => 'default',
|
|
]);
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'sent' => 2,
|
|
'skipped' => 1,
|
|
'errors' => 0,
|
|
]);
|
|
}
|
|
|
|
public function test_compose_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('compose')
|
|
->once()
|
|
->with(10, 'ABS_1', 'default', '2026-03-01')
|
|
->andReturn([
|
|
'success' => true,
|
|
'data' => [
|
|
'student_id' => 10,
|
|
'subject' => 'Test Subject',
|
|
],
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/compose?student_id=10&code=ABS_1&variant=default&incident_date=2026-03-01');
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'student_id' => 10,
|
|
'subject' => 'Test Subject',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_send_manual_email_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$payload = [
|
|
'student_id' => 10,
|
|
'to' => 'parent@example.com',
|
|
'subject' => 'Hello',
|
|
'body_html' => 'Body',
|
|
'code' => 'ABS_1',
|
|
'variant' => 'default',
|
|
'incident_date' => '2026-03-01',
|
|
];
|
|
|
|
$service->shouldReceive('sendEmailManual')
|
|
->once()
|
|
->with($payload)
|
|
->andReturn([
|
|
'success' => true,
|
|
'message' => 'Email sent successfully.',
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/send-manual-email', $payload);
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'Email sent successfully.',
|
|
]);
|
|
}
|
|
|
|
public function test_send_manual_email_validates_payload(): void
|
|
{
|
|
$this->mockService();
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/send-manual-email', [
|
|
'student_id' => 0,
|
|
'to' => 'not-an-email',
|
|
]);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['student_id', 'to', 'subject', 'body_html', 'code']);
|
|
}
|
|
|
|
public function test_parents_info_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$service->shouldReceive('parentsInfo')
|
|
->once()
|
|
->with(10)
|
|
->andReturn([
|
|
'success' => true,
|
|
'data' => [
|
|
'primary' => ['email' => 'parent@example.com'],
|
|
'secondary' => ['email' => 'second@example.com'],
|
|
],
|
|
]);
|
|
|
|
$response = $this->getJson('/api/v1/attendance-tracking/parents-info?student_id=10');
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'data' => [
|
|
'primary' => ['email' => 'parent@example.com'],
|
|
'secondary' => ['email' => 'second@example.com'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function test_save_notification_note_returns_service_response(): void
|
|
{
|
|
$service = $this->mockService();
|
|
|
|
$payload = [
|
|
'student_id' => 10,
|
|
'code' => 'ABS_1',
|
|
'note' => 'Called parent',
|
|
'incident_date' => '2026-03-01',
|
|
];
|
|
|
|
$service->shouldReceive('saveNotificationNote')
|
|
->once()
|
|
->with($payload)
|
|
->andReturn([
|
|
'success' => true,
|
|
'message' => 'Note saved.',
|
|
]);
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/save-notification-note', $payload);
|
|
|
|
$response->assertOk()
|
|
->assertJson([
|
|
'success' => true,
|
|
'message' => 'Note saved.',
|
|
]);
|
|
}
|
|
|
|
public function test_save_notification_note_validates_payload(): void
|
|
{
|
|
$this->mockService();
|
|
|
|
$response = $this->postJson('/api/v1/attendance-tracking/save-notification-note', [
|
|
'student_id' => 0,
|
|
'code' => '',
|
|
'note' => '',
|
|
]);
|
|
|
|
$response->assertStatus(422)
|
|
->assertJsonValidationErrors(['student_id', 'code', 'note']);
|
|
}
|
|
}
|