actingAs($user); session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); setTenantPanelContext($tenant); return Livewire::actingAs($user)->test(TenantDashboard::class); } it('creates a tenant support request from the dashboard', function (): void { $tenant = Tenant::factory()->create(['name' => 'Contoso Support Tenant']); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); tenantSupportRequestComponent($user, $tenant) ->assertActionVisible('requestSupport') ->assertActionEnabled('requestSupport') ->assertActionExists('requestSupport', fn (Action $action): bool => $action->getLabel() === 'Request support') ->mountAction('requestSupport') ->setActionData([ 'severity' => SupportRequest::SEVERITY_HIGH, 'summary' => 'Policy sync failed after the latest tenant refresh.', 'reproduction_notes' => 'Open the tenant dashboard after a failed sync and request support from the header action.', 'contact_name' => 'Ops On Call', 'contact_email' => 'ops@example.test', ]) ->callMountedAction() ->assertHasNoActionErrors() ->assertNotified('Support request submitted'); $supportRequest = SupportRequest::query()->sole(); expect($supportRequest->internal_reference)->toMatch('/^SR-[0-9A-HJKMNP-TV-Z]{26}$/') ->and($supportRequest->workspace_id)->toBe((int) $tenant->workspace_id) ->and($supportRequest->tenant_id)->toBe((int) $tenant->getKey()) ->and($supportRequest->initiated_by_user_id)->toBe((int) $user->getKey()) ->and($supportRequest->primary_context_type)->toBe(SupportRequest::PRIMARY_CONTEXT_TENANT) ->and($supportRequest->operation_run_id)->toBeNull() ->and($supportRequest->attachment_mode)->toBe(SupportRequest::ATTACHMENT_MODE_DIAGNOSTIC_SNAPSHOT_ATTACHED) ->and($supportRequest->severity)->toBe(SupportRequest::SEVERITY_HIGH) ->and($supportRequest->summary)->toBe('Policy sync failed after the latest tenant refresh.') ->and($supportRequest->reproduction_notes)->toContain('failed sync') ->and($supportRequest->contact_name)->toBe('Ops On Call') ->and($supportRequest->contact_email)->toBe('ops@example.test') ->and(data_get($supportRequest->context_envelope, 'primary_context.type'))->toBe('tenant') ->and(data_get($supportRequest->context_envelope, 'primary_context.tenant_id'))->toBe((int) $tenant->getKey()) ->and(data_get($supportRequest->context_envelope, 'diagnostic_snapshot'))->toBeArray(); }); it('stores canonical context only when the creator cannot view support diagnostics', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); mock(CapabilityResolver::class, function ($mock) use ($tenant): void { $mock->shouldReceive('primeMemberships')->andReturnNull(); $mock->shouldReceive('isMember') ->andReturnUsing(static fn ($user, Tenant $resolvedTenant): bool => (int) $resolvedTenant->getKey() === (int) $tenant->getKey()); $mock->shouldReceive('can') ->andReturnUsing(static function ($user, Tenant $resolvedTenant, string $capability) use ($tenant): bool { expect((int) $resolvedTenant->getKey())->toBe((int) $tenant->getKey()); return match ($capability) { Capabilities::SUPPORT_REQUESTS_CREATE => true, Capabilities::SUPPORT_DIAGNOSTICS_VIEW => false, default => true, }; }); }); tenantSupportRequestComponent($user, $tenant) ->assertActionVisible('requestSupport') ->assertActionEnabled('requestSupport') ->mountAction('requestSupport') ->setActionData([ 'summary' => 'Need help reviewing the latest tenant support context.', ]) ->callMountedAction() ->assertHasNoActionErrors() ->assertNotified('Support request submitted'); $supportRequest = SupportRequest::query()->sole(); expect($supportRequest->severity)->toBe(SupportRequest::SEVERITY_NORMAL) ->and($supportRequest->contact_name)->toBe($user->name) ->and($supportRequest->contact_email)->toBe($user->email) ->and($supportRequest->attachment_mode)->toBe(SupportRequest::ATTACHMENT_MODE_CANONICAL_CONTEXT_ONLY) ->and(data_get($supportRequest->context_envelope, 'diagnostic_snapshot'))->toBeNull() ->and(data_get($supportRequest->context_envelope, 'omissions.0.reason'))->toBe('omitted_without_support_diagnostics_view'); }); it('keeps tenant dashboard support requests deny-as-not-found for workspace members without tenant entitlement', function (): void { $tenant = Tenant::factory()->create(); $user = User::factory()->create(); WorkspaceMembership::factory()->create([ 'workspace_id' => (int) $tenant->workspace_id, 'user_id' => (int) $user->getKey(), 'role' => 'operator', ]); $this->actingAs($user) ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]) ->get(TenantDashboard::getUrl(panel: 'tenant', tenant: $tenant)) ->assertNotFound(); }); it('returns forbidden for entitled tenant members without support request capability', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly'); tenantSupportRequestComponent($user, $tenant) ->assertActionVisible('requestSupport') ->assertActionDisabled('requestSupport') ->call('authorizeTenantSupportRequest') ->assertForbidden(); expect(SupportRequest::query()->count())->toBe(0); });