Added `BaselineReadinessGate`, resolution propagation, and disclosure semantics logic per Spec 385. Integrates baseline unreadiness into Customer Review Workspace and Review Packs to prevent report generation when identity bindings are unresolved. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #456
162 lines
5.7 KiB
PHP
162 lines
5.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Support\Baselines\CompareSemantics\CompareResultReason;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\OperationRunOutcome;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
pest()->browser()->timeout(60_000);
|
|
|
|
beforeEach(function (): void {
|
|
Storage::fake('exports');
|
|
});
|
|
|
|
it('Spec385 smokes baseline readiness blockers on the customer review workspace', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec385 Browser Baseline']);
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
[$profile, $baselineSnapshot] = seedActiveBaselineForTenant($environment);
|
|
|
|
seedBaselineCompareRun(
|
|
tenant: $environment,
|
|
profile: $profile,
|
|
snapshot: $baselineSnapshot,
|
|
compareContext: spec385BrowserCompareContext([CompareResultReason::UnresolvedAmbiguousIdentity]),
|
|
outcome: OperationRunOutcome::PartiallySucceeded->value,
|
|
);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$filePath = 'review-packs/spec385-baseline-readiness.zip';
|
|
|
|
Storage::disk('exports')->put($filePath, 'PK-spec385-browser');
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
$pack = ReviewPack::factory()->ready()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'environment_review_id' => (int) $review->getKey(),
|
|
'evidence_snapshot_id' => (int) $snapshot->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
'options' => [
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
'generated_at' => now(),
|
|
]);
|
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
|
|
|
spec385AuthenticateBrowser($this, $user, $environment);
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($environment))
|
|
->resize(1366, 920)
|
|
->waitForText('Output not customer-ready')
|
|
->assertSee('Review blockers are still recorded for this output.')
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
|
->click('[data-testid="customer-review-output-limitations"] summary')
|
|
->assertSee('Baseline readiness blocked')
|
|
->assertSee('Open baseline resolution')
|
|
->assertDontSee('baseline_identity_unresolved')
|
|
->assertDontSee('provider_resource_id')
|
|
->assertDontSee('canonical_subject_key')
|
|
->assertDontSee('internal_diagnostics')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
|
|
$page->screenshot(true, spec385BrowserScreenshotName('01-baseline-readiness-blocked'));
|
|
spec385CopyBrowserScreenshot('01-baseline-readiness-blocked');
|
|
});
|
|
|
|
function spec385AuthenticateBrowser(mixed $test, User $user, ManagedEnvironment $environment): void
|
|
{
|
|
$workspaceId = (int) $environment->workspace_id;
|
|
|
|
$test->actingAs($user)->withSession([
|
|
WorkspaceContext::SESSION_KEY => $workspaceId,
|
|
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
],
|
|
]);
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
|
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
|
(string) $workspaceId => (int) $environment->getKey(),
|
|
]);
|
|
|
|
setAdminPanelContext($environment);
|
|
}
|
|
|
|
/**
|
|
* @param list<CompareResultReason> $reasons
|
|
* @return array<string, mixed>
|
|
*/
|
|
function spec385BrowserCompareContext(array $reasons): array
|
|
{
|
|
$byReason = [];
|
|
$byReadinessImpact = [];
|
|
|
|
foreach ($reasons as $reason) {
|
|
$byReason[$reason->value] = ($byReason[$reason->value] ?? 0) + 1;
|
|
|
|
$impact = $reason->readinessImpact()->value;
|
|
$byReadinessImpact[$impact] = ($byReadinessImpact[$impact] ?? 0) + 1;
|
|
}
|
|
|
|
return [
|
|
'result_semantics' => [
|
|
'version' => 'compare_semantics.v1',
|
|
'run_outcome' => 'partial',
|
|
'operation_outcome' => OperationRunOutcome::PartiallySucceeded->value,
|
|
'counts' => [
|
|
'by_reason' => $byReason,
|
|
'by_readiness_impact' => $byReadinessImpact,
|
|
],
|
|
],
|
|
];
|
|
}
|
|
|
|
function spec385BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec385-evidence-review-readiness-'.$name;
|
|
}
|
|
|
|
function spec385CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec385BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/385-evidence-review-readiness/artifacts/screenshots');
|
|
|
|
if (! is_dir($targetDirectory)) {
|
|
@mkdir($targetDirectory, 0755, true);
|
|
}
|
|
|
|
if (! is_file($source)) {
|
|
$source = \Pest\Browser\Support\Screenshot::path($filename);
|
|
}
|
|
|
|
for ($attempt = 0; $attempt < 10 && ! is_file($source); $attempt++) {
|
|
usleep(100_000);
|
|
clearstatcache(true, $source);
|
|
}
|
|
|
|
if (is_file($source) && is_dir($targetDirectory) && is_writable($targetDirectory)) {
|
|
@copy($source, $targetDirectory.DIRECTORY_SEPARATOR.$name.'.png');
|
|
}
|
|
}
|