diff --git a/services/nginx/app/classes/release_manager.php b/services/nginx/app/classes/release_manager.php index 1fa0130c..6e59bb57 100644 --- a/services/nginx/app/classes/release_manager.php +++ b/services/nginx/app/classes/release_manager.php @@ -1064,6 +1064,7 @@ class release_manager private function releaseGateAutoSyncValidationSteps(array $gateInput, ?array $channel): array { $steps = []; + $requiredChecks = is_array($gateInput['required_checks'] ?? null) ? $gateInput['required_checks'] : []; $context = [ 'channel_slug' => $gateInput['channel_slug'] ?? null, 'app' => $gateInput['app'] ?? null, @@ -1071,6 +1072,7 @@ class release_manager 'branch' => $gateInput['branch'] ?? null, 'expected_commit' => $gateInput['expected_commit'] ?? null, 'workflow_url' => $gateInput['workflow_url'] ?? null, + 'required_checks' => $requiredChecks, ]; if ($channel === null) { @@ -1106,6 +1108,17 @@ class release_manager 'context' => $context, ]; } + if ($requiredChecks === []) { + $steps[] = [ + 'step_key' => 'auto_sync_required_checks', + 'label' => 'Automatic update required checks', + 'status' => 'failed', + 'message' => 'Automatic container updates require at least one release gate check.', + 'diagnostic' => 'required_checks was empty.', + 'solution_hint' => 'Include required_checks (for example static_artifact and/or api_gateway) in the release gate payload.', + 'context' => $context, + ]; + } if ($steps === []) { $steps[] = [ diff --git a/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php b/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php index 23b76516..f4caff61 100644 --- a/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php +++ b/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php @@ -96,6 +96,40 @@ it('normalizes app-specific release gate auto-sync metadata', function (): void expect($appMatches->invoke($manager, [], 'api'))->toBeFalse(); }); +it('requires non-empty release gate checks before auto-sync can proceed', function (): void { + $manager = new release_manager(); + $method = new ReflectionMethod(release_manager::class, 'releaseGateAutoSyncValidationSteps'); + $method->setAccessible(true); + + $failed = $method->invoke($manager, [ + 'channel_slug' => 'stable', + 'app' => 'api', + 'repository' => 'copenhagentruckwash/api', + 'branch' => 'master', + 'expected_commit' => 'd52ceb85138740c45f20cda9b7ed9b7a21f0d4e8', + 'workflow_url' => 'https://github.com/copenhagentruckwash/api/actions/runs/123', + 'required_checks' => [], + ], ['slug' => 'stable']); + expect($failed)->toContainEqual(expect()->toMatchArray([ + 'step_key' => 'auto_sync_required_checks', + 'status' => 'failed', + ])); + + $passed = $method->invoke($manager, [ + 'channel_slug' => 'stable', + 'app' => 'api', + 'repository' => 'copenhagentruckwash/api', + 'branch' => 'master', + 'expected_commit' => 'd52ceb85138740c45f20cda9b7ed9b7a21f0d4e8', + 'workflow_url' => 'https://github.com/copenhagentruckwash/api/actions/runs/123', + 'required_checks' => ['api_gateway'], + ], ['slug' => 'stable']); + expect($passed)->toContainEqual(expect()->toMatchArray([ + 'step_key' => 'auto_sync_inputs', + 'status' => 'passed', + ])); +}); + it('normalizes GitHub repository identifiers for private repository access checks', function (): void { expect(release_manager::normalizeGithubRepositoryName('truckwash/backend-php'))->toBe('truckwash/backend-php'); expect(release_manager::normalizeGithubRepositoryName('https://github.com/truckwash/front-end-vue.git'))->toBe('truckwash/front-end-vue');