TenantAtlas/apps/platform/tests/Feature/Guards/TestLaneArtifactsContractTest.php
ahmido bf38ec1780
Some checks failed
Main Confidence / confidence (push) Failing after 3m36s
Spec 210: implement CI test matrix budget enforcement (#243)
## Summary
- add explicit Gitea workflow files for PR Fast Feedback, `dev` Confidence, Heavy Governance, and Browser lanes
- extend the repo-truth lane support seams with workflow profiles, trigger-aware budget enforcement, artifact publication contracts, CI summaries, and failure classification
- add deterministic artifact staging, new CI governance guard coverage, and Spec 210 planning/contracts/docs updates

## Validation
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Guards/CiFastFeedbackWorkflowContractTest.php tests/Feature/Guards/CiConfidenceWorkflowContractTest.php tests/Feature/Guards/CiHeavyBrowserWorkflowContractTest.php tests/Feature/Guards/CiLaneFailureClassificationContractTest.php tests/Feature/Guards/FastFeedbackLaneContractTest.php tests/Feature/Guards/ConfidenceLaneContractTest.php tests/Feature/Guards/HeavyGovernanceLaneContractTest.php tests/Feature/Guards/BrowserLaneIsolationTest.php tests/Feature/Guards/FixtureLaneImpactBudgetTest.php tests/Feature/Guards/TestLaneManifestTest.php tests/Feature/Guards/TestLaneArtifactsContractTest.php tests/Feature/Guards/TestLaneCommandContractTest.php`
- `./scripts/platform-test-lane fast-feedback`
- `./scripts/platform-test-lane confidence`
- `./scripts/platform-test-lane heavy-governance`
- `./scripts/platform-test-lane browser`
- `./scripts/platform-test-report fast-feedback`
- `./scripts/platform-test-report confidence`

## Notes
- scheduled Heavy Governance and Browser workflows stay gated behind `TENANTATLAS_ENABLE_HEAVY_GOVERNANCE_SCHEDULE=1` and `TENANTATLAS_ENABLE_BROWSER_SCHEDULE=1`
- the remaining rollout evidence task is capturing the live Gitea run set this PR enables: PR Fast Feedback, `dev` Confidence, manual and scheduled Heavy Governance, and manual and scheduled Browser runs

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #243
2026-04-17 18:04:35 +00:00

190 lines
7.9 KiB
PHP

<?php
declare(strict_types=1);
use Tests\Support\TestLaneManifest;
use Tests\Support\TestLaneReport;
function heavyGovernanceSyntheticHotspots(): array
{
return [
['file' => 'tests/Feature/Filament/BaselineProfileCaptureStartSurfaceTest.php', 'seconds' => 22.4],
['file' => 'tests/Feature/Filament/BaselineProfileCompareStartSurfaceTest.php', 'seconds' => 21.1],
['file' => 'tests/Feature/Filament/BaselineActionAuthorizationTest.php', 'seconds' => 19.5],
['file' => 'tests/Feature/Findings/FindingBulkActionsTest.php', 'seconds' => 18.2],
['file' => 'tests/Feature/Findings/FindingWorkflowRowActionsTest.php', 'seconds' => 14.8],
['file' => 'tests/Feature/Findings/FindingWorkflowViewActionsTest.php', 'seconds' => 13.6],
['file' => 'tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php', 'seconds' => 12.7],
['file' => 'tests/Feature/Guards/ActionSurfaceContractTest.php', 'seconds' => 11.9],
['file' => 'tests/Feature/Guards/OperationLifecycleOpsUxGuardTest.php', 'seconds' => 10.4],
['file' => 'tests/Feature/Filament/PolicyResourceAdminSearchParityTest.php', 'seconds' => 4.2],
];
}
it('keeps lane artifact paths app-root relative under storage/logs/test-lanes', function (): void {
$artifacts = TestLaneReport::artifactPaths('fast-feedback');
$artifactContract = TestLaneManifest::artifactPublicationContract('fast-feedback');
expect($artifacts)->toHaveKeys(['junit', 'summary', 'budget', 'report', 'profile']);
expect($artifactContract['requiredFiles'])->toEqualCanonicalizing(['summary.md', 'budget.json', 'report.json', 'junit.xml'])
->and($artifactContract['stagedNamePattern'])->toBe('{laneId}.{artifactFile}');
foreach (array_values($artifacts) as $relativePath) {
expect($relativePath)->toStartWith('storage/logs/test-lanes/');
}
});
it('keeps only the skeleton file checked into the lane artifact directory', function (): void {
$gitignore = base_path('storage/logs/test-lanes/.gitignore');
expect(file_exists($gitignore))->toBeTrue()
->and((string) file_get_contents($gitignore))->toContain('*')
->and((string) file_get_contents($gitignore))->toContain('!.gitignore');
});
it('publishes heavy attribution, contract, and coverage payloads under the canonical artifact root', function (): void {
$durationsByFile = collect(heavyGovernanceSyntheticHotspots())
->mapWithKeys(static fn (array $entry): array => [$entry['file'] => $entry['seconds']])
->all();
$slowestEntries = collect(heavyGovernanceSyntheticHotspots())
->map(static fn (array $entry): array => [
'label' => $entry['file'].'::synthetic',
'subject' => $entry['file'].'::synthetic',
'filePath' => $entry['file'],
'durationSeconds' => $entry['seconds'],
'wallClockSeconds' => $entry['seconds'],
'laneId' => 'heavy-governance',
])
->values()
->all();
$report = TestLaneReport::buildReport(
laneId: 'heavy-governance',
wallClockSeconds: 118.4,
slowestEntries: $slowestEntries,
durationsByFile: $durationsByFile,
);
expect($report['artifactDirectory'])->toBe('storage/logs/test-lanes')
->and($report['slowestEntries'])->toHaveCount(10)
->and($report)->toHaveKeys([
'artifactPublicationContract',
'knownWorkflowProfiles',
'failureClasses',
'budgetContract',
'hotspotInventory',
'decompositionRecords',
'slimmingDecisions',
'authorGuidance',
'inventoryCoverage',
'budgetSnapshots',
'budgetOutcome',
'remainingOpenFamilies',
'stabilizedFamilies',
])
->and(collect($report['classificationAttribution'])->pluck('classificationId')->all())
->toContain('ui-workflow', 'surface-guard', 'discovery-heavy')
->and(collect($report['familyAttribution'])->pluck('familyId')->all())
->toContain(
'baseline-profile-start-surfaces',
'findings-workflow-surfaces',
'finding-bulk-actions-workflow',
'workspace-settings-slice-management',
'action-surface-contract',
'ops-ux-governance',
)
->and(collect($report['budgetEvaluations'])->pluck('targetType')->unique()->values()->all())
->toEqualCanonicalizing(['lane', 'classification', 'family'])
->and($report['familyBudgetEvaluations'])->not->toBeEmpty()
->and($report['inventoryCoverage']['meetsInclusionRule'])->toBeTrue()
->and($report['inventoryCoverage']['inventoryFamilyCount'])->toBe(6)
->and($report['budgetSnapshots'])->toHaveCount(2)
->and($report['budgetOutcome'])->toHaveKeys([
'decisionStatus',
'finalThresholdSeconds',
'remainingOpenFamilies',
'followUpDebt',
])
->and($report['knownWorkflowProfiles'])->toContain('heavy-governance-manual', 'heavy-governance-scheduled');
});
it('stages deterministic CI artifact bundles from the canonical lane outputs', function (): void {
$artifactDirectory = 'storage/logs/test-lanes/contract-stage-test';
$stagingDirectory = base_path('storage/logs/test-lanes/contract-stage-test/staged');
$report = TestLaneReport::buildReport(
laneId: 'fast-feedback',
wallClockSeconds: 182.4,
slowestEntries: [],
durationsByFile: [],
artifactDirectory: $artifactDirectory,
ciContext: [
'workflowId' => 'pr-fast-feedback',
'triggerClass' => 'pull-request',
'entryPointResolved' => true,
'workflowLaneMatched' => true,
],
);
TestLaneReport::writeArtifacts(
laneId: 'fast-feedback',
report: $report,
profileOutput: null,
artifactDirectory: $artifactDirectory,
exitCode: 0,
);
$artifactPaths = TestLaneReport::artifactPaths('fast-feedback', $artifactDirectory);
file_put_contents(
TestLaneManifest::absolutePath($artifactPaths['junit']),
<<<'XML'
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="fast-feedback" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.1">
<testcase name="synthetic" file="tests/Feature/Guards/TestLaneArtifactsContractTest.php" time="0.1" />
</testsuite>
</testsuites>
XML,
);
$stagingResult = TestLaneReport::stageArtifacts(
laneId: 'fast-feedback',
stagingDirectory: $stagingDirectory,
artifactDirectory: $artifactDirectory,
);
expect($stagingResult['complete'])->toBeTrue()
->and(collect($stagingResult['stagedArtifacts'])->pluck('artifactType')->all())
->toEqualCanonicalizing(['summary.md', 'budget.json', 'report.json', 'junit.xml'])
->and(collect($stagingResult['stagedArtifacts'])->pluck('relativePath')->all())
->toContain(
$stagingDirectory.'/fast-feedback.summary.md',
$stagingDirectory.'/fast-feedback.budget.json',
$stagingDirectory.'/fast-feedback.report.json',
$stagingDirectory.'/fast-feedback.junit.xml',
);
});
it('publishes the shared fixture slimming comparison only for the governed standard lanes', function (): void {
$fastFeedback = TestLaneReport::buildReport(
laneId: 'fast-feedback',
wallClockSeconds: 176.73623,
slowestEntries: [],
durationsByFile: [],
comparisonProfile: 'shared-test-fixture-slimming',
);
$heavyGovernance = TestLaneReport::buildReport(
laneId: 'heavy-governance',
wallClockSeconds: 83.66,
slowestEntries: [],
durationsByFile: [],
comparisonProfile: 'shared-test-fixture-slimming',
);
expect($fastFeedback)->toHaveKey('sharedFixtureSlimmingComparison')
->and($heavyGovernance)->not->toHaveKey('sharedFixtureSlimmingComparison');
});