getJson('/api/v1/notifications'); $response->assertStatus(401); } public function test_index_returns_notifications(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Hello', 'message' => 'World', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'created_at' => now(), 'updated_at' => now(), ]); DB::table('user_notifications')->insert([ 'notification_id' => 1, 'user_id' => 1, 'is_read' => 0, ]); $response = $this->getJson('/api/v1/notifications'); $response->assertOk(); $response->assertJsonPath('ok', true); $response->assertJsonStructure(['ok', 'notifications', 'pagination']); } public function test_show_returns_notification(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Hello', 'message' => 'World', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'created_at' => now(), 'updated_at' => now(), ]); DB::table('user_notifications')->insert([ 'notification_id' => 1, 'user_id' => 1, 'is_read' => 0, ]); $response = $this->getJson('/api/v1/notifications/1'); $response->assertOk(); $response->assertJsonPath('ok', true); $response->assertJsonStructure(['ok', 'notification']); } public function test_store_creates_notification(): void { $this->seedRole('parent'); DB::table('users')->insert([ 'id' => 2, 'firstname' => 'Parent', 'lastname' => 'User', 'email' => 'parent@example.com', 'status' => 'Active', ]); DB::table('user_roles')->insert([ 'user_id' => 2, 'role_id' => 1, ]); $user = $this->createUser(); Sanctum::actingAs($user); $response = $this->postJson('/api/v1/notifications', [ 'title' => 'Announcement', 'message' => 'Hello parents', 'target_group' => 'parent', 'channels' => ['in_app'], ]); $response->assertStatus(201); $response->assertJsonPath('ok', true); $this->assertDatabaseHas('notifications', [ 'title' => 'Announcement', ]); $this->assertDatabaseHas('user_notifications', [ 'user_id' => 2, ]); } public function test_store_validates_payload(): void { $user = $this->createUser(); Sanctum::actingAs($user); $response = $this->postJson('/api/v1/notifications', [ 'title' => '', 'message' => '', 'target_group' => '', 'channels' => ['invalid'], ]); $response->assertStatus(422); $response->assertJsonStructure(['message', 'errors']); } public function test_store_handles_service_exception(): void { $this->mock(NotificationSendService::class, function ($mock) { $mock->shouldReceive('send')->andThrow(new \RuntimeException('boom')); }); $user = $this->createUser(); Sanctum::actingAs($user); $response = $this->postJson('/api/v1/notifications', [ 'title' => 'Announcement', 'message' => 'Hello parents', 'target_group' => 'parent', 'channels' => ['in_app'], ]); $response->assertStatus(500); $response->assertJsonPath('ok', false); } public function test_mark_read_updates_user_notification(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Hello', 'message' => 'World', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'created_at' => now(), 'updated_at' => now(), ]); DB::table('user_notifications')->insert([ 'notification_id' => 1, 'user_id' => 1, 'is_read' => 0, ]); $response = $this->postJson('/api/v1/notifications/1/read'); $response->assertOk(); $this->assertDatabaseHas('user_notifications', [ 'notification_id' => 1, 'user_id' => 1, 'is_read' => 1, ]); } public function test_active_and_deleted_lists(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Active', 'message' => 'Now', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'scheduled_at' => now()->subDay(), 'expires_at' => now()->addDay(), 'created_at' => now(), 'updated_at' => now(), ]); DB::table('notifications')->insert([ 'id' => 2, 'title' => 'Deleted', 'message' => 'Old', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'deleted_at' => now(), 'created_at' => now()->subDays(2), 'updated_at' => now()->subDays(2), ]); $active = $this->getJson('/api/v1/notifications/active'); $active->assertOk(); $active->assertJsonPath('ok', true); $deleted = $this->getJson('/api/v1/notifications/deleted'); $deleted->assertOk(); $deleted->assertJsonPath('ok', true); } public function test_update_and_delete_notification(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Old', 'message' => 'Old', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'created_at' => now(), 'updated_at' => now(), ]); $update = $this->patchJson('/api/v1/notifications/1', [ 'title' => 'New', ]); $update->assertOk(); $this->assertDatabaseHas('notifications', [ 'id' => 1, 'title' => 'New', ]); $delete = $this->deleteJson('/api/v1/notifications/1'); $delete->assertOk(); $this->assertSoftDeleted('notifications', ['id' => 1]); } public function test_restore_notification(): void { $user = $this->createUser(); Sanctum::actingAs($user); DB::table('notifications')->insert([ 'id' => 1, 'title' => 'Old', 'message' => 'Old', 'target_group' => 'parent', 'delivery_channels' => json_encode(['in_app']), 'deleted_at' => now(), 'created_at' => now(), 'updated_at' => now(), ]); $response = $this->postJson('/api/v1/notifications/1/restore'); $response->assertOk(); $this->assertDatabaseHas('notifications', [ 'id' => 1, 'deleted_at' => null, ]); } private function seedRole(string $name): void { DB::table('roles')->insert([ 'id' => 1, 'name' => $name, 'slug' => $name, 'is_active' => 1, ]); } private function createUser(): User { DB::table('users')->insert([ 'id' => 1, 'school_id' => 1, 'firstname' => 'Admin', 'lastname' => 'User', 'cellphone' => '5555555555', 'email' => 'admin@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', ]); return User::query()->findOrFail(1); } }