TenantAtlas/tests/Feature/Onboarding/OnboardingDraftAuthorizationTest.php

242 lines
7.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\Workspaces\ManagedTenantOnboardingWizard;
use App\Models\Tenant;
use App\Models\User;
use App\Models\Workspace;
use App\Support\Workspaces\WorkspaceContext;
use Livewire\Livewire;
it('allows another authorized operator to load a shared draft and see attribution', function (): void {
$workspace = Workspace::factory()->create();
$tenant = Tenant::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => Tenant::STATUS_ONBOARDING,
'name' => 'Shared Tenant',
]);
$creator = User::factory()->create(['name' => 'Draft Creator']);
$manager = User::factory()->create(['name' => 'Workspace Manager']);
createUserWithTenant(
tenant: $tenant,
user: $creator,
role: 'owner',
workspaceRole: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
createUserWithTenant(
tenant: $tenant,
user: $manager,
role: 'manager',
workspaceRole: 'manager',
ensureDefaultMicrosoftProviderConnection: false,
);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $tenant,
'started_by' => $creator,
'updated_by' => $manager,
'state' => [
'entra_tenant_id' => (string) $tenant->tenant_id,
'tenant_name' => (string) $tenant->name,
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($manager)
->get(route('admin.onboarding.draft', ['onboardingDraft' => $draft->getKey()]))
->assertSuccessful()
->assertSee('Onboarding draft')
->assertSee('Draft Creator')
->assertSee('Workspace Manager');
});
it('allows a manager to cancel a shared onboarding draft', function (): void {
$workspace = Workspace::factory()->create();
$tenant = Tenant::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => Tenant::STATUS_ONBOARDING,
]);
$creator = User::factory()->create();
$manager = User::factory()->create();
createUserWithTenant(
tenant: $tenant,
user: $creator,
role: 'owner',
workspaceRole: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
createUserWithTenant(
tenant: $tenant,
user: $manager,
role: 'manager',
workspaceRole: 'manager',
ensureDefaultMicrosoftProviderConnection: false,
);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $tenant,
'started_by' => $creator,
'updated_by' => $creator,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
Livewire::actingAs($manager)
->test(ManagedTenantOnboardingWizard::class, [
'onboardingDraft' => (int) $draft->getKey(),
])
->mountAction('cancel_onboarding_draft')
->callMountedAction()
->assertNotified('Onboarding draft cancelled');
expect($draft->fresh()->isCancelled())->toBeTrue();
});
it('returns 404 for non-members when requesting a shared onboarding draft', function (): void {
$workspace = Workspace::factory()->create();
$tenant = Tenant::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => Tenant::STATUS_ONBOARDING,
]);
$creator = User::factory()->create();
$nonMember = User::factory()->create();
createUserWithTenant(
tenant: $tenant,
user: $creator,
role: 'owner',
workspaceRole: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $tenant,
'started_by' => $creator,
'updated_by' => $creator,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($nonMember)
->get(route('admin.onboarding.draft', ['onboardingDraft' => $draft->getKey()]))
->assertNotFound();
});
it('returns 403 for readonly members even when they have tenant access', function (): void {
$workspace = Workspace::factory()->create();
$tenant = Tenant::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'status' => Tenant::STATUS_ONBOARDING,
]);
$readonly = User::factory()->create();
createUserWithTenant(
tenant: $tenant,
user: $readonly,
role: 'readonly',
workspaceRole: 'readonly',
ensureDefaultMicrosoftProviderConnection: false,
);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $tenant,
'started_by' => $readonly,
'updated_by' => $readonly,
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($readonly)
->get(route('admin.onboarding.draft', ['onboardingDraft' => $draft->getKey()]))
->assertForbidden();
});
it('returns 404 for workspace members without linked archived tenant entitlement', function (): void {
$workspace = Workspace::factory()->create();
$archivedTenant = Tenant::factory()->archived()->create([
'workspace_id' => (int) $workspace->getKey(),
]);
$creator = User::factory()->create();
$workspaceOwner = User::factory()->create();
createUserWithTenant(
tenant: $archivedTenant,
user: $creator,
role: 'owner',
workspaceRole: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
\App\Models\WorkspaceMembership::factory()->create([
'workspace_id' => (int) $workspace->getKey(),
'user_id' => (int) $workspaceOwner->getKey(),
'role' => 'owner',
]);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $archivedTenant,
'started_by' => $creator,
'updated_by' => $creator,
'state' => [
'entra_tenant_id' => (string) $archivedTenant->tenant_id,
'tenant_name' => (string) $archivedTenant->name,
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($workspaceOwner)
->get(route('admin.onboarding.draft', ['onboardingDraft' => $draft->getKey()]))
->assertNotFound();
});
it('shows a non-editable summary for entitled operators when the linked tenant is already archived', function (): void {
$workspace = Workspace::factory()->create();
$archivedTenant = Tenant::factory()->archived()->create([
'workspace_id' => (int) $workspace->getKey(),
'name' => 'Archived Linked Tenant',
]);
$user = User::factory()->create();
createUserWithTenant(
tenant: $archivedTenant,
user: $user,
role: 'owner',
workspaceRole: 'owner',
ensureDefaultMicrosoftProviderConnection: false,
);
$draft = createOnboardingDraft([
'workspace' => $workspace,
'tenant' => $archivedTenant,
'started_by' => $user,
'updated_by' => $user,
'state' => [
'entra_tenant_id' => (string) $archivedTenant->tenant_id,
'tenant_name' => (string) $archivedTenant->name,
],
]);
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
$this->actingAs($user)
->get(route('admin.onboarding.draft', ['onboardingDraft' => $draft->getKey()]))
->assertSuccessful()
->assertSee('This onboarding draft is Draft.')
->assertSee('Completed, cancelled, and lifecycle-locked drafts remain viewable, but they cannot return to editable wizard mode.')
->assertDontSee('Cancel draft')
->assertSee('View tenant');
});