create([ 'tenant_id' => (int) $tenant->getKey(), 'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE, 'fingerprint' => hash('sha256', 'permission-'.$tenant->getKey()), ]); StoredReport::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES, 'fingerprint' => hash('sha256', 'entra-'.$tenant->getKey()), ]); Finding::factory()->count(2)->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, ]); Finding::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'finding_type' => Finding::FINDING_TYPE_DRIFT, ]); OperationRun::factory()->forTenant($tenant)->create(); } it('renders the evidence list page for an authorized user', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $this->actingAs($user) ->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant)) ->assertOk(); }); it('disables evidence global search while keeping the view page available', function (): void { $reflection = new ReflectionClass(EvidenceSnapshotResource::class); expect($reflection->getStaticPropertyValue('isGloballySearchable'))->toBeFalse() ->and(array_keys(EvidenceSnapshotResource::getPages()))->toContain('view'); }); it('returns 404 for non members on the evidence list route', function (): void { $tenant = Tenant::factory()->create(); [$user] = createUserWithTenant(role: 'owner'); $this->actingAs($user) ->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant)) ->assertNotFound(); }); it('returns 403 for tenant members without evidence view capability on the evidence list route', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); Gate::define(Capabilities::EVIDENCE_VIEW, fn (): bool => false); $this->actingAs($user) ->get(EvidenceSnapshotResource::getUrl('index', tenant: $tenant)) ->assertForbidden(); }); it('disables create snapshot for readonly members', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly'); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ListEvidenceSnapshots::class) ->assertActionVisible('create_snapshot') ->assertActionDisabled('create_snapshot'); }); it('queues snapshot generation from the list header action', function (): void { Queue::fake(); $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); seedEvidenceDomain($tenant); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ListEvidenceSnapshots::class) ->callAction('create_snapshot') ->assertNotified(); expect(EvidenceSnapshot::query()->count())->toBe(1); Queue::assertPushed(GenerateEvidenceSnapshotJob::class); }); it('renders the view page for an active snapshot', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 2], 'generated_at' => now(), ]); $this->actingAs($user) ->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant)) ->assertOk(); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ViewEvidenceSnapshot::class, ['record' => $snapshot->getKey()]) ->assertActionVisible('refresh_snapshot') ->assertActionVisible('expire_snapshot'); }); it('renders readable evidence dimension summaries and keeps raw json available', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => [ 'finding_count' => 3, 'report_count' => 2, 'operation_count' => 1, ], 'generated_at' => now(), ]); EvidenceSnapshotItem::query()->create([ 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'dimension_key' => 'findings_summary', 'state' => EvidenceCompletenessState::Complete->value, 'required' => true, 'source_kind' => 'model_summary', 'summary_payload' => [ 'count' => 3, 'open_count' => 2, 'severity_counts' => [ 'critical' => 1, 'high' => 1, 'medium' => 1, 'low' => 0, ], 'entries' => [ [ 'title' => 'Admin consent missing', 'severity' => 'critical', 'status' => 'open', ], ], ], 'sort_order' => 10, ]); EvidenceSnapshotItem::query()->create([ 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'dimension_key' => 'entra_admin_roles', 'state' => EvidenceCompletenessState::Complete->value, 'required' => true, 'source_kind' => 'stored_report', 'summary_payload' => [ 'role_count' => 2, 'roles' => [ ['display_name' => 'Global Administrator'], ['display_name' => 'Security Administrator'], ], ], 'sort_order' => 20, ]); EvidenceSnapshotItem::query()->create([ 'evidence_snapshot_id' => (int) $snapshot->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'tenant_id' => (int) $tenant->getKey(), 'dimension_key' => 'operations_summary', 'state' => EvidenceCompletenessState::Complete->value, 'required' => true, 'source_kind' => 'operation_rollup', 'summary_payload' => [ 'operation_count' => 3, 'failed_count' => 0, 'partial_count' => 0, 'entries' => [ [ 'type' => 'tenant.evidence.snapshot.generate', 'outcome' => 'pending', 'status' => 'running', ], [ 'type' => 'tenant.review_pack.generate', 'outcome' => 'succeeded', 'status' => 'completed', ], [ 'type' => 'backup_schedule_purge', 'outcome' => 'pending', 'status' => 'queued', ], ], ], 'sort_order' => 30, ]); $this->actingAs($user) ->get(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $tenant)) ->assertOk() ->assertSeeText('3 findings, 2 open.') ->assertSeeText('Open findings') ->assertSeeText('Admin consent missing') ->assertSeeText('2 privileged Entra roles captured.') ->assertSeeText('Global Administrator') ->assertSeeText('Evidence snapshot generation · In progress') ->assertSeeText('Review pack generation · Completed successfully') ->assertSeeText('Backup schedule purge · Queued for execution') ->assertDontSeeText('Tenant.evidence.snapshot.generate · Pending · Running') ->assertSeeText('Copy JSON'); }); it('hides expire actions for expired snapshots on list and detail surfaces', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Expired->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 2], 'generated_at' => now()->subDay(), 'expires_at' => now(), ]); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ListEvidenceSnapshots::class) ->assertCanSeeTableRecords([$snapshot]) ->assertTableActionHidden('expire', $snapshot); Livewire::actingAs($user) ->test(ViewEvidenceSnapshot::class, ['record' => $snapshot->getKey()]) ->assertActionHidden('expire_snapshot'); }); it('disables refresh and expire actions for readonly members on snapshot detail', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'readonly'); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 2], 'generated_at' => now(), ]); $tenant->makeCurrent(); Filament::setTenant($tenant, true); Livewire::actingAs($user) ->test(ViewEvidenceSnapshot::class, ['record' => $snapshot->getKey()]) ->assertActionVisible('refresh_snapshot') ->assertActionDisabled('refresh_snapshot') ->assertActionVisible('expire_snapshot') ->assertActionDisabled('expire_snapshot'); }); it('keeps evidence list actions within the declared action surface contract', function (): void { $tenant = Tenant::factory()->create(); [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'completeness_state' => EvidenceCompletenessState::Complete->value, 'summary' => ['finding_count' => 2, 'missing_dimensions' => 0], 'generated_at' => now(), ]); $tenant->makeCurrent(); Filament::setTenant($tenant, true); $livewire = Livewire::actingAs($user) ->test(ListEvidenceSnapshots::class) ->assertCanSeeTableRecords([$snapshot]); $table = $livewire->instance()->getTable(); $rowActions = $table->getActions(); $primaryRowActionNames = collect($rowActions) ->reject(static fn ($action): bool => $action instanceof ActionGroup) ->map(static fn ($action): ?string => $action->getName()) ->filter() ->values() ->all(); expect($primaryRowActionNames)->toBe(['view_snapshot', 'expire']) ->and($table->getBulkActions())->toBeEmpty() ->and($table->getRecordUrl($snapshot))->toBe(EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot])); });