create(); createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager'); StoredReport::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE, ]); StoredReport::factory()->create([ 'tenant_id' => (int) $tenant->getKey(), 'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES, ]); OperationRun::factory()->forTenant($tenant)->create(); /** @var FindingExceptionService $exceptionService */ $exceptionService = app(FindingExceptionService::class); $createApprovedException = function (Finding $finding, string $expiresAt) use ($exceptionService, $user, $tenant, $approver): Finding { $requested = $exceptionService->request($finding, $tenant, $user, [ 'owner_user_id' => (int) $user->getKey(), 'request_reason' => 'Temporary exception', 'review_due_at' => now()->addDays(5)->toDateTimeString(), 'expires_at' => now()->addDays(14)->toDateTimeString(), ]); $exceptionService->approve($requested, $approver, [ 'effective_from' => now()->subDays(10)->toDateTimeString(), 'expires_at' => $expiresAt, 'approval_reason' => 'Approved with controls', ]); return $finding; }; $createApprovedException( Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]), now()->addDays(14)->toDateTimeString(), ); $expiredFinding = $createApprovedException( Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]), now()->subDay()->toDateTimeString(), ); app(FindingRiskGovernanceResolver::class)->syncExceptionState($expiredFinding->findingException()->firstOrFail()); $revokedFinding = $createApprovedException( Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]), now()->addDays(14)->toDateTimeString(), ); $exceptionService->revoke($revokedFinding->findingException()->firstOrFail(), $user, [ 'revocation_reason' => 'Controls removed', ]); Finding::factory()->for($tenant)->create([ 'status' => Finding::STATUS_RISK_ACCEPTED, ]); /** @var EvidenceSnapshotService $snapshotService */ $snapshotService = app(EvidenceSnapshotService::class); $payload = $snapshotService->buildSnapshotPayload($tenant); $snapshot = EvidenceSnapshot::query()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'status' => EvidenceSnapshotStatus::Active->value, 'fingerprint' => $payload['fingerprint'], 'completeness_state' => $payload['completeness'], 'summary' => $payload['summary'], 'generated_at' => now(), ]); foreach ($payload['items'] as $item) { $snapshot->items()->create([ 'tenant_id' => (int) $tenant->getKey(), 'workspace_id' => (int) $tenant->workspace_id, 'dimension_key' => $item['dimension_key'], 'state' => $item['state'], 'required' => $item['required'], 'source_kind' => $item['source_kind'], 'source_record_type' => $item['source_record_type'], 'source_record_id' => $item['source_record_id'], 'source_fingerprint' => $item['source_fingerprint'], 'measured_at' => $item['measured_at'], 'freshness_at' => $item['freshness_at'], 'summary_payload' => $item['summary_payload'], 'sort_order' => $item['sort_order'], ]); } /** @var ReviewPackService $reviewPackService */ $reviewPackService = app(ReviewPackService::class); $pack = $reviewPackService->generate($tenant, $user, [ 'include_pii' => true, 'include_operations' => true, ]); $job = new GenerateReviewPackJob( reviewPackId: (int) $pack->getKey(), operationRunId: (int) $pack->operation_run_id, ); app()->call([$job, 'handle']); $pack->refresh(); expect($pack)->toBeInstanceOf(ReviewPack::class) ->and($pack->status)->toBe(ReviewPackStatus::Ready->value) ->and($pack->summary['risk_acceptance'] ?? null)->toBe([ 'status_marked_count' => 4, 'valid_governed_count' => 1, 'warning_count' => 3, 'expired_count' => 1, 'revoked_count' => 1, 'missing_exception_count' => 1, ]); $zipContent = Storage::disk('exports')->get((string) $pack->file_path); $tempFile = tempnam(sys_get_temp_dir(), 'review-pack-risk-'); file_put_contents($tempFile, $zipContent); $zip = new \ZipArchive; $zip->open($tempFile); $summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR); expect($summary['risk_acceptance'] ?? null)->toBe([ 'status_marked_count' => 4, 'valid_governed_count' => 1, 'warning_count' => 3, 'expired_count' => 1, 'revoked_count' => 1, 'missing_exception_count' => 1, ]); $zip->close(); unlink($tempFile); });