actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $service = app(OperationRunService::class); $run = $service->ensureRun( tenant: $tenant, type: 'policy.sync', inputs: ['scope' => 'all'], initiator: $user, ); $service->dispatchOrFail($run, function (): void { // no-op (dispatch succeeded) }); $this->assertDatabaseHas('notifications', [ 'notifiable_id' => $user->getKey(), 'notifiable_type' => $user->getMorphClass(), 'type' => OperationRunQueued::class, 'data->format' => 'filament', 'data->title' => 'Policy sync queued', ]); $notification = $user->notifications()->latest('id')->first(); expect($notification)->not->toBeNull(); expect($notification->data['actions'][0]['url'] ?? null) ->toBe(OperationRunLinks::view($run, $tenant)); }); it('does not emit queued notifications for runs without an initiator', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $service = app(OperationRunService::class); $run = $service->ensureRun( tenant: $tenant, type: 'policy.sync', inputs: ['scope' => 'all'], initiator: null, ); $service->dispatchOrFail($run, function (): void { // no-op }); expect($user->notifications()->count())->toBe(0); }); it('emits a terminal notification when an operation run transitions to completed', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'user_id' => $user->getKey(), 'initiator_name' => $user->name, 'type' => 'inventory.sync', 'status' => 'queued', 'outcome' => 'pending', 'context' => ['policy_types' => ['deviceConfiguration']], ]); $service = app(OperationRunService::class); $service->updateRun( $run, status: 'completed', outcome: 'succeeded', summaryCounts: ['total' => 1], failures: [], ); $this->assertDatabaseHas('notifications', [ 'notifiable_id' => $user->getKey(), 'notifiable_type' => $user->getMorphClass(), 'type' => OperationRunCompleted::class, 'data->format' => 'filament', 'data->title' => 'Inventory sync completed', ]); $notification = $user->notifications()->latest('id')->first(); expect($notification)->not->toBeNull(); expect($notification->data['actions'][0]['url'] ?? null) ->toBe(OperationRunLinks::view($run, $tenant)); }); it('marks a run failed if dispatch throws synchronously', function () { [$user, $tenant] = createUserWithTenant(role: 'owner'); $this->actingAs($user); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $service = app(OperationRunService::class); $run = $service->ensureRun( tenant: $tenant, type: 'policy.sync', inputs: ['scope' => 'all'], initiator: $user, ); expect(fn () => $service->dispatchOrFail($run, function (): void { throw new RuntimeException('Queue misconfigured'); })) ->toThrow(RuntimeException::class); $run->refresh(); expect($run->status)->toBe('completed'); expect($run->outcome)->toBe('failed'); });