add notifications logic and add support of both JWT and Sanctum

This commit is contained in:
root
2026-03-11 01:20:31 -04:00
parent f6be51576c
commit 182036cc41
141 changed files with 8685 additions and 648 deletions
@@ -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']);
}
}