f3f13f5d01
API CI/CD / Validate (composer + pint) (push) Successful in 2m13s
API CI/CD / Test (PHPUnit) (push) Failing after 2m29s
API CI/CD / Build frontend assets (push) Successful in 2m22s
API CI/CD / Deploy to shared hosting (PHP) (push) Has been cancelled
API CI/CD / Security audit (push) Has been cancelled
The previous approach of only mocking AttendanceTrackingService in individual tests was not sufficient. The controller is resolved during route middleware gathering (controllerMiddleware), which happens before the test method body executes. By binding the AttendanceMailerService interface to a mock in setUp, the full dependency chain is satisfied regardless of whether the service-level mock is used.
348 lines
10 KiB
PHP
348 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 mock so the
|
|
// controller can be instantiated during route middleware gathering
|
|
// even when the full service chain is not mocked.
|
|
$this->app->instance(
|
|
AttendanceMailerService::class,
|
|
Mockery::mock(AttendanceMailerService::class)
|
|
);
|
|
}
|
|
|
|
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']);
|
|
}
|
|
}
|