## Summary - add canonical managed-tenant onboarding draft routing with explicit draft identity and landing vs concrete draft behavior - implement draft lifecycle, authorization, attribution, picker UX, resume-stage resolution, and auditable cancel or completion semantics - add focused feature, unit, and browser coverage plus Spec 138 artifacts for the onboarding draft resume flow ## Validation - `vendor/bin/sail artisan test --compact tests/Feature/ManagedTenantOnboardingWizardTest.php tests/Feature/Audit/OnboardingDraftAuditTest.php tests/Feature/Onboarding/OnboardingDraftAccessTest.php tests/Feature/Onboarding/OnboardingDraftAuthorizationTest.php tests/Feature/Onboarding/OnboardingDraftLifecycleTest.php tests/Feature/Onboarding/OnboardingDraftMultiTabTest.php tests/Feature/Onboarding/OnboardingDraftPickerTest.php tests/Feature/Onboarding/OnboardingDraftRoutingTest.php tests/Feature/Onboarding/OnboardingRbacSemanticsTest.php tests/Feature/Onboarding/OnboardingVerificationClustersTest.php tests/Feature/Onboarding/OnboardingVerificationTest.php tests/Feature/Onboarding/OnboardingVerificationV1_5UxTest.php tests/Feature/Verification/VerificationReportViewerDbOnlyTest.php tests/Unit/Onboarding tests/Unit/VerificationReportSanitizerEvidenceKindsTest.php tests/Browser/OnboardingDraftRefreshTest.php tests/Browser/OnboardingDraftVerificationResumeTest.php` - passed: 69 tests, 251 assertions Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #167
103 lines
3.7 KiB
PHP
103 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
|
|
pest()->browser()->timeout(10_000);
|
|
|
|
it('restores the canonical draft route, derived stage, and transient secret inputs after a refresh', function (): void {
|
|
$workspace = Workspace::factory()->create();
|
|
$tenant = Tenant::factory()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => '20202020-2020-2020-2020-202020202020',
|
|
'name' => 'Browser Refresh Tenant',
|
|
'status' => Tenant::STATUS_ONBOARDING,
|
|
]);
|
|
$user = User::factory()->create(['name' => 'Browser Owner']);
|
|
|
|
createUserWithTenant(
|
|
tenant: $tenant,
|
|
user: $user,
|
|
role: 'owner',
|
|
workspaceRole: 'owner',
|
|
ensureDefaultMicrosoftProviderConnection: false,
|
|
);
|
|
|
|
$connection = ProviderConnection::factory()->platform()->consentGranted()->create([
|
|
'workspace_id' => (int) $workspace->getKey(),
|
|
'tenant_id' => (int) $tenant->getKey(),
|
|
'provider' => 'microsoft',
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'display_name' => 'Browser platform connection',
|
|
'is_default' => true,
|
|
'status' => 'connected',
|
|
]);
|
|
|
|
$draft = createOnboardingDraft([
|
|
'workspace' => $workspace,
|
|
'tenant' => $tenant,
|
|
'started_by' => $user,
|
|
'updated_by' => $user,
|
|
'current_step' => 'connection',
|
|
'state' => [
|
|
'entra_tenant_id' => (string) $tenant->tenant_id,
|
|
'tenant_name' => (string) $tenant->name,
|
|
'environment' => 'prod',
|
|
'provider_connection_id' => (int) $connection->getKey(),
|
|
],
|
|
]);
|
|
|
|
$this->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => (int) $workspace->getKey(),
|
|
]);
|
|
session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());
|
|
|
|
$visibleSelectValue = <<<'JS'
|
|
(() => {
|
|
const select = [...document.querySelectorAll('select')].find((element) => {
|
|
const style = window.getComputedStyle(element);
|
|
|
|
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
});
|
|
|
|
return select?.value ?? null;
|
|
})()
|
|
JS;
|
|
|
|
$page = visit(route('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()]));
|
|
|
|
$page
|
|
->assertNoJavaScriptErrors()
|
|
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
|
|
->assertSee('Onboarding draft')
|
|
->assertSee('Browser Refresh Tenant')
|
|
->assertSee('Verify access')
|
|
->assertSee('Status: Not started')
|
|
->refresh()
|
|
->waitForText('Status: Not started')
|
|
->assertNoJavaScriptErrors()
|
|
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
|
|
->assertSee('Verify access')
|
|
->assertSee('Status: Not started')
|
|
->click('Provider connection')
|
|
->assertScript($visibleSelectValue, (string) $connection->getKey())
|
|
->click('Create new connection')
|
|
->check('internal:label="Dedicated override"s')
|
|
->fill('[type="password"]', 'browser-only-secret')
|
|
->assertValue('[type="password"]', 'browser-only-secret')
|
|
->refresh()
|
|
->waitForText('Status: Not started')
|
|
->assertRoute('admin.onboarding.draft', ['onboardingDraft' => (int) $draft->getKey()])
|
|
->assertSee('Verify access')
|
|
->click('Provider connection')
|
|
->assertScript($visibleSelectValue, (string) $connection->getKey())
|
|
->click('Create new connection')
|
|
->check('internal:label="Dedicated override"s')
|
|
->assertValue('[type="password"]', '');
|
|
});
|