create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'status' => Tenant::STATUS_ONBOARDING, ]); $user = User::factory()->create(); createUserWithTenant( tenant: $tenant, user: $user, role: 'owner', workspaceRole: 'owner', ensureDefaultMicrosoftProviderConnection: false, ); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $user, 'updated_by' => $user, ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $resolved = app(OnboardingDraftResolver::class)->resolve($draft->getKey(), $user, $workspace); expect((int) $resolved->getKey())->toBe((int) $draft->getKey()) ->and($resolved->relationLoaded('tenant'))->toBeTrue() ->and($resolved->relationLoaded('startedByUser'))->toBeTrue() ->and($resolved->relationLoaded('updatedByUser'))->toBeTrue(); }); it('throws not found when resolving a missing draft', function (): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); expect(fn () => app(OnboardingDraftResolver::class)->resolve(999999, $user, $workspace)) ->toThrow(NotFoundHttpException::class); }); it('throws not found when resolving a draft from another workspace', function (): void { $workspaceA = Workspace::factory()->create(); $workspaceB = Workspace::factory()->create(); $ownerA = User::factory()->create(); $userB = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspaceA->getKey(), 'user_id' => (int) $ownerA->getKey(), 'role' => 'owner', ]); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspaceB->getKey(), 'user_id' => (int) $userB->getKey(), 'role' => 'owner', ]); $draft = createOnboardingDraft([ 'workspace' => $workspaceA, 'started_by' => $ownerA, 'updated_by' => $ownerA, ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspaceB->getKey()); expect(fn () => app(OnboardingDraftResolver::class)->resolve($draft->getKey(), $userB, $workspaceB)) ->toThrow(NotFoundHttpException::class); }); it('throws authorization exceptions for in-scope members without onboarding capability', function (): void { $workspace = Workspace::factory()->create(); $tenant = Tenant::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'status' => Tenant::STATUS_ONBOARDING, ]); $readonlyUser = User::factory()->create(); createUserWithTenant( tenant: $tenant, user: $readonlyUser, role: 'readonly', workspaceRole: 'readonly', ensureDefaultMicrosoftProviderConnection: false, ); $draft = createOnboardingDraft([ 'workspace' => $workspace, 'tenant' => $tenant, 'started_by' => $readonlyUser, 'updated_by' => $readonlyUser, ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); expect(fn () => app(OnboardingDraftResolver::class)->resolve($draft->getKey(), $readonlyUser, $workspace)) ->toThrow(AuthorizationException::class); }); it('returns only authorized resumable drafts for the selected workspace', function (): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspace->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey()); $activeDraft = createOnboardingDraft([ 'workspace' => $workspace, 'started_by' => $user, 'updated_by' => $user, ]); createOnboardingDraft([ 'workspace' => $workspace, 'started_by' => $user, 'updated_by' => $user, 'status' => 'completed', ]); createOnboardingDraft([ 'workspace' => $workspace, 'started_by' => $user, 'updated_by' => $user, 'status' => 'cancelled', ]); $drafts = app(OnboardingDraftResolver::class)->resumableDraftsFor($user, $workspace); expect($drafts->modelKeys())->toBe([(int) $activeDraft->getKey()]); });