341 lines
15 KiB
PHP
341 lines
15 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Filament\Pages\Reviews\CustomerReviewWorkspace;
|
|
use App\Models\Finding;
|
|
use App\Models\FindingException;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
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\OperationRunStatus;
|
|
use App\Support\OperationRunType;
|
|
use App\Support\Workspaces\WorkspaceContext;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
it('renders a decision-first consumption contract with one primary action and six flow steps', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec342 Ready Environment']);
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'initiator_name' => 'Spec342 Operator',
|
|
]);
|
|
$review = spec342PublishedReview($environment, $user, $snapshot, [
|
|
'control_interpretation' => [
|
|
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
|
|
'controls' => [
|
|
[
|
|
'control_key' => 'customer-output',
|
|
'title' => 'Customer output',
|
|
'readiness_bucket' => 'evidence_on_record',
|
|
'readiness_label' => 'Evidence on record',
|
|
'primary_reason' => 'Evidence path is complete.',
|
|
'recommended_next_action' => 'Open the current customer review pack.',
|
|
],
|
|
],
|
|
],
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => 'none',
|
|
'evidence_state' => EnvironmentReviewCompletenessState::Complete->value,
|
|
'decision_data_state' => 'complete',
|
|
'total_count' => 0,
|
|
'summary' => 'No governance decisions require customer awareness.',
|
|
'next_action' => 'Open the current customer review pack.',
|
|
'entries' => [],
|
|
],
|
|
],
|
|
]);
|
|
|
|
$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(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
])->save();
|
|
|
|
$component = spec342WorkspaceComponent($user, $environment);
|
|
|
|
$component
|
|
->assertSee('Is this review ready to share?')
|
|
->assertSee('Ready to share')
|
|
->assertSee('Stakeholders can use the current review pack and released review as the evidence path.')
|
|
->assertSee('Review consumption flow')
|
|
->assertSee('Review data')
|
|
->assertSee('Findings triaged')
|
|
->assertSee('Accepted risks reviewed')
|
|
->assertSee('Customer-safe output')
|
|
->assertSee('Findings needing attention')
|
|
->assertSee('No open findings require customer action.')
|
|
->assertSee('Review pack state')
|
|
->assertSee('Export ready')
|
|
->assertSee('Operation proof')
|
|
->assertSee('Spec342 Operator')
|
|
->assertDontSee('Auditor-ready')
|
|
->assertDontSee('environment is healthy')
|
|
->assertDontSee('compliant');
|
|
|
|
$html = $component->html();
|
|
|
|
expect(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
|
->and(substr_count($html, 'data-testid="customer-review-readiness-step"'))->toBe(6)
|
|
->and($html)->toContain('source_surface=customer_review_workspace')
|
|
->and($html)->not->toContain('/admin/t/');
|
|
});
|
|
|
|
it('shows not-ready proof states without raw diagnostics or false output claims', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec342 Evidence Missing']);
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'readonly');
|
|
$snapshot = seedPartialEnvironmentReviewEvidence($environment);
|
|
$review = spec342PublishedReview($environment, $user, $snapshot, [
|
|
'debug_payload' => 'raw payload should stay hidden',
|
|
'provider_response' => 'provider response should stay hidden',
|
|
'stack_trace' => 'stack trace should stay hidden',
|
|
'source_fingerprint' => 'spec342-hidden-fingerprint',
|
|
'control_interpretation' => [
|
|
'version_key' => ComplianceEvidenceMappingV1::VERSION_KEY,
|
|
'controls' => [
|
|
[
|
|
'control_key' => 'customer-output',
|
|
'title' => 'Customer output',
|
|
'readiness_bucket' => 'review_recommended',
|
|
'readiness_label' => 'Review recommended',
|
|
'primary_reason' => 'Evidence basis needs review.',
|
|
'recommended_next_action' => 'Review evidence before sharing.',
|
|
],
|
|
],
|
|
],
|
|
'governance_package' => [
|
|
'decision_summary' => [
|
|
'status' => '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' => [],
|
|
],
|
|
],
|
|
]);
|
|
$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(),
|
|
]);
|
|
$review->forceFill(['current_export_review_pack_id' => (int) $pack->getKey()])->save();
|
|
|
|
$component = spec342WorkspaceComponent($user, $environment)
|
|
->assertSee('Follow-up required before sharing')
|
|
->assertSee('Evidence incomplete')
|
|
->assertSee('Customer-safe output')
|
|
->assertSee('Not ready')
|
|
->assertSee('Diagnostics')
|
|
->assertSee('Collapsed')
|
|
->assertDontSee('Ready to share')
|
|
->assertDontSee('Export ready')
|
|
->assertDontSee('Download review pack')
|
|
->assertDontSee('raw payload should stay hidden')
|
|
->assertDontSee('provider response should stay hidden')
|
|
->assertDontSee('stack trace should stay hidden')
|
|
->assertDontSee('spec342-hidden-fingerprint');
|
|
|
|
expect($component->html())->not->toContain('data-testid="customer-review-diagnostics" open');
|
|
});
|
|
|
|
it('uses concrete findings follow-up copy and keeps review-pack download primary-only', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec342 Findings Follow-up']);
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'owner', workspaceRole: 'manager');
|
|
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
|
|
$run = OperationRun::factory()->forTenant($environment)->create([
|
|
'type' => OperationRunType::ReviewPackGenerate->value,
|
|
'status' => OperationRunStatus::Completed->value,
|
|
'initiator_name' => 'Spec342 Operator',
|
|
]);
|
|
$review = spec342PublishedReview($environment, $user, $snapshot);
|
|
|
|
$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(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
'initiated_by_user_id' => (int) $user->getKey(),
|
|
]);
|
|
$review->forceFill([
|
|
'current_export_review_pack_id' => (int) $pack->getKey(),
|
|
'operation_run_id' => (int) $run->getKey(),
|
|
])->save();
|
|
|
|
Finding::factory()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'severity' => Finding::SEVERITY_CRITICAL,
|
|
'status' => Finding::STATUS_NEW,
|
|
]);
|
|
|
|
$component = spec342WorkspaceComponent($user, $environment)
|
|
->assertSee('Follow-up required before sharing')
|
|
->assertSee('1 open finding needs attention; 1 is high impact. Keep open findings visible before customer handoff.')
|
|
->assertSee('Do not treat this review as share-ready until open findings are resolved, accepted, or explicitly reviewed.')
|
|
->assertSee('Open review')
|
|
->assertSee('Review pack state')
|
|
->assertDontSee('Download review pack')
|
|
->assertDontSee('TenantPilot recorded an access, scope, or configuration issue');
|
|
|
|
$html = $component->html();
|
|
|
|
expect(substr_count($html, 'data-testid="customer-review-primary-action"'))->toBe(1)
|
|
->and($html)->toContain('data-testid="customer-review-findings-summary"')
|
|
->and($html)->not->toContain('data-testid="customer-review-readiness-dimensions"');
|
|
});
|
|
|
|
it('surfaces accepted-risk owner rationale and missing review dates when repo-backed', function (): void {
|
|
$environment = ManagedEnvironment::factory()->create(['name' => 'Spec342 Accepted Risk']);
|
|
[$user, $environment] = createUserWithTenant(tenant: $environment, role: 'readonly');
|
|
$owner = User::factory()->create(['name' => 'Spec342 Risk Owner']);
|
|
$finding = Finding::factory()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'status' => Finding::STATUS_RISK_ACCEPTED,
|
|
]);
|
|
$findingWithoutReviewDate = Finding::factory()->create([
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'status' => Finding::STATUS_RISK_ACCEPTED,
|
|
]);
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'finding_id' => (int) $findingWithoutReviewDate->getKey(),
|
|
'status' => FindingException::STATUS_ACTIVE,
|
|
'current_validity_state' => FindingException::VALIDITY_VALID,
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'request_reason' => 'Customer-approved maintenance window.',
|
|
'owner_user_id' => (int) $owner->getKey(),
|
|
'approved_by_user_id' => (int) $owner->getKey(),
|
|
'requested_at' => now()->subDays(3),
|
|
'approved_at' => now()->subDays(2),
|
|
'effective_from' => now()->subDays(2),
|
|
'review_due_at' => now()->addDays(30),
|
|
]);
|
|
FindingException::query()->create([
|
|
'workspace_id' => (int) $environment->workspace_id,
|
|
'managed_environment_id' => (int) $environment->getKey(),
|
|
'finding_id' => (int) $finding->getKey(),
|
|
'status' => FindingException::STATUS_ACTIVE,
|
|
'current_validity_state' => FindingException::VALIDITY_EXPIRING,
|
|
'requested_by_user_id' => (int) $user->getKey(),
|
|
'request_reason' => 'Pending owner confirmation.',
|
|
'requested_at' => now()->subDay(),
|
|
'approved_at' => now()->subDay(),
|
|
'effective_from' => now()->subDay(),
|
|
'review_due_at' => null,
|
|
'expires_at' => null,
|
|
]);
|
|
|
|
$snapshot = seedEnvironmentReviewEvidence($environment, findingCount: 0, driftCount: 0);
|
|
spec342PublishedReview($environment, $user, $snapshot, [
|
|
'governance_package' => [
|
|
'accepted_risks' => [
|
|
[
|
|
'title' => 'Accepted risk renewal',
|
|
'governance_state' => 'expiring_exception',
|
|
'customer_summary' => 'Accepted risk requires customer awareness.',
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
|
|
spec342WorkspaceComponent($user, $environment)
|
|
->assertSee('Accepted risks')
|
|
->assertSee('2')
|
|
->assertSee('Spec342 Risk Owner')
|
|
->assertSee(now()->addDays(30)->toDateString())
|
|
->assertSee('Customer-approved maintenance window.')
|
|
->assertSee('Review date not recorded')
|
|
->assertSee('Accepted risk requires customer awareness.')
|
|
->assertDontSee('raw operation');
|
|
});
|
|
|
|
it('keeps environment_id as the only canonical workspace filter and rejects cross-workspace targets', function (): void {
|
|
$allowed = ManagedEnvironment::factory()->create(['name' => 'Spec342 Allowed Environment']);
|
|
[$user, $allowed] = createUserWithTenant(tenant: $allowed, role: 'readonly');
|
|
$other = ManagedEnvironment::factory()->create([
|
|
'workspace_id' => (int) $allowed->workspace_id,
|
|
'name' => 'Spec342 Other Environment',
|
|
]);
|
|
createUserWithTenant(tenant: $other, user: $user, role: 'readonly');
|
|
$foreign = ManagedEnvironment::factory()->create(['name' => 'Spec342 Foreign Environment']);
|
|
|
|
spec342PublishedReview($allowed, $user, seedEnvironmentReviewEvidence($allowed));
|
|
spec342PublishedReview($other, $user, seedEnvironmentReviewEvidence($other));
|
|
|
|
$this->actingAs($user)->withSession([WorkspaceContext::SESSION_KEY => (int) $allowed->workspace_id]);
|
|
|
|
Livewire::withQueryParams([
|
|
'environment_id' => (int) $allowed->getKey(),
|
|
'tenant' => (string) $other->external_id,
|
|
'tenant_id' => (int) $other->getKey(),
|
|
])
|
|
->actingAs($user)
|
|
->test(CustomerReviewWorkspace::class)
|
|
->assertSet('tableFilters.managed_environment_id.value', (string) $allowed->getKey())
|
|
->assertCanSeeTableRecords([$allowed->fresh()])
|
|
->assertCanNotSeeTableRecords([$other->fresh()])
|
|
->assertSee('Environment filter:')
|
|
->assertSee('Spec342 Allowed Environment')
|
|
->assertDontSee('/admin/t', false)
|
|
->assertDontSee('tenant_id=', false);
|
|
|
|
$this->actingAs($user)
|
|
->withSession([WorkspaceContext::SESSION_KEY => (int) $allowed->workspace_id])
|
|
->get(CustomerReviewWorkspace::getUrl(panel: 'admin').'?environment_id='.(string) $foreign->getKey())
|
|
->assertNotFound();
|
|
});
|
|
|
|
function spec342WorkspaceComponent(User $user, ManagedEnvironment $environment): mixed
|
|
{
|
|
$workspaceId = (int) $environment->workspace_id;
|
|
|
|
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
|
setAdminPanelContext();
|
|
|
|
return Livewire::actingAs($user)
|
|
->test(CustomerReviewWorkspace::class);
|
|
}
|
|
|
|
function spec342PublishedReview(
|
|
ManagedEnvironment $environment,
|
|
User $user,
|
|
\App\Models\EvidenceSnapshot $snapshot,
|
|
array $summaryOverrides = [],
|
|
): \App\Models\EnvironmentReview {
|
|
$review = composeEnvironmentReviewForTest($environment, $user, $snapshot);
|
|
$summary = array_replace_recursive(is_array($review->summary) ? $review->summary : [], $summaryOverrides);
|
|
|
|
$review->forceFill([
|
|
'status' => EnvironmentReviewStatus::Published->value,
|
|
'summary' => $summary,
|
|
'generated_at' => now(),
|
|
'published_at' => now(),
|
|
'published_by_user_id' => (int) $user->getKey(),
|
|
])->save();
|
|
|
|
return $review->refresh();
|
|
}
|