TenantAtlas/apps/platform/tests/Unit/Providers/ProviderOperationStartResultPresenterTest.php
ahmido e64bae9cfc feat: cut over tenant core to managed environments (#335)
## Summary
- replace the legacy Tenant and TenantMembership core models with ManagedEnvironment and ManagedEnvironmentMembership
- propagate the managed environment naming and key changes across Filament resources, pages, controllers, jobs, models, and supporting runtime paths
- add feature 279 spec artifacts and focused managed-environment test coverage for model behavior, route binding, panel context, authorization, and legacy guardrails

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch pushed from commit `1123b122`
- browser smoke test file was added but not run in this pass

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #335
2026-05-07 06:38:14 +00:00

149 lines
5.9 KiB
PHP

<?php
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Services\Providers\ProviderOperationStartResult;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\ProviderOperationStartResultPresenter;
use App\Support\ReasonTranslation\NextStepOption;
use App\Support\ReasonTranslation\ReasonResolutionEnvelope;
use Filament\Actions\Action;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('builds queued notifications for accepted provider-backed starts', function (): void {
$tenant = ManagedEnvironment::factory()->create();
$run = OperationRun::factory()->create([
'managed_environment_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 = ManagedEnvironment::factory()->create();
$run = OperationRun::factory()->create([
'managed_environment_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 = ManagedEnvironment::factory()->create();
$run = OperationRun::factory()->create([
'managed_environment_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 = ManagedEnvironment::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([
'managed_environment_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',
]);
});