TenantAtlas/apps/platform/tests/Feature/Guards/CiLaneFailureClassificationContractTest.php
Ahmed Darrazi 8144a3be78
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 7m2s
feat: implement spec 439 branch and evidence truth
2026-07-11 21:13:42 +02:00

176 lines
7.0 KiB
PHP

<?php
declare(strict_types=1);
use Tests\Support\TestLaneBudget;
use Tests\Support\TestLaneReport;
/**
* @param array<string, mixed> $budgetOutcome
* @return array<string, mixed>
*/
function spec439SuccessfulExecutionWithBudget(array $budgetOutcome): array
{
return TestLaneReport::deriveFinalResult(
execution: TestLaneReport::executionResult(0),
evidence: [
'path' => '/tmp/spec439-budget.junit.xml',
'exists' => true,
'parseValid' => true,
'testCount' => 1,
'failureCount' => 0,
'errorCount' => 0,
'skippedCount' => 0,
'status' => 'passed',
],
reportGeneration: ['status' => 'succeeded'],
artifactPublicationStatus: ['complete' => true, 'publishedArtifacts' => []],
budgetOutcome: $budgetOutcome,
);
}
it('classifies wrapper or manifest drift before lower-level lane failures', function (): void {
$primaryFailureClassId = TestLaneReport::classifyPrimaryFailure(
exitCode: 1,
artifactPublicationStatus: ['complete' => true],
budgetOutcome: ['budgetStatus' => 'over-budget'],
entryPointResolved: false,
workflowLaneMatched: false,
);
$finalResult = TestLaneReport::deriveFinalResult(
execution: TestLaneReport::executionResult(0),
evidence: [
'status' => 'passed',
'testCount' => 1,
'failureCount' => 0,
'errorCount' => 0,
'skippedCount' => 0,
],
reportGeneration: ['status' => 'succeeded'],
artifactPublicationStatus: ['complete' => true],
entryPointResolved: false,
workflowLaneMatched: false,
);
expect($primaryFailureClassId)->toBe('wrapper-failure')
->and($finalResult['exitCode'])->toBe(1)
->and($finalResult['failureContextIds'])->toContain('wrapper-failure');
});
it('[EF-004] keeps confidence budget overruns visible without converting them into blocking failures', function (): void {
$budgetOutcome = TestLaneBudget::evaluateLaneForTrigger('confidence', 'mainline-push', 481.0);
$finalResult = spec439SuccessfulExecutionWithBudget($budgetOutcome);
$summary = TestLaneReport::buildCiSummary(
report: [
'laneId' => 'confidence',
'budgetStatus' => 'within-budget',
'ciContext' => ['workflowId' => 'main-confidence'],
'execution' => TestLaneReport::executionResult(0),
'evidence' => ['status' => 'passed'],
'finalResult' => $finalResult,
],
exitCode: 0,
budgetOutcome: $budgetOutcome,
artifactPublicationStatus: ['complete' => true, 'publishedArtifacts' => []],
);
expect($budgetOutcome['budgetStatus'])->toBe('over-budget')
->and($finalResult['exitCode'])->toBe(0)
->and($finalResult['status'])->toBe('passed')
->and($finalResult['blockingStatus'])->toBe('non-blocking-warning')
->and($finalResult['advisoryContextIds'])->toContain('budget-advisory-warning')
->and($summary['primaryFailureClassId'])->toBe('budget-breach')
->and($summary['blockingStatus'])->toBe('non-blocking-warning')
->and($summary['finalExitCode'])->toBe(0);
});
it('[EF-003] treats mature fast-feedback budget overruns as blocking with a non-zero final exit', function (): void {
$budgetOutcome = TestLaneBudget::evaluateLaneForTrigger('fast-feedback', 'pull-request', 216.0);
$finalResult = spec439SuccessfulExecutionWithBudget($budgetOutcome);
$summary = TestLaneReport::buildCiSummary(
report: [
'laneId' => 'fast-feedback',
'budgetStatus' => 'within-budget',
'ciContext' => ['workflowId' => 'pr-fast-feedback'],
'execution' => TestLaneReport::executionResult(0),
'evidence' => ['status' => 'passed'],
'finalResult' => $finalResult,
],
exitCode: 0,
budgetOutcome: $budgetOutcome,
artifactPublicationStatus: ['complete' => true, 'publishedArtifacts' => []],
);
expect($budgetOutcome['budgetStatus'])->toBe('over-budget')
->and($finalResult['exitCode'])->toBe(1)
->and($finalResult['status'])->toBe('failed')
->and($finalResult['failureContextIds'])->toContain('blocking-budget-breach')
->and($summary['primaryFailureClassId'])->toBe('budget-breach')
->and($summary['blockingStatus'])->toBe('blocking')
->and($summary['finalExitCode'])->toBe(1);
});
it('classifies incomplete artifact bundles independently from test and budget status', function (): void {
$artifactPublicationStatus = [
'complete' => false,
'publishedArtifacts' => [],
'missingRequiredArtifacts' => ['report.json'],
];
$finalResult = TestLaneReport::deriveFinalResult(
execution: TestLaneReport::executionResult(0),
evidence: [
'status' => 'passed',
'testCount' => 1,
'failureCount' => 0,
'errorCount' => 0,
'skippedCount' => 0,
],
reportGeneration: ['status' => 'succeeded'],
artifactPublicationStatus: $artifactPublicationStatus,
);
$summary = TestLaneReport::buildCiSummary(
report: [
'laneId' => 'fast-feedback',
'budgetStatus' => 'within-budget',
'ciContext' => ['workflowId' => 'pr-fast-feedback'],
'execution' => TestLaneReport::executionResult(0),
'evidence' => ['status' => 'passed'],
'finalResult' => $finalResult,
],
exitCode: 0,
budgetOutcome: null,
artifactPublicationStatus: $artifactPublicationStatus,
);
expect($summary['primaryFailureClassId'])->toBe('artifact-publication-failure')
->and($summary['blockingStatus'])->toBe('blocking')
->and($finalResult['status'])->toBe('failed')
->and($finalResult['exitCode'])->toBe(1)
->and($finalResult['failureContextIds'])->toContain('artifact-publication-failure')
->and($summary['finalExitCode'])->toBe(1);
});
it('surfaces classification-only scope boundaries for heavy governance and browser reports', function (): void {
$heavySummary = TestLaneReport::buildCiSummary(
report: [
'laneId' => 'heavy-governance',
'budgetStatus' => 'within-budget',
'ciContext' => ['workflowId' => 'heavy-governance-manual'],
],
exitCode: 1,
artifactPublicationStatus: ['complete' => true, 'publishedArtifacts' => []],
);
$browserSummary = TestLaneReport::buildCiSummary(
report: [
'laneId' => 'browser',
'budgetStatus' => 'within-budget',
'ciContext' => ['workflowId' => 'browser-manual'],
],
exitCode: 1,
artifactPublicationStatus: ['complete' => true, 'publishedArtifacts' => []],
);
expect($heavySummary['scopeBoundaryNote'])->toContain('full-suite repair ownership')
->and($browserSummary['scopeBoundaryNote'])->toContain('classification-only');
});