TenantAtlas/tests/Unit/Tenants/TenantActionPolicySurfaceTest.php

170 lines
6.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Resources\TenantResource;
use App\Models\Tenant;
use App\Services\Tenants\TenantActionPolicySurface;
use App\Support\Tenants\TenantActionSurface;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
it('does not expose archive as a lifecycle action for draft and onboarding tenants', function (\Closure $tenantFactory): void {
$tenant = $tenantFactory();
expect(app(TenantActionPolicySurface::class)->lifecycleActionForTenant($tenant))
->toBeNull();
})->with([
'draft' => [fn (): Tenant => Tenant::factory()->draft()->create()],
'onboarding' => [fn (): Tenant => Tenant::factory()->onboarding()->create()],
]);
it('returns archive only for active tenants and restore only for archived tenants', function (
\Closure $tenantFactory,
string $expectedKey,
string $expectedLabel,
): void {
$tenant = $tenantFactory();
$descriptor = app(TenantActionPolicySurface::class)->lifecycleActionForTenant($tenant);
expect($descriptor)
->not->toBeNull()
->and($descriptor?->key)->toBe($expectedKey)
->and($descriptor?->label)->toBe($expectedLabel)
->and($descriptor?->requiresConfirmation)->toBeTrue();
})->with([
'active' => [fn (): Tenant => Tenant::factory()->active()->create(), 'archive', 'Archive'],
'archived' => [fn (): Tenant => Tenant::factory()->archived()->create(), 'restore', 'Restore'],
]);
it('returns resume onboarding as the primary action for draft and onboarding tenants with resumable drafts', function (\Closure $tenantFactory): void {
$tenant = $tenantFactory();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
createOnboardingDraft([
'workspace' => $tenant->workspace,
'tenant' => $tenant,
'started_by' => $user,
'updated_by' => $user,
'state' => [
'entra_tenant_id' => (string) $tenant->tenant_id,
'tenant_name' => (string) $tenant->name,
],
]);
$catalog = tenantActionCatalog($tenant, TenantActionSurface::TenantIndexRow, $user);
expect(tenantActionKeys($catalog))
->toBe(['view', 'related_onboarding'])
->and($catalog[1]->label)->toBe('Resume onboarding');
})->with([
'draft' => [fn (): Tenant => Tenant::factory()->draft()->create()],
'onboarding' => [fn (): Tenant => Tenant::factory()->onboarding()->create()],
]);
it('keeps completed onboarding as a view-only overflow action for active tenants', function (): void {
$tenant = Tenant::factory()->active()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
createOnboardingDraft([
'workspace' => $tenant->workspace,
'tenant' => $tenant,
'started_by' => $user,
'updated_by' => $user,
'status' => 'completed',
'state' => [
'entra_tenant_id' => (string) $tenant->tenant_id,
'tenant_name' => (string) $tenant->name,
],
]);
$catalog = tenantActionCatalog($tenant, TenantActionSurface::TenantIndexRow, $user);
expect(tenantActionKeys($catalog))
->toBe(['view', 'archive', 'related_onboarding'])
->and($catalog[2]->label)->toBe('View completed onboarding');
});
it('keeps tenant index catalogs within the two-primary-action overflow contract', function (): void {
$tenant = Tenant::factory()->active()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
createOnboardingDraft([
'workspace' => $tenant->workspace,
'tenant' => $tenant,
'started_by' => $user,
'updated_by' => $user,
'status' => 'completed',
'state' => [
'entra_tenant_id' => (string) $tenant->tenant_id,
'tenant_name' => (string) $tenant->name,
],
]);
$catalog = tenantActionCatalog($tenant, TenantActionSurface::TenantIndexRow, $user);
$primaryKeys = collect($catalog)
->filter(static fn ($action): bool => $action->group === 'primary')
->map(static fn ($action): string => $action->key)
->values()
->all();
$overflowKeys = collect($catalog)
->filter(static fn ($action): bool => $action->group === 'overflow')
->map(static fn ($action): string => $action->key)
->values()
->all();
expect($primaryKeys)->toBe(['view', 'archive'])
->and($overflowKeys)->toBe(['related_onboarding'])
->and(TenantResource::actionSurfaceDeclaration()->listRowPrimaryActionLimit())->toBe(2);
});
it('invalidates cached tenant index catalogs when the related onboarding draft lifecycle changes', function (): void {
$tenant = Tenant::factory()->active()->create();
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner', ensureDefaultMicrosoftProviderConnection: false);
$draft = createOnboardingDraft([
'workspace' => $tenant->workspace,
'tenant' => $tenant,
'started_by' => $user,
'updated_by' => $user,
'status' => 'in_progress',
'state' => [
'entra_tenant_id' => (string) $tenant->tenant_id,
'tenant_name' => (string) $tenant->name,
],
]);
$initialCatalog = tenantActionCatalog($tenant, TenantActionSurface::TenantIndexRow, $user);
expect(tenantActionKeys($initialCatalog))->toBe(['view', 'archive', 'related_onboarding'])
->and($initialCatalog[2]->group)->toBe('overflow')
->and($initialCatalog[2]->label)->toBe('View related onboarding');
$draft->forceFill([
'completed_at' => now()->addSecond(),
'lifecycle_state' => 'completed',
'updated_at' => now()->addSecond(),
])->save();
$tenant->refresh();
$updatedCatalog = tenantActionCatalog($tenant, TenantActionSurface::TenantIndexRow, $user);
expect(tenantActionKeys($updatedCatalog))->toBe(['view', 'archive', 'related_onboarding'])
->and($updatedCatalog[2]->group)->toBe('overflow')
->and($updatedCatalog[2]->label)->toBe('View completed onboarding');
});
it('uses workflow-accurate onboarding entry labels', function (int $draftCount, string $expectedLabel): void {
$descriptor = app(TenantActionPolicySurface::class)->onboardingEntryDescriptor($draftCount);
expect($descriptor->label)->toBe($expectedLabel);
})->with([
'no drafts' => [0, 'Add tenant'],
'one draft' => [1, 'Resume onboarding'],
'multiple drafts' => [2, 'Choose onboarding draft'],
]);