TenantAtlas/apps/platform/tests/Feature/Guards/TestLaneTrendContractSchemaTest.php
Ahmed Darrazi 97262787c9
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 4m15s
feat: implement runtime trend recalibration reporting
2026-04-18 09:34:23 +02:00

138 lines
5.0 KiB
PHP

<?php
declare(strict_types=1);
use Tests\Support\TestLaneTrendFixtures;
function validateTrendSchema(mixed $value, array $schema, array $defs, string $path = '$'): array
{
if (isset($schema['$ref']) && is_string($schema['$ref'])) {
$ref = $schema['$ref'];
$definitionKey = str_replace('#/$defs/', '', $ref);
return validateTrendSchema($value, $defs[$definitionKey] ?? [], $defs, $path);
}
$errors = [];
$types = $schema['type'] ?? null;
if (isset($schema['const']) && $value !== $schema['const']) {
$errors[] = sprintf('%s must equal %s.', $path, var_export($schema['const'], true));
}
if (isset($schema['enum']) && ! in_array($value, $schema['enum'], true)) {
$errors[] = sprintf('%s must be one of [%s].', $path, implode(', ', array_map(static fn (mixed $item): string => var_export($item, true), $schema['enum'])));
}
if (is_string($types)) {
$types = [$types];
}
if (is_array($types)) {
$typeValid = false;
foreach ($types as $type) {
$typeValid = match ($type) {
'array' => is_array($value) && array_is_list($value),
'object' => is_array($value) && ! array_is_list($value),
'string' => is_string($value),
'integer' => is_int($value),
'number' => is_int($value) || is_float($value),
'boolean' => is_bool($value),
'null' => $value === null,
default => false,
};
if ($typeValid) {
break;
}
}
if (! $typeValid) {
$errors[] = sprintf('%s has the wrong type.', $path);
return $errors;
}
}
if (is_array($value) && array_is_list($value)) {
if (isset($schema['minItems']) && count($value) < (int) $schema['minItems']) {
$errors[] = sprintf('%s must contain at least %d item(s).', $path, (int) $schema['minItems']);
}
if (isset($schema['items']) && is_array($schema['items'])) {
foreach ($value as $index => $item) {
$errors = array_merge($errors, validateTrendSchema($item, $schema['items'], $defs, sprintf('%s[%d]', $path, $index)));
}
}
return $errors;
}
if (is_array($value) && ! array_is_list($value)) {
foreach ($schema['required'] ?? [] as $requiredKey) {
if (! array_key_exists((string) $requiredKey, $value)) {
$errors[] = sprintf('%s is missing required key [%s].', $path, $requiredKey);
}
}
if (($schema['additionalProperties'] ?? true) === false) {
$allowedKeys = array_keys($schema['properties'] ?? []);
foreach (array_keys($value) as $key) {
if (! in_array($key, $allowedKeys, true)) {
$errors[] = sprintf('%s contains unsupported key [%s].', $path, $key);
}
}
}
foreach ($schema['properties'] ?? [] as $key => $propertySchema) {
if (! array_key_exists($key, $value)) {
continue;
}
$errors = array_merge($errors, validateTrendSchema($value[$key], $propertySchema, $defs, $path.'.'.$key));
}
return $errors;
}
if ((is_int($value) || is_float($value)) && isset($schema['minimum']) && $value < $schema['minimum']) {
$errors[] = sprintf('%s must be >= %s.', $path, $schema['minimum']);
}
return $errors;
}
it('keeps the generated trend-history artifact synchronized with the checked-in JSON schema contract', function (): void {
$schemaPath = repo_path('specs/211-runtime-trend-recalibration/contracts/test-runtime-trend-history.schema.json');
$artifactDirectory = TestLaneTrendFixtures::artifactDirectory('trend-schema-contract');
$report = TestLaneTrendFixtures::buildReport(
laneId: 'fast-feedback',
wallClockSeconds: 184.6,
durationsByFile: [
'tests/Feature/Guards/TestLaneTrendContractSchemaTest.php' => 16.4,
'tests/Feature/Guards/TestLaneArtifactsContractTest.php' => 11.2,
],
artifactDirectory: $artifactDirectory,
ciContext: [
'workflowId' => 'pr-fast-feedback',
'triggerClass' => 'pull-request',
'entryPointResolved' => true,
'workflowLaneMatched' => true,
],
comparisonProfile: 'shared-test-fixture-slimming',
);
/** @var array<string, mixed> $schema */
$schema = json_decode((string) file_get_contents($schemaPath), true, 512, JSON_THROW_ON_ERROR);
$artifact = $report['trendHistoryArtifact'];
$errors = validateTrendSchema($artifact, $schema, $schema['$defs'] ?? []);
expect($artifact['schemaVersion'])->toBe('1.0.0')
->and($artifact['laneId'])->toBe('fast-feedback')
->and($artifact['workflowProfile'])->toBe('pr-fast-feedback')
->and($artifact['history'][0]['artifactRefs']['trendHistory'])->toBe($artifactDirectory.'/fast-feedback-latest.trend-history.json')
->and($errors)->toBe([]);
});