create(); $worker = User::factory()->create(); $workspace = Workspace::factory()->create(); $workspace->users()->attach($sender->id, ['role' => $senderRole, 'permissions' => []]); $workspace->users()->attach($worker->id, ['role' => 'worker', 'permissions' => []]); $declaration = Declaration::factory()->forWorkspace($workspace)->create([ 'assigned_to' => $worker->id, ]); session(['current_workspace_id' => $workspace->id]); return [$sender, $worker, $workspace, $declaration]; } // ── AC2, AC5, AC7: Owner can send nudge ────────────────── test('owner can send nudge — notification dispatched, activity logged, success flash', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertRedirect(); $response->assertSessionHas('flash', [ 'type' => 'success', 'message' => 'Relance envoyée à '.$worker->name, ]); Notification::assertSentTo($worker, NudgeNotification::class); }); // ── AC2, AC5: Manager can send nudge ───────────────────── test('manager can send nudge — same behavior as owner', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('manager'); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertRedirect(); $response->assertSessionHas('flash', [ 'type' => 'success', 'message' => 'Relance envoyée à '.$worker->name, ]); Notification::assertSentTo($worker, NudgeNotification::class); }); // ── AC5: Worker cannot send nudge ──────────────────────── test('worker cannot send nudge — abort 404', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); $response = $this->actingAs($worker)->post(route('declarations.nudge', $declaration)); $response->assertNotFound(); Notification::assertNothingSent(); }); // ── AC5: Nudge on declaration in wrong workspace ───────── test('nudge on declaration in wrong workspace — abort 404', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); // Create a different workspace and set it as current $otherWorkspace = Workspace::factory()->create(); $otherWorkspace->users()->attach($sender->id, ['role' => 'owner', 'permissions' => []]); session(['current_workspace_id' => $otherWorkspace->id]); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertNotFound(); Notification::assertNothingSent(); }); // ── Nudge on declaration with no assignee ──────────────── test('nudge on declaration with no assignee — returns error', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); $declaration->update(['assigned_to' => null]); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertRedirect(); $response->assertSessionHas('flash', function (array $flash) { return $flash['type'] === 'warning'; }); Notification::assertNothingSent(); }); // ── AC8: Duplicate nudge within 1 hour — debounce ──────── test('duplicate nudge within 1 hour — debounce prevents, returns warning flash', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); // Create a recent nudge notification manually in the database $worker->notifications()->create([ 'id' => \Illuminate\Support\Str::uuid(), 'type' => NudgeNotification::class, 'data' => [ 'workspace_id' => $workspace->id, 'declaration_id' => $declaration->id, 'sender_id' => $sender->id, ], 'created_at' => now()->subMinutes(30), ]); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertRedirect(); $response->assertSessionHas('flash', [ 'type' => 'warning', 'message' => 'Relance déjà envoyée récemment', ]); Notification::assertNothingSent(); }); // ── AC8: Nudge after 1 hour — allowed ──────────────────── test('nudge after 1 hour — allowed (debounce expired)', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); // Create an old nudge notification (more than 1 hour ago) $worker->notifications()->create([ 'id' => \Illuminate\Support\Str::uuid(), 'type' => NudgeNotification::class, 'data' => [ 'workspace_id' => $workspace->id, 'declaration_id' => $declaration->id, 'sender_id' => $sender->id, ], 'created_at' => now()->subMinutes(61), ]); $response = $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $response->assertRedirect(); $response->assertSessionHas('flash', [ 'type' => 'success', 'message' => 'Relance envoyée à '.$worker->name, ]); Notification::assertSentTo($worker, NudgeNotification::class); }); // ── AC7: Activity log entry created ────────────────────── test('activity log entry created with correct actor/target/action', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); $activity = Activity::query() ->where('subject_type', Declaration::class) ->where('subject_id', $declaration->id) ->where('causer_id', $sender->id) ->where('description', 'nudged') ->first(); expect($activity)->not->toBeNull(); expect($activity->causer_id)->toBe($sender->id); expect($activity->subject_id)->toBe($declaration->id); }); // ── Unauthenticated user cannot nudge ──────────────────── test('unauthenticated user cannot nudge — redirected to login', function () { $declaration = Declaration::factory()->create(); $response = $this->post(route('declarations.nudge', $declaration)); $response->assertRedirect(route('login')); }); // ── Cache cleared for assignee after nudge ─────────────── test('notification cache cleared for assignee after nudge', function () { Notification::fake(); [$sender, $worker, $workspace, $declaration] = setupNudgeTest('owner'); // Pre-populate cache Cache::put("user:{$worker->id}:workspace:{$workspace->id}:unread_notifications", 5); $this->actingAs($sender)->post(route('declarations.nudge', $declaration)); expect(Cache::has("user:{$worker->id}:workspace:{$workspace->id}:unread_notifications"))->toBeFalse(); });