From 4ea5eeb942a3d54b24833dd3870acbed407017c3 Mon Sep 17 00:00:00 2001 From: Jeppe B <2jepp9350@gmail.com> Date: Mon, 1 Jun 2026 23:23:23 +0200 Subject: [PATCH] Require app-scoped release gates for bundle promotion --- services/nginx/app/classes/release_manager.php | 9 +++++---- .../app/tests/Unit/ReleaseManager/ReleaseManagerTest.php | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/services/nginx/app/classes/release_manager.php b/services/nginx/app/classes/release_manager.php index 6e59bb57..c978859c 100644 --- a/services/nginx/app/classes/release_manager.php +++ b/services/nginx/app/classes/release_manager.php @@ -1018,6 +1018,7 @@ class release_manager 'channel_slug' => $channelSlug, 'route_slug' => $routeSlug, 'app' => $app, + 'apps' => $this->releaseTestAppsFromInput($input), 'repository' => $repository, 'branch' => $branch, 'auto_sync' => $this->toBool($input['auto_sync'] ?? false), @@ -4726,22 +4727,22 @@ class release_manager throw new RuntimeException('Beta release channel uses production services and does not promote separate release bundles.'); } $this->assertBetaProductionDataPolicy($channel, $serviceSet); + $frontendVersionId = $this->nullablePositiveInt($bundle['frontend_version_id'] ?? null); + $apiVersionId = $this->nullablePositiveInt($bundle['api_version_id'] ?? null); $this->assertReleaseGatePassedForPromotion( $channelId, (string)($bundle['frontend_commit_sha'] ?? ''), null, 'frontend' ); - if (trim((string)($bundle['api_commit_sha'] ?? '')) !== '') { + if ($apiVersionId !== null) { $this->assertReleaseGatePassedForPromotion( $channelId, - (string)$bundle['api_commit_sha'], + (string)($bundle['api_commit_sha'] ?? ''), null, 'api' ); } - $frontendVersionId = $this->nullablePositiveInt($bundle['frontend_version_id'] ?? null); - $apiVersionId = $this->nullablePositiveInt($bundle['api_version_id'] ?? null); $deploymentId = $this->nullablePositiveInt($bundle['api_deployment_id'] ?? null) ?? $this->nullablePositiveInt($bundle['frontend_deployment_id'] ?? null); diff --git a/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php b/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php index f4caff61..17b1bcb4 100644 --- a/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php +++ b/services/nginx/app/tests/Unit/ReleaseManager/ReleaseManagerTest.php @@ -83,6 +83,7 @@ it('normalizes app-specific release gate auto-sync metadata', function (): void 'channel_slug' => 'stable', 'route_slug' => 'master', 'app' => 'api', + 'apps' => ['api'], 'repository' => 'copenhagentruckwash/api', 'branch' => 'master', 'expected_commit' => 'd52ceb85138740c45f20cda9b7ed9b7a21f0d4e8', @@ -90,6 +91,11 @@ it('normalizes app-specific release gate auto-sync metadata', function (): void 'auto_sync' => true, ]); + $fullStackGate = $normalizeGate->invoke($manager, [ + 'channel_slug' => 'stable', + ], ['slug' => 'stable']); + + expect($fullStackGate['apps'])->toBe(['frontend', 'api']); expect($appMatches->invoke($manager, ['app' => 'api'], 'api'))->toBeTrue(); expect($appMatches->invoke($manager, ['apps' => ['frontend', 'api']], 'api'))->toBeTrue(); expect($appMatches->invoke($manager, [], 'frontend'))->toBeTrue(); @@ -899,6 +905,7 @@ it('defines release manager schema, routes, permissions, and system-status integ expect($manager)->toContain('createBundle'); expect($manager)->toContain('deployBundle'); expect($manager)->toContain('promoteBundle'); + expect($manager)->toContain('if ($apiVersionId !== null)'); expect($manager)->toContain('setChannelBundle'); expect($manager)->toContain('searchAssignmentSubjects'); expect($manager)->toContain('publicAssignmentSubjectSuggestion'); @@ -975,6 +982,7 @@ it('defines release manager schema, routes, permissions, and system-status integ expect($manager)->toContain('channelSlugForRoute'); expect($manager)->toContain('syncChannel'); expect($manager)->toContain('runReleaseTest'); + expect($manager)->toContain("'apps' => \$this->releaseTestAppsFromInput(\$input)"); expect($manager)->toContain('releaseTestAppsFromInput'); expect($manager)->toContain('release_operation_runs'); expect($manager)->toContain('active_channel_app_key');