service = new NotificationService; } public function test_send_creates_notification_with_all_fields(): void { $notification = $this->service->send( NotificationTypeEnum::FEED_STALE, NotificationSeverityEnum::WARNING, 'Feed is stale', 'Feed "VRT" has not returned new articles in 24 hours.', data: ['hours_stale' => 24], ); $this->assertDatabaseHas('notifications', [ 'type' => 'feed_stale', 'severity' => 'warning', 'title' => 'Feed is stale', ]); $this->assertEquals(['hours_stale' => 24], $notification->data); $this->assertNull($notification->read_at); } public function test_info_convenience_method(): void { $notification = $this->service->info('Test title', 'Test message'); $this->assertEquals(NotificationTypeEnum::GENERAL, $notification->type); $this->assertEquals(NotificationSeverityEnum::INFO, $notification->severity); } public function test_warning_convenience_method(): void { $notification = $this->service->warning('Warning title', 'Warning message'); $this->assertEquals(NotificationSeverityEnum::WARNING, $notification->severity); } public function test_error_convenience_method(): void { $notification = $this->service->error('Error title', 'Error message'); $this->assertEquals(NotificationSeverityEnum::ERROR, $notification->severity); } public function test_mark_as_read(): void { $notification = Notification::factory()->unread()->create(); $this->assertFalse($notification->isRead()); $notification->markAsRead(); $this->assertTrue($notification->fresh()->isRead()); $this->assertNotNull($notification->fresh()->read_at); } public function test_mark_all_as_read(): void { Notification::factory()->count(3)->unread()->create(); Notification::factory()->count(2)->read()->create(); $this->assertEquals(3, Notification::unread()->count()); Notification::markAllAsRead(); $this->assertEquals(0, Notification::unread()->count()); } public function test_unread_scope(): void { Notification::factory()->count(2)->unread()->create(); Notification::factory()->count(3)->read()->create(); $this->assertEquals(2, Notification::unread()->count()); } public function test_recent_scope_limits_to_50(): void { Notification::factory()->count(60)->create(); $this->assertCount(50, Notification::recent()->get()); } public function test_enums_cast_correctly(): void { $notification = Notification::factory()->create([ 'type' => NotificationTypeEnum::PUBLISH_FAILED, 'severity' => NotificationSeverityEnum::ERROR, ]); $fresh = Notification::find($notification->id); $this->assertInstanceOf(NotificationTypeEnum::class, $fresh->type); $this->assertInstanceOf(NotificationSeverityEnum::class, $fresh->severity); $this->assertEquals(NotificationTypeEnum::PUBLISH_FAILED, $fresh->type); $this->assertEquals(NotificationSeverityEnum::ERROR, $fresh->severity); } public function test_notifiable_morph_to_relationship(): void { $feed = Feed::factory()->create(); $notification = $this->service->send( NotificationTypeEnum::FEED_STALE, NotificationSeverityEnum::WARNING, 'Feed stale', 'No new articles', notifiable: $feed, ); $fresh = Notification::find($notification->id); $this->assertInstanceOf(Feed::class, $fresh->notifiable); $this->assertEquals($feed->id, $fresh->notifiable->id); } public function test_notification_without_notifiable(): void { $notification = $this->service->info('General notice', 'Something happened'); $this->assertNull($notification->notifiable_type); $this->assertNull($notification->notifiable_id); $this->assertNull($notification->notifiable); } public function test_notification_type_enum_labels(): void { $this->assertEquals('General', NotificationTypeEnum::GENERAL->label()); $this->assertEquals('Feed Stale', NotificationTypeEnum::FEED_STALE->label()); $this->assertEquals('Publish Failed', NotificationTypeEnum::PUBLISH_FAILED->label()); $this->assertEquals('Credential Expired', NotificationTypeEnum::CREDENTIAL_EXPIRED->label()); } public function test_data_stores_null_when_empty(): void { $notification = $this->service->info('Title', 'Message'); $this->assertNull($notification->data); } public function test_data_stores_array_when_provided(): void { $notification = $this->service->info('Title', 'Message', data: ['key' => 'value']); $this->assertEquals(['key' => 'value'], $notification->data); } }