create(); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => 'queued', 'context' => [ 'provider_connection_id' => 123, ], ]); $presenter = app(ProviderOperationStartResultPresenter::class); $notification = $presenter->notification( result: ProviderOperationStartResult::started($run, true), blockedTitle: 'Verification blocked', runUrl: OperationRunLinks::tenantlessView($run), extraActions: [ Action::make('manage_connections') ->label('Manage Provider Connections') ->url('/provider-connections'), ], ); $actions = collect($notification->getActions()); expect($notification->getTitle())->toBe('Provider connection check queued') ->and($notification->getBody())->toBe('Queued for execution. Open the operation for progress and next steps.') ->and($actions->map(fn (Action $action): string => (string) $action->getName())->all())->toBe([ 'view_run', 'manage_connections', ]) ->and($actions->map(fn (Action $action): string => (string) $action->getLabel())->all())->toBe([ 'Open operation', 'Manage Provider Connections', ]); }); it('builds already-running notifications for deduped provider-backed starts', function (): void { $tenant = Tenant::factory()->create(); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => 'running', 'context' => [ 'provider_connection_id' => 123, ], ]); $presenter = app(ProviderOperationStartResultPresenter::class); $notification = $presenter->notification( result: ProviderOperationStartResult::deduped($run), blockedTitle: 'Verification blocked', runUrl: OperationRunLinks::tenantlessView($run), ); expect($notification->getTitle())->toBe('Provider connection check already running') ->and($notification->getBody())->toBe('A matching operation is already queued or running. Open the operation for progress and next steps.'); }); it('builds scope-busy notifications for conflicting provider-backed starts', function (): void { $tenant = Tenant::factory()->create(); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'inventory_sync', 'status' => 'running', 'context' => [ 'provider_connection_id' => 123, ], ]); $presenter = app(ProviderOperationStartResultPresenter::class); $notification = $presenter->notification( result: ProviderOperationStartResult::scopeBusy($run), blockedTitle: 'Inventory sync blocked', runUrl: OperationRunLinks::tenantlessView($run), ); expect($notification->getTitle())->toBe('Scope busy') ->and($notification->getBody())->toBe('Another provider-backed operation is already running for this scope. Open the active operation for progress and next steps.'); }); it('builds blocked notifications from translated reason detail and first next step', function (): void { $tenant = Tenant::factory()->create(); $reasonEnvelope = new ReasonResolutionEnvelope( internalCode: 'provider_consent_missing', operatorLabel: 'Admin consent required', shortExplanation: 'Grant admin consent for this provider connection before retrying.', actionability: 'prerequisite_missing', nextSteps: [ NextStepOption::link('Grant admin consent', '/provider-connections/1/consent'), NextStepOption::link('Open provider settings', '/provider-connections/1'), ], ); $run = OperationRun::factory()->create([ 'tenant_id' => $tenant->getKey(), 'type' => 'provider.connection.check', 'status' => 'completed', 'outcome' => 'blocked', 'context' => [ 'reason_code' => 'provider_consent_missing', 'reason_translation' => $reasonEnvelope->toArray(), ], ]); $presenter = app(ProviderOperationStartResultPresenter::class); $notification = $presenter->notification( result: ProviderOperationStartResult::blocked($run), blockedTitle: 'Verification blocked', runUrl: OperationRunLinks::tenantlessView($run), extraActions: [ Action::make('manage_connections') ->label('Manage Provider Connections') ->url('/provider-connections'), ], ); $actions = collect($notification->getActions()); expect($notification->getTitle())->toBe('Verification blocked') ->and($notification->getBody())->toBe("Admin consent required\nGrant admin consent for this provider connection before retrying.\nNext step: Grant admin consent.") ->and($actions->map(fn (Action $action): string => (string) $action->getName())->all())->toBe([ 'view_run', 'next_step_0', 'manage_connections', ]) ->and($actions->map(fn (Action $action): string => (string) $action->getLabel())->all())->toBe([ 'Open operation', 'Grant admin consent', 'Manage Provider Connections', ]); });