204 lines
8.1 KiB
PHP
204 lines
8.1 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
use App\Enums\NotificationType;
|
||
|
|
use App\Enums\WorkspaceUserRole;
|
||
|
|
use App\Mail\NudgeNotificationMail;
|
||
|
|
use App\Models\Declaration;
|
||
|
|
use App\Models\User;
|
||
|
|
use App\Models\Workspace;
|
||
|
|
use App\Notifications\DeclarationOverdueNotification;
|
||
|
|
use App\Notifications\DocumentUploadedNotification;
|
||
|
|
use App\Notifications\NudgeNotification;
|
||
|
|
use Illuminate\Support\Facades\Notification;
|
||
|
|
use Inertia\Testing\AssertableInertia as Assert;
|
||
|
|
|
||
|
|
function setupNotificationUser(string $role = 'owner'): array
|
||
|
|
{
|
||
|
|
$user = User::factory()->create();
|
||
|
|
$workspace = Workspace::factory()->create();
|
||
|
|
$workspace->users()->attach($user->id, [
|
||
|
|
'role' => $role,
|
||
|
|
'permissions' => [],
|
||
|
|
]);
|
||
|
|
session(['current_workspace_id' => $workspace->id]);
|
||
|
|
|
||
|
|
return [$user, $workspace];
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── NotificationType Enum ──────────────────────────────────
|
||
|
|
|
||
|
|
test('notification type enum has all expected values', function () {
|
||
|
|
expect(NotificationType::getValues())->toContain('nudge')
|
||
|
|
->toContain('declaration_overdue')
|
||
|
|
->toContain('document_uploaded')
|
||
|
|
->toContain('bulk_notification')
|
||
|
|
->toContain('status_changed');
|
||
|
|
|
||
|
|
expect(NotificationType::getValues())->toHaveCount(5);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ── NudgeNotification ──────────────────────────────────────
|
||
|
|
|
||
|
|
test('nudge notification sends via database and mail channels', function () {
|
||
|
|
Notification::fake();
|
||
|
|
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
$recipient = User::factory()->create();
|
||
|
|
$workspace->users()->attach($recipient->id, ['role' => 'worker', 'permissions' => []]);
|
||
|
|
|
||
|
|
$recipient->notify(new NudgeNotification($declaration, $user));
|
||
|
|
|
||
|
|
Notification::assertSentTo($recipient, NudgeNotification::class, function ($notification, $channels) {
|
||
|
|
return in_array('database', $channels) && in_array('mail', $channels);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('nudge notification payload includes workspace_id and notification_type', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$notification = new NudgeNotification($declaration, $user);
|
||
|
|
$payload = $notification->toDatabase($user);
|
||
|
|
|
||
|
|
expect($payload)->toHaveKey('workspace_id', $workspace->id)
|
||
|
|
->toHaveKey('declaration_id', $declaration->id)
|
||
|
|
->toHaveKey('sender_id', $user->id)
|
||
|
|
->toHaveKey('notification_type', NotificationType::Nudge);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('nudge notification toMail returns NudgeNotificationMail', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$notification = new NudgeNotification($declaration, $user);
|
||
|
|
$mailable = $notification->toMail($user);
|
||
|
|
|
||
|
|
expect($mailable)->toBeInstanceOf(NudgeNotificationMail::class);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ── DocumentUploadedNotification ────────────────────────────
|
||
|
|
|
||
|
|
test('document uploaded notification sends via database channel only', function () {
|
||
|
|
Notification::fake();
|
||
|
|
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$user->notify(new DocumentUploadedNotification($declaration, $declaration->client_id));
|
||
|
|
|
||
|
|
Notification::assertSentTo($user, DocumentUploadedNotification::class, function ($notification, $channels) {
|
||
|
|
return $channels === ['database'];
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('document uploaded notification payload includes workspace_id and notification_type', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$notification = new DocumentUploadedNotification($declaration, $declaration->client_id);
|
||
|
|
$payload = $notification->toDatabase($user);
|
||
|
|
|
||
|
|
expect($payload)->toHaveKey('workspace_id', $workspace->id)
|
||
|
|
->toHaveKey('declaration_id', $declaration->id)
|
||
|
|
->toHaveKey('client_id', $declaration->client_id)
|
||
|
|
->toHaveKey('notification_type', NotificationType::DocumentUploaded);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ── DeclarationOverdueNotification ──────────────────────────
|
||
|
|
|
||
|
|
test('declaration overdue notification sends via database and mail channels', function () {
|
||
|
|
Notification::fake();
|
||
|
|
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$user->notify(new DeclarationOverdueNotification($declaration));
|
||
|
|
|
||
|
|
Notification::assertSentTo($user, DeclarationOverdueNotification::class, function ($notification, $channels) {
|
||
|
|
return in_array('database', $channels) && in_array('mail', $channels);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
test('declaration overdue notification payload includes workspace_id and notification_type', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$notification = new DeclarationOverdueNotification($declaration);
|
||
|
|
$payload = $notification->toDatabase($user);
|
||
|
|
|
||
|
|
expect($payload)->toHaveKey('workspace_id', $workspace->id)
|
||
|
|
->toHaveKey('declaration_id', $declaration->id)
|
||
|
|
->toHaveKey('notification_type', NotificationType::DeclarationOverdue);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('declaration overdue notification has retry configuration', function () {
|
||
|
|
$declaration = Declaration::factory()->make();
|
||
|
|
$notification = new DeclarationOverdueNotification($declaration);
|
||
|
|
|
||
|
|
expect($notification->tries)->toBe(3);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('nudge notification has retry configuration', function () {
|
||
|
|
$declaration = Declaration::factory()->make();
|
||
|
|
$sender = User::factory()->make();
|
||
|
|
$notification = new NudgeNotification($declaration, $sender);
|
||
|
|
|
||
|
|
expect($notification->tries)->toBe(3);
|
||
|
|
});
|
||
|
|
|
||
|
|
// ── NotificationController ──────────────────────────────────
|
||
|
|
|
||
|
|
test('notification controller index returns paginated workspace-scoped notifications', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
// Create a notification in the database for this user
|
||
|
|
$user->notify(new DocumentUploadedNotification($declaration, $declaration->client_id));
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->get(route('notifications.index'));
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
$response->assertInertia(fn (Assert $page) => $page
|
||
|
|
->component('notifications/Index')
|
||
|
|
->has('notifications')
|
||
|
|
->has('markAllReadUrl')
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('notification controller markAsRead marks notification as read', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$user->notify(new DocumentUploadedNotification($declaration, $declaration->client_id));
|
||
|
|
$notification = $user->notifications()->first();
|
||
|
|
|
||
|
|
expect($notification->read_at)->toBeNull();
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('notifications.read', $notification->id));
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
expect($notification->fresh()->read_at)->not->toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
test('notification controller markAllAsRead marks all as read', function () {
|
||
|
|
[$user, $workspace] = setupNotificationUser();
|
||
|
|
$declaration = Declaration::factory()->forWorkspace($workspace)->create();
|
||
|
|
|
||
|
|
$user->notify(new DocumentUploadedNotification($declaration, $declaration->client_id));
|
||
|
|
$user->notify(new DocumentUploadedNotification($declaration, $declaration->client_id));
|
||
|
|
|
||
|
|
expect($user->unreadNotifications)->toHaveCount(2);
|
||
|
|
|
||
|
|
$response = $this->actingAs($user)->post(route('notifications.readAll'));
|
||
|
|
|
||
|
|
$response->assertRedirect();
|
||
|
|
$user->refresh();
|
||
|
|
expect($user->unreadNotifications)->toHaveCount(0);
|
||
|
|
});
|
||
|
|
|
||
|
|
test('unauthenticated users cannot access notification routes', function () {
|
||
|
|
$this->get(route('notifications.index'))->assertRedirect(route('login'));
|
||
|
|
$this->post(route('notifications.readAll'))->assertRedirect(route('login'));
|
||
|
|
});
|