create(); $workspaceA = Workspace::factory()->create(); $workspaceB = Workspace::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspaceA->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $workspaceB->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); $this->actingAs($user) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertRedirect('/admin/choose-workspace'); }); it('returns 404 for users outside the active workspace on the decision register route', function (): void { $user = User::factory()->create(); $workspace = Workspace::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) Workspace::factory()->create()->getKey(), 'user_id' => (int) $user->getKey(), 'role' => 'owner', ]); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertNotFound(); }); it('returns 403 for workspace members with no visible decisions in the default unfiltered register', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'readonly', workspaceRole: 'readonly'); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertForbidden(); }); it('hides the decision register page when the default workspace register would resolve to 403', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'readonly', workspaceRole: 'readonly'); $response = $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get('/admin') ->assertOk(); $response->assertDontSee(DecisionRegister::getUrl(panel: 'admin')); expect(DecisionRegister::canAccess())->toBeFalse(); }); it('returns 404 for explicit tenant filters outside the actor scope', function (): void { $visibleTenant = Tenant::factory()->create(['status' => 'active']); [$user, $visibleTenant] = createUserWithTenant($visibleTenant, role: 'readonly', workspaceRole: 'readonly'); $hiddenTenant = Tenant::factory()->create([ 'status' => 'active', 'workspace_id' => (int) $visibleTenant->workspace_id, ]); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $visibleTenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin').'?tenant_id='.(string) $hiddenTenant->getKey()) ->assertNotFound(); }); it('allows readonly tenant members to open the decision register when visible decisions exist', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'readonly', workspaceRole: 'readonly'); decisionRegisterAuthException( tenant: $tenant, actor: $user, status: FindingException::STATUS_PENDING, validityState: FindingException::VALIDITY_MISSING_SUPPORT, decisionType: FindingExceptionDecision::TYPE_REQUESTED, decisionReason: 'Visible approval request', ); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(DecisionRegister::getUrl(panel: 'admin')) ->assertOk() ->assertSee('Decision register'); }); it('registers the decision register page once visible open decisions exist', function (): void { $tenant = Tenant::factory()->create(['status' => 'active']); [$user, $tenant] = createUserWithTenant($tenant, role: 'readonly', workspaceRole: 'readonly'); decisionRegisterAuthException( tenant: $tenant, actor: $user, status: FindingException::STATUS_PENDING, validityState: FindingException::VALIDITY_MISSING_SUPPORT, decisionType: FindingExceptionDecision::TYPE_REQUESTED, decisionReason: 'Visible approval request', ); $response = $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get('/admin') ->assertOk(); $response->assertSee(DecisionRegister::getUrl(panel: 'admin')); expect(DecisionRegister::canAccess())->toBeTrue(); }); function decisionRegisterAuthException( Tenant $tenant, User $actor, string $status, string $validityState, string $decisionType, string $decisionReason, ): FindingException { $finding = Finding::factory()->for($tenant)->create([ 'workspace_id' => (int) $tenant->workspace_id, ]); $exception = FindingException::query()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'finding_id' => (int) $finding->getKey(), 'requested_by_user_id' => (int) $actor->getKey(), 'owner_user_id' => (int) $actor->getKey(), 'status' => $status, 'current_validity_state' => $validityState, 'request_reason' => 'Decision register authorization test', 'requested_at' => now()->subDay(), 'review_due_at' => now()->addDay(), 'evidence_summary' => ['reference_count' => 0], ]); $decision = $exception->decisions()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'actor_user_id' => (int) $actor->getKey(), 'decision_type' => $decisionType, 'reason' => $decisionReason, 'metadata' => [], 'decided_at' => now()->subDay(), ]); $exception->forceFill(['current_decision_id' => (int) $decision->getKey()])->save(); return $exception->fresh(['currentDecision']); }