Add tests for GitHub commit timestamp handling and runtime channel selection in ReleaseManager. Extend release URL normalization and runtime channel methods, and introduce assignment subject searches.
This commit is contained in:
@@ -46,6 +46,32 @@ it('normalizes GitHub repository identifiers for private repository access check
|
||||
expect(release_manager::normalizeGithubRepositoryName('not a repository'))->toBe('');
|
||||
});
|
||||
|
||||
it('keeps GitHub commit timestamps in public release manager commit payloads', function (): void {
|
||||
$manager = new release_manager();
|
||||
$method = new ReflectionMethod(release_manager::class, 'publicGithubCommit');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$commit = $method->invoke($manager, [
|
||||
'sha' => 'feedface00000000000000000000000000000000',
|
||||
'html_url' => 'https://github.com/truckwash/backend-php/commit/feedface',
|
||||
'commit' => [
|
||||
'message' => "Deploy release bundle\n\nBody is intentionally omitted from option labels.",
|
||||
'author' => [
|
||||
'name' => 'Release Bot',
|
||||
'date' => '2026-05-19T08:10:00Z',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
expect($commit)->toMatchArray([
|
||||
'sha' => 'feedface00000000000000000000000000000000',
|
||||
'short_sha' => 'feedface0000',
|
||||
'message' => 'Deploy release bundle',
|
||||
'author_name' => 'Release Bot',
|
||||
'authored_at' => '2026-05-19T08:10:00Z',
|
||||
]);
|
||||
});
|
||||
|
||||
it('summarizes failed deployments and blocks promotion until a deployment succeeds', function (): void {
|
||||
$summary = release_manager::deploymentFailureSummary(
|
||||
new RuntimeException('Coolify API request failed: HTTP 404'),
|
||||
@@ -381,6 +407,8 @@ it('defines release manager schema, routes, permissions, and system-status integ
|
||||
expect($route)->toContain('/superuser/releases/github/commits');
|
||||
expect($route)->toContain('/superuser/releases/github/test');
|
||||
expect($route)->toContain('/superuser/releases/channels');
|
||||
expect($route)->toContain('/superuser/releases/channels/{id}/bundle');
|
||||
expect($route)->toContain('/superuser/releases/assignment-subjects');
|
||||
expect($route)->toContain('/superuser/releases/assignments');
|
||||
expect($route)->toContain('/superuser/releases/service-sets');
|
||||
expect($route)->toContain("\$this->delete('/superuser/releases/service-sets/{id}'");
|
||||
@@ -424,6 +452,12 @@ 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('setChannelBundle');
|
||||
expect($manager)->toContain('searchAssignmentSubjects');
|
||||
expect($manager)->toContain('publicAssignmentSubjectSuggestion');
|
||||
expect($manager)->toContain('available_channels');
|
||||
expect($manager)->toContain('chooseRuntimeChannel');
|
||||
expect($manager)->toContain('requestedRuntimeChannelSlug');
|
||||
expect($manager)->toContain("status = 'superseded'");
|
||||
expect($manager)->toContain('serviceSetIsActive');
|
||||
expect($manager)->toContain('bundleIsActive');
|
||||
@@ -469,6 +503,7 @@ it('defines release manager schema, routes, permissions, and system-status integ
|
||||
expect($manager)->toContain('load_balancer_domains');
|
||||
expect($manager)->toContain('appendDomainSuggestion');
|
||||
expect($manager)->toContain('Coolify SSL requires a DNS domain routed to the load balancer.');
|
||||
expect($manager)->toContain('releaseRuntimeUrls');
|
||||
expect($manager)->toContain('coolify_services');
|
||||
expect($manager)->toContain('coolify_enable_ssl');
|
||||
expect($manager)->toContain('createService');
|
||||
@@ -484,3 +519,145 @@ it('defines release manager schema, routes, permissions, and system-status integ
|
||||
expect($index)->toContain('X-Release-Trace');
|
||||
expect($manager)->not->toContain('X-Release-Channel');
|
||||
});
|
||||
|
||||
it('requires non-default release channel runtime URLs and preserves load balancer paths', function (): void {
|
||||
$manager = new release_manager();
|
||||
$availability = new ReflectionMethod(release_manager::class, 'channelAvailability');
|
||||
$availability->setAccessible(true);
|
||||
$runtimeUrls = new ReflectionMethod(release_manager::class, 'releaseRuntimeUrls');
|
||||
$runtimeUrls->setAccessible(true);
|
||||
$publicUrl = new ReflectionMethod(release_manager::class, 'releaseCoolifyPublicUrl');
|
||||
$publicUrl->setAccessible(true);
|
||||
|
||||
expect($availability->invoke($manager, [
|
||||
'id' => 1,
|
||||
'slug' => 'stable',
|
||||
'default_channel' => 1,
|
||||
'frontend_base_url' => null,
|
||||
'api_base_url' => null,
|
||||
]))->toMatchArray([
|
||||
'configured' => true,
|
||||
'missing' => [],
|
||||
'status' => 'ready',
|
||||
]);
|
||||
|
||||
expect($runtimeUrls->invoke($manager, [
|
||||
'id' => 2,
|
||||
'slug' => 'canary',
|
||||
'default_channel' => 0,
|
||||
'frontend_base_url' => null,
|
||||
'api_base_url' => null,
|
||||
], [
|
||||
'frontend' => ['deployed_url' => 'https://api-v2.truckwash.io/canary/frontend/health'],
|
||||
'api' => ['deployed_url' => 'https://api-v2.truckwash.io/canary/api/ping'],
|
||||
]))->toBe([
|
||||
'frontend_base_url' => 'https://api-v2.truckwash.io/canary/frontend',
|
||||
'api_base_url' => 'https://api-v2.truckwash.io/canary/api',
|
||||
]);
|
||||
|
||||
expect($publicUrl->invoke($manager, ['app' => 'frontend'], [
|
||||
'coolify_public_url' => 'https://api-v2.truckwash.io/canary/frontend',
|
||||
'coolify_enable_ssl' => true,
|
||||
]))->toBe('https://api-v2.truckwash.io/canary/frontend');
|
||||
|
||||
$source = file(app_path('classes/release_manager.php'));
|
||||
$methodSource = implode('', array_slice(
|
||||
$source,
|
||||
$availability->getStartLine() - 1,
|
||||
$availability->getEndLine() - $availability->getStartLine() + 1
|
||||
));
|
||||
|
||||
expect($methodSource)->toContain('frontend_base_url');
|
||||
expect($methodSource)->toContain('api_base_url');
|
||||
expect($methodSource)->toContain('release_bundle');
|
||||
expect($methodSource)->toContain('frontend_version');
|
||||
expect($methodSource)->toContain('api_version');
|
||||
});
|
||||
|
||||
it('exposes release version git commit metadata for runtime channel cards', function (): void {
|
||||
$manager = new release_manager();
|
||||
$publicVersion = new ReflectionMethod(release_manager::class, 'publicVersion');
|
||||
$publicVersion->setAccessible(true);
|
||||
|
||||
$version = $publicVersion->invoke($manager, [
|
||||
'id' => 12,
|
||||
'app' => 'frontend',
|
||||
'repository' => 'truckwash/front-end-vue',
|
||||
'branch' => 'release/canary',
|
||||
'commit_sha' => 'c0ffee0000001111222233334444555566667777',
|
||||
'tag' => null,
|
||||
'version_label' => 'frontend-canary',
|
||||
'build_url' => null,
|
||||
'artifact_url' => null,
|
||||
'deployed_url' => null,
|
||||
'status' => 'active',
|
||||
'metadata_json' => json_encode([
|
||||
'github_access' => [
|
||||
'commit' => [
|
||||
'sha' => 'c0ffee0000001111222233334444555566667777',
|
||||
'authored_at' => '2026-05-19T10:15:00Z',
|
||||
],
|
||||
],
|
||||
]),
|
||||
'created_at' => '2026-05-19 10:10:00',
|
||||
'deployed_at' => '2026-05-19 10:20:00',
|
||||
]);
|
||||
|
||||
expect($version['commit_sha'])->toBe('c0ffee0000001111222233334444555566667777');
|
||||
expect($version['commit']['sha'])->toBe('c0ffee0000001111222233334444555566667777');
|
||||
expect($version['commit_authored_at'])->toBe('2026-05-19T10:15:00Z');
|
||||
expect($version['deployed_at'])->toBe('2026-05-19 10:20:00');
|
||||
});
|
||||
|
||||
it('chooses a requested runtime channel only when it is available to the principal', function (): void {
|
||||
$manager = new release_manager();
|
||||
$choose = new ReflectionMethod(release_manager::class, 'chooseRuntimeChannel');
|
||||
$choose->setAccessible(true);
|
||||
|
||||
$stable = [
|
||||
'id' => 1,
|
||||
'slug' => 'stable',
|
||||
'name' => 'Stable',
|
||||
'default_channel' => 1,
|
||||
];
|
||||
$canary = [
|
||||
'id' => 2,
|
||||
'slug' => 'canary',
|
||||
'name' => 'Canary',
|
||||
'default_channel' => 0,
|
||||
];
|
||||
|
||||
expect($choose->invoke($manager, $stable, [$stable, $canary], 'canary'))->toBe($canary);
|
||||
expect($choose->invoke($manager, $stable, [$stable, $canary], 'unknown'))->toBe($stable);
|
||||
expect($choose->invoke($manager, $canary, [$stable, $canary], ''))->toBe($canary);
|
||||
});
|
||||
|
||||
it('normalizes release assignment subject suggestions without leaking private fields', function (): void {
|
||||
$suggestion = release_manager::publicAssignmentSubjectSuggestion([
|
||||
'subject_type' => 'USER',
|
||||
'subject_id' => 42,
|
||||
'title' => ' Dispatcher ',
|
||||
'description' => 'Customer #424242 / dispatcher@example.test',
|
||||
'icon' => 'fas fa-user',
|
||||
'source' => 'users',
|
||||
'password' => 'secret',
|
||||
'two_factor_secret' => 'private',
|
||||
]);
|
||||
|
||||
expect($suggestion)->toBe([
|
||||
'subject_type' => 'user',
|
||||
'subject_id' => '42',
|
||||
'label' => 'Dispatcher - Customer #424242 / dispatcher@example.test',
|
||||
'title' => 'Dispatcher',
|
||||
'description' => 'Customer #424242 / dispatcher@example.test',
|
||||
'icon' => 'fas fa-user',
|
||||
'source' => 'users',
|
||||
]);
|
||||
expect(array_keys($suggestion))->not->toContain('password');
|
||||
expect(array_keys($suggestion))->not->toContain('two_factor_secret');
|
||||
expect(release_manager::publicAssignmentSubjectSuggestion([
|
||||
'subject_type' => 'invalid',
|
||||
'subject_id' => 42,
|
||||
'title' => 'Invalid',
|
||||
]))->toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user