TenantAtlas/apps/platform/tests/Feature/Filament/Spec330EnvironmentDashboardBaselineCompareProductizationTest.php
ahmido 9a564d6bf2 feat: environment dashboard operator guidance consolidation (spec 352) (#423)
Implemented the consolidated operator guidance panel for the environment dashboard. Updated EnvironmentDashboardSummaryBuilder to prioritize and select guidance based on the operator guidance contract. Added comprehensive unit, feature, and browser tests to verify the guidance selection logic and UI rendering.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #423
2026-06-04 12:56:02 +00:00

324 lines
16 KiB
PHP

<?php
declare(strict_types=1);
use App\Filament\Pages\BaselineCompareLanding;
use App\Filament\Widgets\Dashboard\EnvironmentDashboardOverview;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Support\ManagedEnvironmentLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\Workspaces\WorkspaceContext;
use Livewire\Livewire;
it('keeps the Spec 330 repo truth map present with the required source areas', function (): void {
$path = collect([
dirname(base_path(), 2).'/specs/330-environment-dashboard-baseline-compare-productization/repo-truth-map.md',
dirname(base_path()).'/repo/specs/330-environment-dashboard-baseline-compare-productization/repo-truth-map.md',
])->first(fn (string $candidate): bool => is_file($candidate));
expect($path)->not->toBeNull();
$contents = file_get_contents($path);
expect($contents)
->toContain('Environment Dashboard')
->toContain('Baseline Compare')
->toContain('Provider connection/readiness')
->toContain('Required permissions')
->toContain('Backup sets')
->toContain('Restore runs')
->toContain('Baseline profile assignment')
->toContain('Baseline compare result')
->toContain('OperationRuns');
});
it('renders the Environment Dashboard as a decision-first readiness workbench', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
foreach (range(1, 3) as $index) {
OperationRun::factory()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'type' => 'inventory_sync',
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Failed->value,
'completed_at' => now()->subHours($index),
]);
}
$this->actingAs($user);
setAdminPanelContext($environment);
$component = Livewire::test(EnvironmentDashboardOverview::class)
->assertSee('Is this environment ready, blocked, stale, or requiring review?')
->assertSee('Status')
->assertSee('Reason')
->assertSee('Impact')
->assertSee('Readiness proof')
->assertSee('Recommended next action')
->assertSee('Readiness dimensions')
->assertSee('Recommended next actions')
->assertSee('Supporting signals')
->assertSee('Additional readiness signals used to explain the current recommendation.')
->assertSee('Signal')
->assertSee('State')
->assertSee('Action')
->assertSee('Unavailable')
->assertSee('Not ready')
->assertSee('Absent')
->assertSee('No active review')
->assertSee('No customer-safe output')
->assertSee('Baseline missing')
->assertSee('Operations follow-up')
->assertSee('3 require review')
->assertSee('Open operations hub')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('Secondary context')
->assertDontSee('Show operation details')
->assertDontSee('View operation details')
->assertDontSee('Ab...')
->assertDontSee('Unava...')
->assertDontSee('Not re...')
->assertDontSee('No customer-saf...')
->assertDontSee('Baseline assig...')
->assertDontSee('Fully ready')
->assertDontSee('Protected')
->assertDontSee('Compliant')
->assertDontSee('raw payload')
->assertDontSee('raw diff')
->assertDontSee('provider secret')
->assertDontSee('stack trace')
->assertDontSee('debug metadata')
->assertDontSee('internal exception')
->assertDontSee('provider response');
$content = $component->html();
$readinessDecisionPosition = strpos($content, 'data-testid="tenant-dashboard-readiness-decision"');
$supportingSignalsPosition = strpos($content, 'data-testid="tenant-dashboard-supporting-signals"');
$operationFollowUpPosition = strpos($content, 'data-signal-key="operations_follow_up"');
$diagnosticsPosition = strpos($content, 'data-testid="tenant-dashboard-diagnostics"');
expect(substr_count($content, 'data-testid="tenant-dashboard-readiness-decision"'))->toBe(1)
->and(substr_count($content, 'data-testid="tenant-dashboard-readiness-proof-panel"'))->toBe(1)
->and(substr_count($content, 'data-testid="tenant-dashboard-readiness-card"'))->toBe(0)
->and(substr_count($content, 'data-testid="tenant-dashboard-supporting-signals"'))->toBe(1)
->and(substr_count($content, 'data-testid="tenant-dashboard-supporting-signal"'))->toBe(6)
->and(substr_count($content, 'data-testid="tenant-dashboard-operations-attention-summary"'))->toBe(0)
->and(substr_count($content, 'data-testid="tenant-dashboard-operations-attention-item"'))->toBe(0)
->and(substr_count($content, 'data-testid="tenant-dashboard-operation-details"'))->toBe(0)
->and(substr_count($content, 'data-testid="tenant-dashboard-diagnostics"'))->toBe(1)
->and(substr_count($content, 'data-testid="tenant-dashboard-status-badge"'))->toBeGreaterThan(0)
->and($content)->not->toContain('tenant-dashboard-secondary-context')
->and($content)->not->toContain('data-testid="tenant-dashboard-diagnostics" open')
->and($content)->not->toContain('truncate')
->and($content)->not->toContain('text-overflow')
->and($readinessDecisionPosition)->not->toBeFalse()
->and($supportingSignalsPosition)->not->toBeFalse()
->and($operationFollowUpPosition)->not->toBeFalse()
->and($diagnosticsPosition)->not->toBeFalse()
->and($supportingSignalsPosition)->toBeGreaterThan($readinessDecisionPosition)
->and($operationFollowUpPosition)->toBeGreaterThan($supportingSignalsPosition)
->and($diagnosticsPosition)->toBeGreaterThan($supportingSignalsPosition);
});
it('keeps dynamic Tenant display names while avoiding static tenant copy on the dashboard', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$environment->forceFill(['name' => 'Spec330 Tenant Named Environment'])->save();
$this->actingAs($user);
$this->get(ManagedEnvironmentLinks::viewUrl($environment))
->assertOk()
->assertSeeText('Spec330 Tenant Named Environment')
->assertDontSeeText('MANAGED_ENVIRONMENT')
->assertDontSeeText('current tenant')
->assertDontSeeText('tenant filter')
->assertDontSeeText('all tenants')
->assertDontSeeText('choose tenant')
->assertDontSeeText('tenant scope');
});
it('renders Baseline Compare no-assignment as an actionable unavailable decision state', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
createInventorySyncOperationRunWithCoverage($environment, ['deviceConfiguration' => 'succeeded']);
$this->actingAs($user);
setAdminPanelContext($environment);
$component = baselineCompareLandingLivewire($environment)
->assertSee('Which baseline drift requires action?')
->assertSee('Baseline not assigned')
->assertSee('Baseline drift cannot be used for governance decisions until a baseline assignment exists.')
->assertSee('Evidence unavailable')
->assertSee('Open baseline profiles to assign a baseline to this environment.')
->assertSee('Evidence path')
->assertSee('Next action')
->assertSee('Compare readiness flow')
->assertSee('Baseline comparison needs an assigned baseline, linked snapshots, a compare run, and a decision output.')
->assertSee('Baseline assigned')
->assertSee('Missing')
->assertSee('No baseline is assigned.')
->assertSee('Baseline snapshot')
->assertSee('No snapshot linked.')
->assertSee('Environment snapshot')
->assertSee('Environment snapshot evidence is present.')
->assertSee('Compare run')
->assertSee('Blocked by missing inputs.')
->assertSee('Decision output')
->assertSee('No decision output.')
->assertSee('Available inputs')
->assertSee('Operation proof')
->assertSee('Unavailable because no baseline assigned.')
->assertSee('What this unlocks after assignment')
->assertSee('Actionable drift categories')
->assertSee('Evidence-backed compare')
->assertSee('Governance decision path')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('No Baseline Assigned')
->assertDontSee('This environment does not have an assigned baseline yet.')
->assertDontSee('No coverage warning is currently reported for the latest compare.')
->assertDontSee('Readiness overview')
->assertDontSee('Recent baseline activity')
->assertDontSee('0% Ready')
->assertDontSee('Assigned baseline')
->assertDontSee('Drift impact')
->assertDontSee('Evidence gap details')
->assertDontSee('Search recorded gap subjects')
->assertDontSee('raw payload')
->assertDontSee('raw diff')
->assertDontSee('provider secret')
->assertDontSee('stack trace')
->assertDontSee('debug metadata')
->assertDontSee('internal exception')
->assertDontSee('provider response');
$content = $component->html();
$visibleLabelCount = static fn (string $label): int => preg_match_all('/>\s*'.preg_quote($label, '/').'\s*</', $content);
expect(substr_count($content, 'data-testid="baseline-compare-decision-workbench"'))->toBe(1)
->and(substr_count($content, 'data-testid="baseline-compare-decision-summary"'))->toBe(0)
->and(substr_count($content, 'data-testid="baseline-compare-proof-item"'))->toBe(0)
->and(substr_count($content, 'data-testid="baseline-compare-readiness-flow"'))->toBe(1)
->and(substr_count($content, 'data-testid="baseline-compare-readiness-step"'))->toBe(5)
->and(substr_count($content, 'data-testid="baseline-compare-readiness-connector"'))->toBe(4)
->and(substr_count($content, 'data-testid="baseline-compare-available-input"'))->toBe(3)
->and(substr_count($content, 'data-testid="baseline-compare-assignment-unlocks"'))->toBe(1)
->and($content)->toContain('aria-label="Compare readiness pipeline"')
->and($content)->toContain('data-connector-label="Baseline assigned to Baseline snapshot"')
->and($content)->toContain('data-connector-label="Baseline snapshot to Environment snapshot"')
->and($content)->toContain('data-connector-label="Environment snapshot to Compare run"')
->and($content)->toContain('data-connector-label="Compare run to Decision output"')
->and($content)->toMatch('/data-step-label="Baseline assigned"[\s\S]*?data-step-state="Missing"[\s\S]*?data-step-current-blocker="true"[\s\S]*?>\s*Missing\s*</')
->and($content)->toMatch('/data-step-label="Environment snapshot"[\s\S]*?>\s*Available\s*</')
->and($content)->toMatch('/data-step-label="Compare run"[\s\S]*?>\s*Unavailable\s*</')
->and($content)->toMatch('/data-input-label="Environment snapshot"[\s\S]*?>\s*Available\s*</')
->and($content)->toMatch('/data-input-label="Operation proof"[\s\S]*?>\s*Unavailable\s*</')
->and($visibleLabelCount('Assigned baseline'))->toBe(0)
->and($visibleLabelCount('Compare trust'))->toBe(0)
->and($visibleLabelCount('Drift impact'))->toBe(0)
->and($visibleLabelCount('Evidence path'))->toBe(1)
->and($content)->not->toContain('data-testid="baseline-compare-diagnostics" open')
->and($content)->not->toContain('evidence_gap_details')
->and($content)->not->toContain('raw diff');
});
it('renders Baseline Compare drift and evidence summary before support diagnostics', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$profile, $snapshot] = seedActiveBaselineForTenant($environment);
$run = seedBaselineCompareRun($environment, $profile, $snapshot, [
'reason_code' => \App\Support\Baselines\BaselineCompareReasonCode::CoverageUnproven->value,
'coverage' => [
'effective_types' => ['deviceConfiguration', 'deviceCompliancePolicy'],
'covered_types' => ['deviceConfiguration'],
'uncovered_types' => ['deviceCompliancePolicy'],
'proof' => true,
],
'evidence_gaps' => [
'count' => 1,
'by_reason' => [
'inventory_record_missing' => 1,
],
],
'diagnostics' => [
'support_only' => 'raw payload should stay hidden',
'provider_response' => 'provider response should stay hidden',
],
], OperationRunStatus::Completed->value, OperationRunOutcome::PartiallySucceeded->value);
Finding::factory()->create([
'managed_environment_id' => (int) $environment->getKey(),
'workspace_id' => (int) $environment->workspace_id,
'finding_type' => Finding::FINDING_TYPE_DRIFT,
'scope_key' => 'baseline_profile:'.$profile->getKey(),
'severity' => Finding::SEVERITY_HIGH,
'status' => Finding::STATUS_NEW,
'source' => OperationRunType::BaselineCompare->value,
'baseline_operation_run_id' => (int) $run->getKey(),
]);
$this->actingAs($user);
setAdminPanelContext($environment);
baselineCompareLandingLivewire($environment)
->assertSee('Which baseline drift requires action?')
->assertSee('Drift requires review')
->assertSee('Evidence path')
->assertSee('Evidence gaps need review')
->assertSee('Open operation proof')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('raw payload should stay hidden')
->assertDontSee('provider response should stay hidden')
->assertDontSee('stack trace')
->assertDontSee('debug metadata');
});
it('keeps both surfaces environment-owned and rejects legacy compare entry points', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $environment->workspace_id => (int) $environment->getKey(),
],
])
->get(ManagedEnvironmentLinks::viewUrl($environment))
->assertOk()
->assertSeeText('Is this environment ready, blocked, stale, or requiring review?')
->assertDontSeeText('MANAGED_ENVIRONMENT');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get(ManagedEnvironmentLinks::baselineCompareUrl($environment))
->assertOk()
->assertSeeText('Which baseline drift requires action?')
->assertDontSeeText('MANAGED_ENVIRONMENT');
$this->actingAs($user)
->withSession([
WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id,
WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY => [
(string) $environment->workspace_id => (int) $environment->getKey(),
],
])
->get('/admin/baseline-compare-landing?environment_id='.(int) $environment->getKey().'&tenant_scope=selected')
->assertNotFound();
expect(BaselineCompareLanding::getUrl([
'tenant' => (string) $environment->external_id,
'tenant_id' => (int) $environment->getKey(),
'managed_environment_id' => (int) $environment->getKey(),
'environment' => 'legacy-alias',
'tenant_scope' => 'selected',
'tableFilters' => ['managed_environment_id' => ['value' => (int) $environment->getKey()]],
], panel: 'admin', tenant: $environment))
->not->toContain('tenant_id=')
->not->toContain('managed_environment_id=')
->not->toContain('tenant_scope=')
->not->toContain('tableFilters');
});