TenantAtlas/apps/platform/tests/Feature/Filament/Spec336BaselineCompareProductProcessFlowAlignmentTest.php
Ahmed Darrazi 72499a05d6
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
feat: align baseline compare product process flow
2026-05-30 00:07:18 +02:00

352 lines
15 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\BaselineProfile;
use App\Models\BaselineTenantAssignment;
use App\Models\Finding;
use App\Support\Baselines\BaselineCompareReasonCode;
use App\Support\ManagedEnvironmentLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Support\Facades\DB;
it('renders no baseline assigned as the first product process flow blocker', 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('This environment does not have an assigned baseline.')
->assertSee('Open baseline profiles')
->assertSee('Compare readiness flow')
->assertSee('Available inputs')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('raw diff')
->assertDontSee('raw payload')
->assertDontSee('provider response')
->assertDontSee('stack trace');
$content = $component->html();
spec336AssertFlowStep($content, 'Baseline assigned', 'Missing', true);
spec336AssertFlowStep($content, 'Baseline snapshot', 'Unavailable', false);
spec336AssertFlowStep($content, 'Environment snapshot', 'Available', false);
spec336AssertFlowStep($content, 'Compare run', 'Unavailable', false);
spec336AssertFlowStep($content, 'Decision output', 'Unavailable', false);
expect(substr_count($content, 'data-testid="baseline-compare-readiness-step"'))->toBe(5)
->and(substr_count($content, 'data-testid="baseline-compare-available-input"'))->toBe(3)
->and($content)->not->toContain('data-testid="baseline-compare-diagnostics" open');
});
it('renders baseline assigned with snapshot missing as a blocked compare flow', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$profile = BaselineProfile::factory()->active()->create([
'workspace_id' => (int) $environment->workspace_id,
]);
BaselineTenantAssignment::factory()->create([
'workspace_id' => (int) $environment->workspace_id,
'managed_environment_id' => (int) $environment->getKey(),
'baseline_profile_id' => (int) $profile->getKey(),
]);
$this->actingAs($user);
setAdminPanelContext($environment);
$component = baselineCompareLandingLivewire($environment)
->assertSee('Baseline snapshot required')
->assertSee('A baseline is assigned, but no usable baseline snapshot is available.')
->assertSee('Compare cannot run until baseline snapshot input exists.')
->assertSee('Open baseline profile')
->assertSee('No usable baseline snapshot input is linked.')
->assertDontSee('Drift findings available')
->assertDontSee('No drift detected')
->assertDontSee('raw diff');
$content = $component->html();
spec336AssertFlowStep($content, 'Baseline assigned', 'Complete', false);
spec336AssertFlowStep($content, 'Baseline snapshot', 'Missing', true);
spec336AssertFlowStep($content, 'Compare run', 'Unavailable', false);
spec336AssertFlowStep($content, 'Decision output', 'Unavailable', false);
spec336AssertInputState($content, 'Assigned baseline', 'Available');
spec336AssertInputState($content, 'Baseline snapshot', 'Missing');
spec336AssertInputState($content, 'Drift findings', 'Unavailable');
});
it('renders compare required with the capability-aware primary action', function (): void {
[$owner, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$readonly] = createUserWithTenant(tenant: $environment, role: 'readonly', workspaceRole: 'readonly');
seedActiveBaselineForTenant($environment);
createInventorySyncOperationRunWithCoverage($environment, ['deviceConfiguration' => 'succeeded']);
$this->actingAs($owner);
setAdminPanelContext($environment);
$component = baselineCompareLandingLivewire($environment)
->assertSee('Compare run required')
->assertSee('Required inputs exist, but no compare run has been created for the current state.')
->assertSee('Compare now')
->assertActionEnabled('compareNow')
->assertDontSee('Ready to Compare');
$content = $component->html();
spec336AssertFlowStep($content, 'Baseline assigned', 'Complete', false);
spec336AssertFlowStep($content, 'Baseline snapshot', 'Available', false);
spec336AssertFlowStep($content, 'Compare run', 'Required', true);
spec336AssertFlowStep($content, 'Decision output', 'Required', false);
spec336AssertInputState($content, 'OperationRun proof', 'Unavailable');
baselineCompareLandingLivewire($environment, user: $readonly)
->assertSee('Compare unavailable')
->assertActionDisabled('compareNow');
});
it('renders in-progress and failed compare proof without claiming a decision output', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$profile, $snapshot] = seedActiveBaselineForTenant($environment);
$runningRun = seedBaselineCompareRun(
$environment,
$profile,
$snapshot,
[],
OperationRunStatus::Running->value,
OperationRunOutcome::Pending->value,
now(),
);
$this->actingAs($user);
setAdminPanelContext($environment);
$runningComponent = baselineCompareLandingLivewire($environment)
->assertSee('Compare in progress')
->assertSee('View operation progress')
->assertSet('operationRunId', (int) $runningRun->getKey());
$runningContent = $runningComponent->html();
spec336AssertFlowStep($runningContent, 'Compare run', 'In progress', true);
spec336AssertFlowStep($runningContent, 'Decision output', 'Unavailable', false);
spec336AssertInputState($runningContent, 'OperationRun proof', 'Available');
$failedRun = seedBaselineCompareRun(
$environment,
$profile,
$snapshot,
[],
OperationRunStatus::Completed->value,
OperationRunOutcome::Failed->value,
now()->addMinute(),
);
$failedComponent = baselineCompareLandingLivewire($environment)
->assertSee('Compare failed')
->assertSee('Review compare failure')
->assertSet('operationRunId', (int) $failedRun->getKey());
$failedContent = $failedComponent->html();
spec336AssertFlowStep($failedContent, 'Compare run', 'Failed', true);
spec336AssertFlowStep($failedContent, 'Decision output', 'Unavailable', false);
spec336AssertInputState($failedContent, 'OperationRun proof', 'Available');
});
it('renders drift findings and zero-drift outcomes without broad health or evidence claims', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$profile, $snapshot] = seedActiveBaselineForTenant($environment);
$run = seedBaselineCompareRun($environment, $profile, $snapshot, [
'reason_code' => BaselineCompareReasonCode::OverdueFindingsRemain->value,
'coverage' => [
'effective_types' => ['deviceConfiguration'],
'covered_types' => ['deviceConfiguration'],
'uncovered_types' => [],
'proof' => true,
],
]);
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);
$driftComponent = baselineCompareLandingLivewire($environment)
->assertSee('Drift findings available')
->assertSee('Drift requires review before a decision is recorded.')
->assertSee('Review drift findings')
->assertSee('Evidence unavailable - Operation proof available')
->assertDontSee('environment is healthy')
->assertDontSee('customer-safe')
->assertDontSee('compliant');
$driftContent = $driftComponent->html();
spec336AssertFlowStep($driftContent, 'Compare run', 'Available', false);
spec336AssertFlowStep($driftContent, 'Decision output', 'Available', false);
spec336AssertInputState($driftContent, 'Drift findings', 'Available');
$quietEnvironment = createUserWithTenant(tenant: null, user: $user, role: 'owner', workspaceRole: 'manager')[1];
[$quietProfile, $quietSnapshot] = seedActiveBaselineForTenant($quietEnvironment);
seedBaselineCompareRun($quietEnvironment, $quietProfile, $quietSnapshot, [
'reason_code' => BaselineCompareReasonCode::NoDriftDetected->value,
'coverage' => [
'effective_types' => ['deviceConfiguration'],
'covered_types' => ['deviceConfiguration'],
'uncovered_types' => [],
'proof' => true,
],
]);
setAdminPanelContext($quietEnvironment);
$quietComponent = baselineCompareLandingLivewire($quietEnvironment)
->assertSee('No drift detected')
->assertSee('No governance action is required from this compare result within available compare coverage.')
->assertSee('Review evidence')
->assertSee('Evidence unavailable - Operation proof available')
->assertDontSee('environment is healthy')
->assertDontSee('customer-safe')
->assertDontSee('compliant');
spec336AssertFlowStep($quietComponent->html(), 'Decision output', 'Available', false);
});
it('keeps compare result, OperationRun proof, and evidence unavailable states separated', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$profile, $snapshot] = seedActiveBaselineForTenant($environment);
$run = seedBaselineCompareRun($environment, $profile, $snapshot, [
'reason_code' => BaselineCompareReasonCode::EvidenceCaptureIncomplete->value,
'coverage' => [
'effective_types' => ['deviceConfiguration'],
'covered_types' => ['deviceConfiguration'],
'uncovered_types' => [],
'proof' => true,
],
'evidence_gaps' => [
'count' => 1,
'by_reason' => [
'inventory_record_missing' => 1,
],
],
], 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_MEDIUM,
'status' => Finding::STATUS_NEW,
'source' => OperationRunType::BaselineCompare->value,
'baseline_operation_run_id' => (int) $run->getKey(),
]);
$this->actingAs($user);
setAdminPanelContext($environment);
$component = baselineCompareLandingLivewire($environment)
->assertSee('Drift findings available')
->assertSee('OperationRun proof')
->assertSee('Evidence unavailable - Evidence gaps need review')
->assertSee('Compare result exists, but evidence output is not available. Evidence gaps need review.')
->assertSee('Diagnostics - Collapsed')
->assertDontSee('customer-safe')
->assertDontSee('raw diff');
$content = $component->html();
spec336AssertFlowStep($content, 'Compare run', 'Available', false);
spec336AssertFlowStep($content, 'Decision output', 'Needs review', true);
spec336AssertInputState($content, 'OperationRun proof', 'Available');
spec336AssertInputState($content, 'Evidence path', 'Needs review');
expect($content)->not->toContain('data-testid="baseline-compare-diagnostics" open');
});
it('preserves environment-owned routing and workspace isolation', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
$hiddenEnvironment = createUserWithTenant(user: $user, role: 'owner', workspaceRole: 'manager')[1];
[$hiddenProfile, $hiddenSnapshot] = seedActiveBaselineForTenant($hiddenEnvironment);
seedBaselineCompareRun($hiddenEnvironment, $hiddenProfile, $hiddenSnapshot, [
'reason_code' => BaselineCompareReasonCode::OverdueFindingsRemain->value,
'coverage' => [
'effective_types' => ['deviceConfiguration'],
'covered_types' => ['deviceConfiguration'],
'uncovered_types' => [],
'proof' => true,
],
]);
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get(ManagedEnvironmentLinks::baselineCompareUrl($environment))
->assertOk()
->assertSeeText('Which baseline drift requires action?')
->assertDontSeeText((string) $hiddenProfile->name)
->assertDontSeeText('MANAGED_ENVIRONMENT');
$this->actingAs($user)
->withSession([WorkspaceContext::SESSION_KEY => (int) $environment->workspace_id])
->get('/admin/baseline-compare-landing?tenant_id='.(int) $environment->getKey())
->assertNotFound();
});
it('renders invalid assigned baseline scope as needs-review instead of running compare', function (): void {
[$user, $environment] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');
[$profile] = seedActiveBaselineForTenant($environment);
DB::table('baseline_tenant_assignments')
->where('managed_environment_id', (int) $environment->getKey())
->where('baseline_profile_id', (int) $profile->getKey())
->update([
'override_scope_jsonb' => json_encode(['version' => 2, 'entries' => []], JSON_THROW_ON_ERROR),
'updated_at' => now(),
]);
$this->actingAs($user);
setAdminPanelContext($environment);
$component = baselineCompareLandingLivewire($environment)
->assertSee('Baseline scope requires review')
->assertSee('A baseline is assigned, but its scope cannot be used safely for compare.')
->assertSee('Open baseline profile')
->assertActionDisabled('compareNow');
$content = $component->html();
spec336AssertFlowStep($content, 'Baseline assigned', 'Needs review', true);
spec336AssertFlowStep($content, 'Compare run', 'Unavailable', false);
spec336AssertFlowStep($content, 'Decision output', 'Unavailable', false);
});
function spec336AssertFlowStep(string $content, string $label, string $state, bool $currentBlocker): void
{
$blocker = $currentBlocker ? 'true' : 'false';
expect($content)->toMatch(
'/data-step-label="'.preg_quote($label, '/').'"[\s\S]*?data-step-state="'.preg_quote($state, '/').'"[\s\S]*?data-step-current-blocker="'.$blocker.'"[\s\S]*?>\s*'.preg_quote($state, '/').'\s*</'
);
}
function spec336AssertInputState(string $content, string $label, string $state): void
{
expect($content)->toMatch(
'/data-input-label="'.preg_quote($label, '/').'"[\s\S]*?>\s*'.preg_quote($state, '/').'\s*</'
);
}