add notifications logic and add support of both JWT and Sanctum
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Resources\Notifications;
|
||||
|
||||
use App\Http\Resources\Notifications\NotificationResource;
|
||||
use App\Models\Notification;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationResourceTest extends TestCase
|
||||
{
|
||||
public function test_resource_returns_expected_shape(): void
|
||||
{
|
||||
$notification = new Notification([
|
||||
'id' => 1,
|
||||
'title' => 'Title',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => ['in_app'],
|
||||
'priority' => 'normal',
|
||||
'status' => 'sent',
|
||||
'action_url' => 'https://example.test',
|
||||
'attachment_path' => '/path/file.pdf',
|
||||
'scheduled_at' => now(),
|
||||
'sent_at' => now(),
|
||||
'expires_at' => now()->addDay(),
|
||||
'school_year' => '2025-2026',
|
||||
'semester' => 'Fall',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$payload = (new NotificationResource($notification))->toArray(request());
|
||||
|
||||
$this->assertArrayHasKey('id', $payload);
|
||||
$this->assertArrayHasKey('title', $payload);
|
||||
$this->assertArrayHasKey('message', $payload);
|
||||
$this->assertArrayHasKey('target_group', $payload);
|
||||
$this->assertArrayHasKey('delivery_channels', $payload);
|
||||
$this->assertArrayHasKey('priority', $payload);
|
||||
$this->assertArrayHasKey('status', $payload);
|
||||
$this->assertArrayHasKey('action_url', $payload);
|
||||
$this->assertArrayHasKey('attachment_path', $payload);
|
||||
$this->assertArrayHasKey('scheduled_at', $payload);
|
||||
$this->assertArrayHasKey('sent_at', $payload);
|
||||
$this->assertArrayHasKey('expires_at', $payload);
|
||||
$this->assertArrayHasKey('school_year', $payload);
|
||||
$this->assertArrayHasKey('semester', $payload);
|
||||
$this->assertArrayHasKey('created_at', $payload);
|
||||
$this->assertArrayHasKey('updated_at', $payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Resources\Notifications;
|
||||
|
||||
use App\Http\Resources\Notifications\UserNotificationResource;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserNotificationResourceTest extends TestCase
|
||||
{
|
||||
public function test_resource_returns_expected_shape(): void
|
||||
{
|
||||
$resource = new UserNotificationResource([
|
||||
'notification_id' => 10,
|
||||
'title' => 'Title',
|
||||
'message' => 'Message',
|
||||
'priority' => 'high',
|
||||
'scheduled_at' => '2025-01-01 00:00:00',
|
||||
'expires_at' => '2025-01-02 00:00:00',
|
||||
'target_group' => 'parent',
|
||||
'is_read' => 1,
|
||||
]);
|
||||
|
||||
$payload = $resource->toArray(request());
|
||||
|
||||
$this->assertArrayHasKey('notification_id', $payload);
|
||||
$this->assertArrayHasKey('title', $payload);
|
||||
$this->assertArrayHasKey('message', $payload);
|
||||
$this->assertArrayHasKey('priority', $payload);
|
||||
$this->assertArrayHasKey('scheduled_at', $payload);
|
||||
$this->assertArrayHasKey('expires_at', $payload);
|
||||
$this->assertArrayHasKey('target_group', $payload);
|
||||
$this->assertArrayHasKey('is_read', $payload);
|
||||
$this->assertTrue($payload['is_read']);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class AdministratorAbsenceServiceTest extends TestCase
|
||||
new \App\Models\User(),
|
||||
new \App\Models\StaffAttendance(),
|
||||
new \App\Services\SemesterRangeService(new \App\Services\Semesters\SemesterConfigService()),
|
||||
Mockery::mock(\App\Libraries\StaffTimeOffLinkService::class)
|
||||
Mockery::mock(\App\Services\Staff\StaffTimeOffLinkService::class)
|
||||
);
|
||||
|
||||
$request = Request::create('/absences', 'POST', ['dates' => ['2025-01-05']]);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\AttendanceAutoPublishJobService;
|
||||
use App\Services\Attendance\AttendanceAutoPublishService;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceAutoPublishJobServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_run_publishes_due_records(): void
|
||||
{
|
||||
$autoPublish = new AttendanceAutoPublishService();
|
||||
$service = new AttendanceAutoPublishJobService($autoPublish);
|
||||
|
||||
$now = new DateTimeImmutable('2025-03-01 10:00:00', new DateTimeZone('UTC'));
|
||||
$cutoff = $autoPublish->secondSundayBackwardDate($now);
|
||||
|
||||
DB::table('attendance_day')->insert([
|
||||
[
|
||||
'class_section_id' => 1,
|
||||
'date' => '2025-03-01',
|
||||
'status' => 'submitted',
|
||||
'auto_publish_at' => '2025-03-01 09:00:00',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
[
|
||||
'class_section_id' => 2,
|
||||
'date' => $cutoff,
|
||||
'status' => 'submitted',
|
||||
'auto_publish_at' => null,
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
[
|
||||
'class_section_id' => 3,
|
||||
'date' => '2025-02-20',
|
||||
'status' => 'draft',
|
||||
'auto_publish_at' => null,
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
]);
|
||||
|
||||
$result = $service->run($now);
|
||||
|
||||
$this->assertSame(2, $result['published']);
|
||||
$this->assertDatabaseHas('attendance_day', [
|
||||
'class_section_id' => 1,
|
||||
'status' => 'published',
|
||||
]);
|
||||
$this->assertDatabaseHas('attendance_day', [
|
||||
'class_section_id' => 2,
|
||||
'status' => 'published',
|
||||
]);
|
||||
$this->assertDatabaseHas('attendance_day', [
|
||||
'class_section_id' => 3,
|
||||
'status' => 'draft',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\AttendanceAutoPublishService;
|
||||
use DateTimeImmutable;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceAutoPublishServiceTest extends TestCase
|
||||
{
|
||||
public function test_second_sunday_after_end_of_day(): void
|
||||
{
|
||||
$service = new AttendanceAutoPublishService();
|
||||
|
||||
$result = $service->secondSundayAfterEndOfDay('2025-02-03', 'UTC');
|
||||
|
||||
$this->assertSame('2025-02-16 23:59:59', $result);
|
||||
}
|
||||
|
||||
public function test_second_sunday_backward_date(): void
|
||||
{
|
||||
$service = new AttendanceAutoPublishService();
|
||||
$now = new DateTimeImmutable('2025-02-16 10:00:00', new \DateTimeZone('UTC'));
|
||||
|
||||
$result = $service->secondSundayBackwardDate($now, 'UTC');
|
||||
|
||||
$this->assertSame('2025-02-02', $result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Models\AttendanceTracking;
|
||||
use App\Services\Attendance\AttendanceConsequenceService;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceConsequenceServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_follow_up_requires_parent_email(): void
|
||||
{
|
||||
$service = new AttendanceConsequenceService(
|
||||
Mockery::mock(EmailService::class),
|
||||
Mockery::mock(UserNotificationDispatchService::class)
|
||||
);
|
||||
|
||||
$result = $service->sendFollowUp([
|
||||
'parent' => ['user_id' => 5],
|
||||
]);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
|
||||
public function test_send_follow_up_marks_tracking_and_notifies(): void
|
||||
{
|
||||
\Illuminate\Support\Facades\DB::table('students')->insert([
|
||||
'id' => 1,
|
||||
'school_id' => 1,
|
||||
'parent_id' => 1,
|
||||
'firstname' => 'Student',
|
||||
'lastname' => 'One',
|
||||
'dob' => '2015-01-01',
|
||||
'age' => 10,
|
||||
'gender' => 'Male',
|
||||
'is_active' => 1,
|
||||
'photo_consent' => 1,
|
||||
'year_of_registration' => '2025',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
AttendanceTracking::query()->create([
|
||||
'student_id' => 1,
|
||||
'date' => now(),
|
||||
'is_reported' => 1,
|
||||
'reason' => 'ABS_2',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new AttendanceConsequenceService($email, $notifier);
|
||||
|
||||
$result = $service->sendFollowUp([
|
||||
'student_id' => 1,
|
||||
'parent' => ['email' => 'parent@example.com', 'user_id' => 5],
|
||||
'student' => ['firstname' => 'A', 'lastname' => 'B'],
|
||||
'tracking_id' => 1,
|
||||
'html' => '<p>Test</p>',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('attendance_tracking', [
|
||||
'id' => 1,
|
||||
'is_notified' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\AttendanceDailySummaryService;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceDailySummaryServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_absentees_summary_notifies_parent(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2025-02-10 08:00:00'));
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 5,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'One',
|
||||
'email' => 'parent@example.com',
|
||||
'status' => 'Active',
|
||||
]);
|
||||
|
||||
DB::table('students')->insert([
|
||||
'id' => 10,
|
||||
'school_id' => 1,
|
||||
'parent_id' => 5,
|
||||
'firstname' => 'Student',
|
||||
'lastname' => 'One',
|
||||
'dob' => '2015-01-01',
|
||||
'age' => 10,
|
||||
'gender' => 'Male',
|
||||
'is_active' => 1,
|
||||
'photo_consent' => 1,
|
||||
'year_of_registration' => '2025',
|
||||
'school_year' => '2024-2025',
|
||||
]);
|
||||
|
||||
DB::table('attendance_data')->insert([
|
||||
'class_id' => 1,
|
||||
'class_section_id' => 1,
|
||||
'student_id' => 10,
|
||||
'school_id' => 'S1',
|
||||
'date' => '2025-02-10',
|
||||
'status' => 'absent',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
]);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$service = new AttendanceDailySummaryService($notifier, $email);
|
||||
|
||||
$count = $service->sendAbsenteesSummary();
|
||||
|
||||
$this->assertSame(1, $count);
|
||||
}
|
||||
}
|
||||
@@ -39,4 +39,12 @@ class AttendancePolicyServiceTest extends TestCase
|
||||
|
||||
$this->assertTrue($isAdmin);
|
||||
}
|
||||
|
||||
public function test_can_manage_early_dismissals_for_teacher(): void
|
||||
{
|
||||
$service = new AttendancePolicyService();
|
||||
$allowed = $service->canManageEarlyDismissals(['Teacher']);
|
||||
|
||||
$this->assertTrue($allowed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,8 @@ class AttendanceQueryServiceTest extends TestCase
|
||||
new \App\Models\AttendanceData(),
|
||||
new \App\Models\Student(),
|
||||
new \App\Services\Attendance\AttendanceRecordSyncService(new \App\Models\AttendanceRecord())
|
||||
)
|
||||
),
|
||||
new \App\Services\Attendance\AttendanceAutoPublishService()
|
||||
),
|
||||
new \App\Services\Attendance\AttendanceRecordSyncService(new \App\Models\AttendanceRecord())
|
||||
),
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Attendance;
|
||||
|
||||
use App\Services\Attendance\AttendanceSummaryRebuildService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AttendanceSummaryRebuildServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_rebuild_inserts_summary_rows(): void
|
||||
{
|
||||
DB::table('attendance_data')->insert([
|
||||
[
|
||||
'class_id' => 1,
|
||||
'class_section_id' => 10,
|
||||
'student_id' => 1,
|
||||
'school_id' => 'S1',
|
||||
'date' => '2025-02-01',
|
||||
'status' => 'present',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
[
|
||||
'class_id' => 1,
|
||||
'class_section_id' => 10,
|
||||
'student_id' => 1,
|
||||
'school_id' => 'S1',
|
||||
'date' => '2025-02-02',
|
||||
'status' => 'absent',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
[
|
||||
'class_id' => 1,
|
||||
'class_section_id' => 11,
|
||||
'student_id' => 2,
|
||||
'school_id' => 'S1',
|
||||
'date' => '2025-02-02',
|
||||
'status' => 'late',
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new AttendanceSummaryRebuildService();
|
||||
$result = $service->rebuild();
|
||||
|
||||
$this->assertSame(2, $result['inserted']);
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'student_id' => 1,
|
||||
'total_presence' => 1,
|
||||
'total_absence' => 1,
|
||||
'total_late' => 0,
|
||||
'total_attendance' => 2,
|
||||
]);
|
||||
$this->assertDatabaseHas('attendance_record', [
|
||||
'student_id' => 2,
|
||||
'total_presence' => 0,
|
||||
'total_absence' => 0,
|
||||
'total_late' => 1,
|
||||
'total_attendance' => 1,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,8 @@ class TeacherAttendanceSubmissionServiceTest extends TestCase
|
||||
new \App\Models\AttendanceData(),
|
||||
new \App\Models\Student(),
|
||||
new \App\Services\Attendance\AttendanceRecordSyncService(new \App\Models\AttendanceRecord())
|
||||
)
|
||||
),
|
||||
new \App\Services\Attendance\AttendanceAutoPublishService()
|
||||
);
|
||||
|
||||
$this->expectException(\RuntimeException::class);
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Auth;
|
||||
|
||||
use App\Services\Auth\PasswordResetCleanupService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PasswordResetCleanupServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_cleanup_removes_old_requests(): void
|
||||
{
|
||||
DB::table('password_reset_requests')->insert([
|
||||
['ip_address' => '127.0.0.1', 'requested_at' => now()->subDays(40)],
|
||||
['ip_address' => '127.0.0.1', 'requested_at' => now()->subDays(5)],
|
||||
]);
|
||||
|
||||
$service = new PasswordResetCleanupService();
|
||||
$deleted = $service->cleanup(30);
|
||||
|
||||
$this->assertSame(1, $deleted);
|
||||
$this->assertDatabaseCount('password_reset_requests', 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Grading;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Grading\BelowSixtyEmailService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class BelowSixtyEmailServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_requires_student_id(): void
|
||||
{
|
||||
$service = new BelowSixtyEmailService(Mockery::mock(EmailService::class));
|
||||
$result = $service->send([]);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
|
||||
public function test_send_delivers_to_guardians(): void
|
||||
{
|
||||
DB::table('families')->insert([
|
||||
'id' => 1,
|
||||
'family_code' => 'FAM-1',
|
||||
'household_name' => 'Family',
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 10,
|
||||
'school_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '5551112222',
|
||||
'email' => 'parent@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'accept_school_policy' => 1,
|
||||
'is_verified' => 1,
|
||||
'status' => 'Active',
|
||||
'is_suspended' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'password' => bcrypt('secret'),
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
DB::table('family_guardians')->insert([
|
||||
'family_id' => 1,
|
||||
'user_id' => 10,
|
||||
'relation' => 'primary',
|
||||
'is_primary' => 1,
|
||||
'receive_emails' => 1,
|
||||
'receive_sms' => 0,
|
||||
]);
|
||||
|
||||
DB::table('students')->insert([
|
||||
'id' => 22,
|
||||
'school_id' => 1,
|
||||
'parent_id' => 10,
|
||||
'firstname' => 'Student',
|
||||
'lastname' => 'One',
|
||||
'dob' => '2015-01-01',
|
||||
'age' => 10,
|
||||
'gender' => 'Male',
|
||||
'is_active' => 1,
|
||||
'photo_consent' => 1,
|
||||
'year_of_registration' => '2025',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
DB::table('family_students')->insert([
|
||||
'family_id' => 1,
|
||||
'student_id' => 22,
|
||||
'is_primary_home' => 1,
|
||||
]);
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$service = new BelowSixtyEmailService($email);
|
||||
$result = $service->send([
|
||||
'student_id' => 22,
|
||||
'student_name' => 'Student',
|
||||
'class_section_name' => 'Class',
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
'scores' => ['semester_score' => 55],
|
||||
'comment' => 'Needs improvement',
|
||||
'html' => '<p>Report</p>',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(1, $result['sent']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationActiveService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationActiveServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_list_filters_by_target_group(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
[
|
||||
'id' => 1,
|
||||
'title' => 'Parent',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'scheduled_at' => now()->subHour(),
|
||||
'expires_at' => now()->addHour(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'title' => 'Teacher',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'teacher',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'scheduled_at' => now()->subHour(),
|
||||
'expires_at' => now()->addHour(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new NotificationActiveService();
|
||||
$result = $service->list('parent');
|
||||
|
||||
$this->assertCount(1, $result['notifications']);
|
||||
$this->assertFalse($result['notifications'][0]['isExpired']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationCleanupService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationCleanupServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_cleanup_expired_soft_deletes_rows(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
[
|
||||
'id' => 1,
|
||||
'title' => 'Old',
|
||||
'message' => 'Old',
|
||||
'target_group' => 'all',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'priority' => 1,
|
||||
'status' => 'sent',
|
||||
'scheduled_at' => now()->subDays(2),
|
||||
'expires_at' => now()->subDay(),
|
||||
'school_year' => '2024-2025',
|
||||
'semester' => 'Spring',
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'title' => 'Active',
|
||||
'message' => 'Active',
|
||||
'target_group' => 'all',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'priority' => 1,
|
||||
'status' => 'sent',
|
||||
'scheduled_at' => now()->subDay(),
|
||||
'expires_at' => now()->addDay(),
|
||||
'school_year' => '2024-2025',
|
||||
'semester' => 'Spring',
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new NotificationCleanupService();
|
||||
$deleted = $service->cleanupExpired();
|
||||
|
||||
$this->assertSame(1, $deleted);
|
||||
$this->assertSoftDeleted('notifications', ['id' => 1]);
|
||||
$this->assertDatabaseHas('notifications', ['id' => 2]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationDeletedService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationDeletedServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_list_returns_deleted_notifications(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
'id' => 1,
|
||||
'title' => 'Deleted',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'deleted_at' => now(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$service = new NotificationDeletedService();
|
||||
$result = $service->list();
|
||||
|
||||
$this->assertCount(1, $result['notifications']);
|
||||
$this->assertSame(1, $result['notifications'][0]['id']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationDispatchService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationDispatchServiceTest extends TestCase
|
||||
{
|
||||
public function test_dispatch_logs_email_and_sms(): void
|
||||
{
|
||||
Log::spy();
|
||||
|
||||
$service = new NotificationDispatchService();
|
||||
$service->sendEmail('parent@example.com', 'Subject', 'Message');
|
||||
$service->sendSms('5551112222', 'Message');
|
||||
|
||||
Log::shouldHaveReceived('info')->twice();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationManagementService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationManagementServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_update_modifies_notification(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
'id' => 1,
|
||||
'title' => 'Old',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$service = new NotificationManagementService();
|
||||
$result = $service->update(1, ['title' => 'New']);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'id' => 1,
|
||||
'title' => 'New',
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_update_returns_false_when_missing(): void
|
||||
{
|
||||
$service = new NotificationManagementService();
|
||||
$result = $service->update(999, ['title' => 'New']);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
|
||||
public function test_delete_and_restore_notification(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
'id' => 1,
|
||||
'title' => 'Old',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$service = new NotificationManagementService();
|
||||
$this->assertTrue($service->delete(1));
|
||||
$this->assertSoftDeleted('notifications', ['id' => 1]);
|
||||
|
||||
$this->assertTrue($service->restore(1));
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'id' => 1,
|
||||
'deleted_at' => null,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationReadService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationReadServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_mark_read_updates_row(): void
|
||||
{
|
||||
DB::table('user_notifications')->insert([
|
||||
'notification_id' => 1,
|
||||
'user_id' => 1,
|
||||
'is_read' => 0,
|
||||
]);
|
||||
|
||||
$service = new NotificationReadService();
|
||||
$result = $service->markRead(1, 1);
|
||||
|
||||
$this->assertTrue($result);
|
||||
$this->assertDatabaseHas('user_notifications', [
|
||||
'notification_id' => 1,
|
||||
'user_id' => 1,
|
||||
'is_read' => 1,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_mark_read_returns_false_when_missing(): void
|
||||
{
|
||||
$service = new NotificationReadService();
|
||||
|
||||
$this->assertFalse($service->markRead(1, 99));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationRecipientService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationRecipientServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_get_recipients_by_role(): void
|
||||
{
|
||||
DB::table('roles')->insert([
|
||||
'id' => 1,
|
||||
'name' => 'parent',
|
||||
'slug' => 'parent',
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 10,
|
||||
'school_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '5551112222',
|
||||
'email' => 'parent@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'accept_school_policy' => 1,
|
||||
'is_verified' => 1,
|
||||
'status' => 'Active',
|
||||
'is_suspended' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'password' => bcrypt('secret'),
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
DB::table('user_roles')->insert([
|
||||
'user_id' => 10,
|
||||
'role_id' => 1,
|
||||
]);
|
||||
|
||||
$service = new NotificationRecipientService();
|
||||
$rows = $service->getRecipients('parent');
|
||||
|
||||
$this->assertCount(1, $rows);
|
||||
$this->assertSame('parent@example.com', $rows[0]['email']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Models\Notification;
|
||||
use App\Models\UserNotification;
|
||||
use App\Services\Notifications\NotificationDispatchService;
|
||||
use App\Services\Notifications\NotificationRecipientService;
|
||||
use App\Services\Notifications\NotificationSendService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationSendServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_creates_notification_and_user_notifications(): void
|
||||
{
|
||||
$recipients = Mockery::mock(NotificationRecipientService::class);
|
||||
$recipients->shouldReceive('getRecipients')
|
||||
->with('parent')
|
||||
->andReturn([
|
||||
['id' => 10, 'email' => 'parent1@example.com', 'phone' => '5551112222'],
|
||||
['id' => 11, 'email' => 'parent2@example.com', 'phone' => null],
|
||||
]);
|
||||
|
||||
$dispatcher = Mockery::mock(NotificationDispatchService::class);
|
||||
$dispatcher->shouldReceive('sendEmail')->once();
|
||||
$dispatcher->shouldReceive('sendSms')->once();
|
||||
|
||||
$service = new NotificationSendService($recipients, $dispatcher);
|
||||
|
||||
$result = $service->send([
|
||||
'title' => 'Announcement',
|
||||
'message' => 'Hello parents',
|
||||
'target_group' => 'parent',
|
||||
'channels' => ['in_app', 'email', 'sms'],
|
||||
], 99);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(2, $result['recipient_count']);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'title' => 'Announcement',
|
||||
'target_group' => 'parent',
|
||||
]);
|
||||
$this->assertSame(1, Notification::query()->count());
|
||||
$this->assertSame(2, UserNotification::query()->count());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationShowService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationShowServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_get_for_user_returns_notification(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
'id' => 1,
|
||||
'title' => 'Hello',
|
||||
'message' => 'Message',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'scheduled_at' => now()->subHour(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
DB::table('user_notifications')->insert([
|
||||
'notification_id' => 1,
|
||||
'user_id' => 1,
|
||||
'is_read' => 0,
|
||||
]);
|
||||
|
||||
$service = new NotificationShowService();
|
||||
$row = $service->getForUser(1, 1);
|
||||
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame(1, $row['notification_id']);
|
||||
}
|
||||
|
||||
public function test_get_for_user_returns_null_when_missing(): void
|
||||
{
|
||||
$service = new NotificationShowService();
|
||||
$row = $service->getForUser(1, 99);
|
||||
|
||||
$this->assertNull($row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationTriggerService;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationTriggerServiceTest extends TestCase
|
||||
{
|
||||
public function test_trigger_dispatches_event(): void
|
||||
{
|
||||
Event::fake();
|
||||
|
||||
NotificationTriggerService::trigger('customNotification', ['foo' => 'bar']);
|
||||
|
||||
Event::assertDispatched('customNotification');
|
||||
}
|
||||
|
||||
public function test_to_user_dispatches_event(): void
|
||||
{
|
||||
Event::fake();
|
||||
|
||||
NotificationTriggerService::toUser(5, 'Hello', 'Message');
|
||||
|
||||
Event::assertDispatched('customNotification');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\NotificationUserListService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class NotificationUserListServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_list_filters_by_read_status(): void
|
||||
{
|
||||
DB::table('notifications')->insert([
|
||||
[
|
||||
'id' => 1,
|
||||
'title' => 'Unread',
|
||||
'message' => 'Message 1',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'scheduled_at' => now()->subHour(),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'title' => 'Read',
|
||||
'message' => 'Message 2',
|
||||
'target_group' => 'parent',
|
||||
'delivery_channels' => json_encode(['in_app']),
|
||||
'scheduled_at' => now()->subHours(2),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
],
|
||||
]);
|
||||
|
||||
DB::table('user_notifications')->insert([
|
||||
[
|
||||
'notification_id' => 1,
|
||||
'user_id' => 1,
|
||||
'is_read' => 0,
|
||||
],
|
||||
[
|
||||
'notification_id' => 2,
|
||||
'user_id' => 1,
|
||||
'is_read' => 1,
|
||||
],
|
||||
]);
|
||||
|
||||
$service = new NotificationUserListService();
|
||||
$result = $service->listForUser(1, [
|
||||
'read' => true,
|
||||
'per_page' => 10,
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['pagination']['total']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Notifications;
|
||||
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserNotificationDispatchServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_notify_user_creates_notification(): void
|
||||
{
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'school_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '5551112222',
|
||||
'email' => 'parent@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'accept_school_policy' => 1,
|
||||
'is_verified' => 1,
|
||||
'status' => 'Active',
|
||||
'is_suspended' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'password' => bcrypt('secret'),
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
$service = new UserNotificationDispatchService();
|
||||
$result = $service->notifyUser(1, 'Title', 'Message', ['in_app'], 'registration', 'parent');
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('notifications', [
|
||||
'title' => 'Title',
|
||||
'target_group' => 'parent',
|
||||
]);
|
||||
$this->assertDatabaseHas('user_notifications', [
|
||||
'user_id' => 1,
|
||||
'is_read' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_notify_user_rejects_invalid_user(): void
|
||||
{
|
||||
$service = new UserNotificationDispatchService();
|
||||
$result = $service->notifyUser(0, 'Title', 'Message');
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Payments;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use App\Services\Payments\PaymentMissedCheckService;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PaymentMissedCheckServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_find_users_with_missed_payments(): void
|
||||
{
|
||||
Carbon::setTestNow(Carbon::parse('2025-03-01'));
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'Late',
|
||||
'email' => 'late@example.com',
|
||||
'status' => 'Active',
|
||||
]);
|
||||
|
||||
DB::table('invoices')->insert([
|
||||
'id' => 1,
|
||||
'parent_id' => 1,
|
||||
'invoice_number' => 'INV-1',
|
||||
'total_amount' => 100,
|
||||
'balance' => 50,
|
||||
'paid_amount' => 50,
|
||||
'status' => 'Unpaid',
|
||||
'school_year' => '2024-2025',
|
||||
'semester' => 'Spring',
|
||||
'issue_date' => '2025-01-01',
|
||||
'due_date' => '2025-02-01',
|
||||
]);
|
||||
|
||||
$service = new PaymentMissedCheckService(
|
||||
Mockery::mock(UserNotificationDispatchService::class),
|
||||
Mockery::mock(EmailService::class)
|
||||
);
|
||||
|
||||
$users = $service->findUsersWithMissedPayments();
|
||||
|
||||
$this->assertCount(1, $users);
|
||||
$this->assertSame(1, (int) $users[0]['user_id']);
|
||||
}
|
||||
|
||||
public function test_send_reminders_sends_email_and_notification(): void
|
||||
{
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$service = new PaymentMissedCheckService($notifier, $email);
|
||||
|
||||
$result = $service->sendReminders([
|
||||
[
|
||||
'user_id' => 5,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'email' => 'parent@example.com',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertSame(1, $result['sent']);
|
||||
$this->assertSame(0, $result['failed']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Payments;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Payments\PaymentTestNotificationService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PaymentTestNotificationServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_logs_and_sends_email(): void
|
||||
{
|
||||
DB::table('configuration')->insert([
|
||||
['config_key' => 'school_year', 'config_value' => '2024-2025'],
|
||||
['config_key' => 'installment_date', 'config_value' => '2025-06-01'],
|
||||
]);
|
||||
|
||||
DB::table('users')->insert([
|
||||
['id' => 10, 'firstname' => 'Primary', 'lastname' => 'Parent', 'email' => 'primary@example.com', 'status' => 'Active'],
|
||||
['id' => 11, 'firstname' => 'Secondary', 'lastname' => 'Parent', 'email' => 'secondary@example.com', 'status' => 'Active'],
|
||||
]);
|
||||
|
||||
DB::table('families')->insert([
|
||||
'id' => 1,
|
||||
'family_code' => 'FAM-1',
|
||||
'is_active' => 1,
|
||||
]);
|
||||
|
||||
DB::table('family_guardians')->insert([
|
||||
[
|
||||
'family_id' => 1,
|
||||
'user_id' => 10,
|
||||
'relation' => 'guardian',
|
||||
'is_primary' => 1,
|
||||
'receive_emails' => 1,
|
||||
'receive_sms' => 0,
|
||||
],
|
||||
[
|
||||
'family_id' => 1,
|
||||
'user_id' => 11,
|
||||
'relation' => 'guardian',
|
||||
'is_primary' => 0,
|
||||
'receive_emails' => 1,
|
||||
'receive_sms' => 0,
|
||||
],
|
||||
]);
|
||||
|
||||
DB::table('invoices')->insert([
|
||||
'id' => 1,
|
||||
'parent_id' => 10,
|
||||
'invoice_number' => 'INV-1',
|
||||
'total_amount' => 200.00,
|
||||
'balance' => 150.00,
|
||||
'paid_amount' => 50.00,
|
||||
'status' => 'Partially Paid',
|
||||
'school_year' => '2024-2025',
|
||||
'semester' => 'Spring',
|
||||
'issue_date' => '2025-01-01',
|
||||
]);
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->twice()->andReturn(true);
|
||||
|
||||
$service = new PaymentTestNotificationService($email);
|
||||
$result = $service->send('primary@example.com', 'no_payment');
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('payment_notification_logs', [
|
||||
'parent_id' => 10,
|
||||
'type' => 'no_payment',
|
||||
'to_email' => 'primary@example.com',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Payments;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Payments\PaypalPaymentSyncService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PaypalPaymentSyncServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_sync_applies_payment_and_marks_synced(): void
|
||||
{
|
||||
DB::table('configuration')->insert([
|
||||
['config_key' => 'school_year', 'config_value' => '2024-2025'],
|
||||
['config_key' => 'semester', 'config_value' => 'Spring'],
|
||||
]);
|
||||
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'school_id' => 123,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'email' => 'parent@example.com',
|
||||
'status' => 'Active',
|
||||
]);
|
||||
|
||||
DB::table('invoices')->insert([
|
||||
'id' => 1,
|
||||
'parent_id' => 1,
|
||||
'invoice_number' => 'INV-1',
|
||||
'total_amount' => 100,
|
||||
'balance' => 100,
|
||||
'paid_amount' => 0,
|
||||
'status' => 'Unpaid',
|
||||
'school_year' => '2024-2025',
|
||||
'semester' => 'Spring',
|
||||
'issue_date' => '2025-01-01',
|
||||
]);
|
||||
|
||||
DB::table('paypal_payments')->insert([
|
||||
'id' => 1,
|
||||
'parent_school_id' => 123,
|
||||
'transaction_id' => 'TXN-1',
|
||||
'status' => 'COMPLETED',
|
||||
'amount' => 50,
|
||||
'synced' => 0,
|
||||
'sync_attempts' => 0,
|
||||
'created_at' => '2025-01-15 10:00:00',
|
||||
]);
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$service = new PaypalPaymentSyncService($email);
|
||||
$result = $service->sync(false, false);
|
||||
|
||||
$this->assertSame(1, $result['processed']);
|
||||
$this->assertDatabaseHas('paypal_payments', [
|
||||
'id' => 1,
|
||||
'synced' => 1,
|
||||
]);
|
||||
$this->assertDatabaseHas('payments', [
|
||||
'invoice_id' => 1,
|
||||
'parent_id' => 1,
|
||||
'paid_amount' => 50,
|
||||
]);
|
||||
$this->assertDatabaseHas('invoices', [
|
||||
'id' => 1,
|
||||
'balance' => 50,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\School;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use App\Services\School\AccountEventService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class AccountEventServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_new_account_added_creates_family_links(): void
|
||||
{
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'school_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'cellphone' => '5551112222',
|
||||
'email' => 'parent@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'accept_school_policy' => 1,
|
||||
'is_verified' => 1,
|
||||
'status' => 'Active',
|
||||
'is_suspended' => 0,
|
||||
'failed_attempts' => 0,
|
||||
'password' => bcrypt('secret'),
|
||||
'semester' => 'Fall',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
DB::table('students')->insert([
|
||||
'id' => 5,
|
||||
'school_id' => 1,
|
||||
'parent_id' => 1,
|
||||
'firstname' => 'Student',
|
||||
'lastname' => 'One',
|
||||
'dob' => '2015-01-01',
|
||||
'age' => 10,
|
||||
'gender' => 'Male',
|
||||
'is_active' => 1,
|
||||
'photo_consent' => 1,
|
||||
'year_of_registration' => '2025',
|
||||
'school_year' => '2025-2026',
|
||||
]);
|
||||
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->twice()->andReturn(true);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
|
||||
$service = new AccountEventService($email, $notifier);
|
||||
$result = $service->newAccountAdded([
|
||||
'id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'email' => 'parent@example.com',
|
||||
'address_street' => '123 Main',
|
||||
'city' => 'City',
|
||||
'state' => 'ST',
|
||||
'zip' => '12345',
|
||||
'cellphone' => '5551112222',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('families', [
|
||||
'family_code' => 'FAM-1',
|
||||
]);
|
||||
$this->assertDatabaseHas('family_guardians', [
|
||||
'user_id' => 1,
|
||||
'is_primary' => 1,
|
||||
]);
|
||||
$this->assertDatabaseHas('family_students', [
|
||||
'student_id' => 5,
|
||||
]);
|
||||
}
|
||||
|
||||
public function test_delete_unverified_user_handles_missing_admin_emails(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->andReturn(true);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new AccountEventService($email, $notifier);
|
||||
$result = $service->deleteUnverifiedUser([
|
||||
'id' => 2,
|
||||
'email' => 'inactive@example.com',
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\School;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use App\Services\School\EnrollmentEventService;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class EnrollmentEventServiceTest extends TestCase
|
||||
{
|
||||
public function test_admission_under_review_sends_email(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new EnrollmentEventService($email, $notifier);
|
||||
$result = $service->admissionUnderReview([
|
||||
'user_id' => 1,
|
||||
'email' => 'parent@example.com',
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
], [
|
||||
['firstname' => 'Student', 'lastname' => 'One'],
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
public function test_admission_under_review_requires_email(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new EnrollmentEventService($email, $notifier);
|
||||
$result = $service->admissionUnderReview([
|
||||
'user_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
], []);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\School;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Notifications\UserNotificationDispatchService;
|
||||
use App\Services\School\PaymentEventService;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class PaymentEventServiceTest extends TestCase
|
||||
{
|
||||
public function test_payment_received_sends_email(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new PaymentEventService($email, $notifier);
|
||||
$result = $service->paymentReceived([
|
||||
'user_id' => 1,
|
||||
'email' => 'parent@example.com',
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'amount' => 25,
|
||||
'invoice_id' => 10,
|
||||
'payment_date' => now()->toDateTimeString(),
|
||||
'installment_seq' => 1,
|
||||
'pre_balance' => 50,
|
||||
'post_balance' => 25,
|
||||
'invoice_total' => 100,
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
public function test_extra_charge_requires_email(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$notifier = Mockery::mock(UserNotificationDispatchService::class);
|
||||
$notifier->shouldReceive('notifyUser')->once();
|
||||
|
||||
$service = new PaymentEventService($email, $notifier);
|
||||
$result = $service->extraCharge([
|
||||
'user_id' => 1,
|
||||
'firstname' => 'Parent',
|
||||
'lastname' => 'User',
|
||||
'amount_signed' => 10,
|
||||
'amount_abs' => 10,
|
||||
'pre_balance' => 0,
|
||||
'post_balance' => 10,
|
||||
'invoice_total' => 100,
|
||||
'charge_title' => 'Fee',
|
||||
'charge_type' => 'add',
|
||||
]);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Staff;
|
||||
|
||||
use App\Services\Staff\StaffTimeOffLinkService;
|
||||
use Tests\TestCase;
|
||||
|
||||
class StaffTimeOffLinkServiceTest extends TestCase
|
||||
{
|
||||
public function test_token_round_trip(): void
|
||||
{
|
||||
$service = new StaffTimeOffLinkService('secret', 3600);
|
||||
|
||||
$token = $service->createToken(['uid' => 10, 'email' => 'staff@example.com']);
|
||||
$payload = $service->parseToken($token);
|
||||
|
||||
$this->assertNotNull($payload);
|
||||
$this->assertSame(10, $payload['uid']);
|
||||
$this->assertSame('staff@example.com', $payload['email']);
|
||||
}
|
||||
|
||||
public function test_token_invalid_on_expiry(): void
|
||||
{
|
||||
$service = new StaffTimeOffLinkService('secret', 1);
|
||||
|
||||
$token = $service->createToken(['uid' => 10]);
|
||||
sleep(2);
|
||||
$payload = $service->parseToken($token);
|
||||
|
||||
$this->assertNull($payload);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\System;
|
||||
|
||||
use App\Services\System\ConfigUpdateService;
|
||||
use DateTimeZone;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ConfigUpdateServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_run_task_rejects_unknown_task(): void
|
||||
{
|
||||
$service = new ConfigUpdateService();
|
||||
$result = $service->runTask('unknown', false, false, new DateTimeZone('UTC'));
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
|
||||
public function test_run_task_sets_config_when_forced(): void
|
||||
{
|
||||
$service = new ConfigUpdateService();
|
||||
$result = $service->runTask('enable_attendance_on', false, true, new DateTimeZone('UTC'));
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertDatabaseHas('configuration', [
|
||||
'config_key' => 'enable_attendance',
|
||||
'config_value' => '1',
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace Tests\Unit\Services\Teachers;
|
||||
|
||||
use App\Libraries\StaffTimeOffLinkService;
|
||||
use App\Services\Staff\StaffTimeOffLinkService;
|
||||
use App\Services\SemesterRangeService;
|
||||
use App\Services\Semesters\SemesterConfigService;
|
||||
use App\Services\Teachers\TeacherAbsenceService;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Users;
|
||||
|
||||
use App\Services\Users\InactiveUserCleanupService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
||||
class InactiveUserCleanupServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_cleanup_removes_inactive_users_and_roles(): void
|
||||
{
|
||||
DB::table('users')->insert([
|
||||
'id' => 1,
|
||||
'firstname' => 'Inactive',
|
||||
'lastname' => 'User',
|
||||
'email' => 'inactive@example.com',
|
||||
'status' => 'Inactive',
|
||||
'created_at' => now()->subMinutes(30),
|
||||
]);
|
||||
|
||||
DB::table('parents')->insert([
|
||||
'id' => 1,
|
||||
'secondparent_firstname' => 'Second',
|
||||
'secondparent_lastname' => 'Parent',
|
||||
'secondparent_gender' => 'Female',
|
||||
'secondparent_email' => 'second@example.com',
|
||||
'secondparent_phone' => '1234567890',
|
||||
'firstparent_id' => 1,
|
||||
'secondparent_id' => 2,
|
||||
'semester' => 'Spring',
|
||||
'school_year' => '2024-2025',
|
||||
]);
|
||||
|
||||
DB::table('user_roles')->insert([
|
||||
'user_id' => 1,
|
||||
'role_id' => 1,
|
||||
]);
|
||||
|
||||
$service = new InactiveUserCleanupService();
|
||||
$result = $service->cleanup(15);
|
||||
|
||||
$this->assertSame(1, $result['deleted_users']);
|
||||
$this->assertDatabaseMissing('users', ['id' => 1]);
|
||||
$this->assertDatabaseMissing('parents', ['firstparent_id' => 1]);
|
||||
$this->assertDatabaseMissing('user_roles', ['user_id' => 1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit\Services\Whatsapp;
|
||||
|
||||
use App\Services\EmailService;
|
||||
use App\Services\Whatsapp\WhatsappInviteEmailService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Mockery;
|
||||
use Tests\TestCase;
|
||||
|
||||
class WhatsappInviteEmailServiceTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_send_requires_recipients(): void
|
||||
{
|
||||
$service = new WhatsappInviteEmailService(Mockery::mock(EmailService::class));
|
||||
$result = $service->send([]);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
|
||||
public function test_send_dispatches_email(): void
|
||||
{
|
||||
$email = Mockery::mock(EmailService::class);
|
||||
$email->shouldReceive('send')->once()->andReturn(true);
|
||||
|
||||
$service = new WhatsappInviteEmailService($email);
|
||||
$result = $service->send([
|
||||
'to_emails' => ['parent@example.com'],
|
||||
'sections' => [
|
||||
[
|
||||
'class_section_id' => 1,
|
||||
'class_section_name' => 'Class A',
|
||||
'invite_link' => 'https://chat.whatsapp.com/test',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(1, $result['recipients']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user