Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m45s
Implemented the first version of review output resolve actions. Included a ReviewOutputResolveActionMapper, commands to seed browser fixtures, updated CustomerReviewWorkspace, EnvironmentReviewResource, UI enforcement, and related views. Also added extensive unit, feature, and browser tests, and updated the design coverage matrix.
269 lines
11 KiB
PHP
269 lines
11 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\EnvironmentReview;
|
|
use App\Models\EvidenceSnapshot;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\ReviewPack;
|
|
use App\Models\User;
|
|
use App\Support\EnvironmentReviewCompletenessState;
|
|
use App\Support\EnvironmentReviewStatus;
|
|
use App\Support\Governance\Controls\ComplianceEvidenceMappingV1;
|
|
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('Spec349 smokes output resolution guidance states and collapsed disclosures', function (): void {
|
|
[$user, $readyEnvironment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
|
|
$readyEnvironment->forceFill(['name' => 'Spec349 Browser Ready'])->save();
|
|
$blockedEnvironment = spec349BrowserEnvironmentFor($user, $readyEnvironment, 'Spec349 Browser Blocked');
|
|
$internalEnvironment = spec349BrowserEnvironmentFor($user, $readyEnvironment, 'Spec349 Browser Internal');
|
|
|
|
spec349BrowserCreatePublishedReviewWithPack(
|
|
$readyEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($readyEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
[
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'review-packs/spec349-browser-ready.zip',
|
|
markReady: true,
|
|
);
|
|
|
|
spec349BrowserCreatePublishedReviewWithPack(
|
|
$blockedEnvironment,
|
|
$user,
|
|
seedPartialEnvironmentReviewEvidence($blockedEnvironment, findingCount: 0, driftCount: 0),
|
|
[
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'incomplete',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Partial->value,
|
|
'decision_data_state' => 'incomplete',
|
|
'total_count' => 1,
|
|
'summary' => 'Decision evidence is incomplete for this released review.',
|
|
'next_action' => 'Review the evidence basis before relying on the decision summary.',
|
|
'entries' => [],
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
],
|
|
'review-packs/spec349-browser-blocked.zip',
|
|
markReady: false,
|
|
);
|
|
|
|
spec349BrowserCreatePublishedReviewWithPack(
|
|
$internalEnvironment,
|
|
$user,
|
|
seedEnvironmentReviewEvidence($internalEnvironment, findingCount: 0, driftCount: 0),
|
|
[],
|
|
[
|
|
'include_pii' => true,
|
|
'include_operations' => true,
|
|
],
|
|
'review-packs/spec349-browser-internal.zip',
|
|
markReady: true,
|
|
);
|
|
|
|
spec349AuthenticateBrowser($this, $user, $readyEnvironment);
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($blockedEnvironment))
|
|
->resize(1236, 900)
|
|
->waitForText('Output not customer-ready')
|
|
->assertSee('Create next review')
|
|
->assertSee('Inspect review blockers')
|
|
->assertSee('Download review pack with limitations')
|
|
->assertSee('Create the next review cycle from the latest eligible evidence basis.')
|
|
->assertScript('Array.from(document.querySelectorAll("[data-testid=\"customer-review-decision-card\"] [data-testid=\"customer-review-secondary-action\"]")).some((element) => element.innerText.includes("Open review")) === false', true)
|
|
->assertSee('Requires review')
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-output-limitations\"]")?.open === false', true)
|
|
->assertScript('document.querySelector("[data-testid=\"customer-review-technical-details\"]")?.open === false', true)
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec349BrowserScreenshotName('01-output-blocked'));
|
|
spec349CopyBrowserScreenshot('01-output-blocked');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($internalEnvironment))
|
|
->waitForText('Internal review package available')
|
|
->assertSee('Review PII/redaction state')
|
|
->assertSee('Download internal review pack')
|
|
->assertSee('Internal only')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec349BrowserScreenshotName('02-internal-only'));
|
|
spec349CopyBrowserScreenshot('02-internal-only');
|
|
|
|
$page = visit(CustomerReviewWorkspace::environmentFilterUrl($readyEnvironment))
|
|
->waitForText('Customer-safe review pack ready')
|
|
->assertSee('Download customer-safe review pack')
|
|
->assertDontSee('Ready to share')
|
|
->assertNoJavaScriptErrors()
|
|
->assertNoConsoleLogs();
|
|
$page->screenshot(true, spec349BrowserScreenshotName('03-customer-safe-ready'));
|
|
spec349CopyBrowserScreenshot('03-customer-safe-ready');
|
|
});
|
|
|
|
function spec349BrowserScreenshotName(string $name): string
|
|
{
|
|
return 'spec349-output-resolution-guidance-'.$name;
|
|
}
|
|
|
|
function spec349CopyBrowserScreenshot(string $name): void
|
|
{
|
|
$filename = spec349BrowserScreenshotName($name).'.png';
|
|
$source = base_path('tests/Browser/Screenshots/'.$filename);
|
|
$targetDirectory = repo_path('specs/349-customer-review-workspace-output-resolution-guidance/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');
|
|
}
|
|
}
|
|
|
|
function spec349AuthenticateBrowser(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);
|
|
}
|
|
|
|
function spec349BrowserEnvironmentFor(User $user, ManagedEnvironment $baseEnvironment, string $name): ManagedEnvironment
|
|
{
|
|
$environment = ManagedEnvironment::factory()->active()->create([
|
|
'workspace_id' => (int) $baseEnvironment->workspace_id,
|
|
'name' => $name,
|
|
]);
|
|
|
|
createUserWithTenant(tenant: $environment, user: $user, role: 'owner', workspaceRole: 'manager');
|
|
|
|
return $environment;
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $summaryOverrides
|
|
* @param array<string, mixed> $packOptions
|
|
* @return array{0: EnvironmentReview, 1: ReviewPack}
|
|
*/
|
|
function spec349BrowserCreatePublishedReviewWithPack(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
EvidenceSnapshot $snapshot,
|
|
array $summaryOverrides = [],
|
|
array $packOptions = [],
|
|
string $filePath = 'review-packs/spec349-browser-review-pack.zip',
|
|
bool $markReady = true,
|
|
): array {
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$summary = array_replace_recursive(
|
|
is_array($review->summary) ? $review->summary : [],
|
|
[
|
|
'control_interpretation' => [
|
|
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
|
|
'controls' => [
|
|
[
|
|
'control_key' => 'customer-output',
|
|
'title' => 'Customer output',
|
|
'readiness_bucket' => $markReady ? 'evidence_on_record' : 'review_recommended',
|
|
'readiness_label' => $markReady ? 'Evidence on record' : 'Review recommended',
|
|
'primary_reason' => $markReady ? 'Evidence path is complete.' : 'Evidence basis needs review.',
|
|
'recommended_next_action' => $markReady ? 'Open the current customer review pack.' : 'Review the evidence basis before sharing.',
|
|
],
|
|
],
|
|
],
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => $markReady ? 'none' : 'incomplete',
|
|
'evidence_state' => $markReady ? EnvironmentReviewCompletenessState::Complete->value : EnvironmentReviewCompletenessState::Partial->value,
|
|
'decision_data_state' => $markReady ? 'complete' : 'incomplete',
|
|
'total_count' => $markReady ? 0 : 1,
|
|
'summary' => $markReady
|
|
? 'No governance decisions require customer awareness.'
|
|
: 'Decision evidence is incomplete for this released review.',
|
|
'next_action' => $markReady
|
|
? 'Open the current customer review pack.'
|
|
: 'Review the evidence basis before relying on the decision summary.',
|
|
'entries' => [],
|
|
],
|
|
],
|
|
],
|
|
$summaryOverrides,
|
|
);
|
|
|
|
Storage::disk('exports')->put($filePath, 'PK-spec349-browser-test');
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'completeness_state' => $markReady
|
|
? EnvironmentReviewCompletenessState::Complete->value
|
|
: (string) $review->completeness_state,
|
|
'summary' => $summary,
|
|
'generated_at' => now()->subMinutes(5),
|
|
'published_at' => now()->subMinutes(3),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
if ($markReady) {
|
|
$review = markEnvironmentReviewCustomerSafeReady($review);
|
|
}
|
|
|
|
$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' => array_replace([
|
|
'include_pii' => false,
|
|
'include_operations' => true,
|
|
], $packOptions),
|
|
'file_path' => $filePath,
|
|
'file_disk' => 'exports',
|
|
'generated_at' => now()->subMinutes(4),
|
|
]);
|
|
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
])->save();
|
|
|
|
return [$review->refresh(), $pack->refresh()];
|
|
}
|