TenantAtlas/apps/platform/tests/Feature/Filament/Spec330EnvironmentDashboardBaselineCompareProductizationTest.php
ahmido 0c7adefe5b Spec 330: environment dashboard baseline compare productization (#392)
## Summary
- add the baseline compare landing experience for the environment dashboard productization flow
- expand the environment dashboard overview and summary-building logic to support richer baseline comparison states and assessments
- update the supporting Blade templates for the new compare and overview presentation
- add English and German translations for the baseline compare surface
- include the Spec 330 planning and task artifacts alongside the implementation

## Tests
- touched browser, feature, and unit coverage for the new baseline compare flow
- updated test files include `Spec330EnvironmentDashboardBaselineCompareSmokeTest`, `BaselineCompareLandingWhyNoFindingsTest`, `Spec330EnvironmentDashboardBaselineCompareProductizationTest`, `HeaderContextBarTest`, and `ManagedEnvironmentModelTest`
- no additional test run was performed as part of this commit/push/PR workflow

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #392
2026-05-20 20:32:39 +00:00

326 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('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 compare cannot be used for governance decisions until an assignment exists.')
->assertSee('Compare trust is unavailable until a baseline assignment exists.')
->assertSee('No usable drift result is available yet.')
->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 to this environment.')
->assertSee('Baseline snapshot')
->assertSee('No baseline snapshot is linked.')
->assertSee('Environment snapshot')
->assertSee('Current environment evidence is present.')
->assertSee('Environment snapshot state is required for compare.')
->assertSee('Compare run')
->assertSee('Compare cannot run until required inputs exist.')
->assertSee('Decision output')
->assertSee('No drift decision output is available yet.')
->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');
});