Files
dockhand-upstream/tests/parse-registry-url.test.ts
T

118 lines
4.1 KiB
TypeScript

/**
* parseRegistryUrl Unit Tests
*
* Tests the registry URL parsing logic used by loginToRegistries() during stack deploys.
* Covers bare hostnames (ghcr.io), full URLs (https://ghcr.io), ports, and paths.
*
* The function is inlined here (mirrors docker.ts) because importing docker.ts triggers
* the full DB module graph. Same pattern as notifications.test.ts.
*
* Run with: bun test tests/unit/parse-registry-url.test.ts
*/
import { describe, test, expect } from 'bun:test';
// Mirrors parseRegistryUrl() from src/lib/server/docker.ts
function parseRegistryUrl(url: string): { host: string; path: string; fullRegistry: string; protocol: string } {
const protocol = url.startsWith('http://') ? 'http' : 'https';
const withoutProtocol = url.replace(/^https?:\/\//, '');
const trimmed = withoutProtocol.replace(/\/$/, '');
const slashIndex = trimmed.indexOf('/');
if (slashIndex === -1) {
return { host: trimmed, path: '', fullRegistry: trimmed, protocol };
}
const host = trimmed.substring(0, slashIndex);
const path = trimmed.substring(slashIndex);
return { host, path, fullRegistry: trimmed, protocol };
}
describe('parseRegistryUrl', () => {
test('bare hostname without protocol', () => {
const result = parseRegistryUrl('ghcr.io');
expect(result.host).toBe('ghcr.io');
expect(result.protocol).toBe('https');
expect(result.path).toBe('');
});
test('hostname with https protocol', () => {
const result = parseRegistryUrl('https://ghcr.io');
expect(result.host).toBe('ghcr.io');
expect(result.protocol).toBe('https');
expect(result.path).toBe('');
});
test('hostname with http protocol', () => {
const result = parseRegistryUrl('http://registry.local:5000');
expect(result.host).toBe('registry.local:5000');
expect(result.protocol).toBe('http');
expect(result.path).toBe('');
});
test('hostname with port and no protocol', () => {
const result = parseRegistryUrl('registry.local:5000');
expect(result.host).toBe('registry.local:5000');
expect(result.protocol).toBe('https');
});
test('hostname with org path', () => {
const result = parseRegistryUrl('ghcr.io/myorg');
expect(result.host).toBe('ghcr.io');
expect(result.path).toBe('/myorg');
expect(result.fullRegistry).toBe('ghcr.io/myorg');
});
test('full URL with org path', () => {
const result = parseRegistryUrl('https://ghcr.io/myorg');
expect(result.host).toBe('ghcr.io');
expect(result.path).toBe('/myorg');
});
test('trailing slash is stripped', () => {
const result = parseRegistryUrl('ghcr.io/');
expect(result.host).toBe('ghcr.io');
expect(result.path).toBe('');
});
test('Docker Hub registry', () => {
const result = parseRegistryUrl('https://index.docker.io/v1/');
expect(result.host).toBe('index.docker.io');
expect(result.path).toBe('/v1');
});
test('private registry with port and path', () => {
const result = parseRegistryUrl('http://registry.example.com:5000/team');
expect(result.host).toBe('registry.example.com:5000');
expect(result.path).toBe('/team');
expect(result.protocol).toBe('http');
});
});
describe('loginToRegistries host extraction (issue #397)', () => {
// These test the specific scenario from issue #397:
// loginToRegistries() used `new URL(reg.url)` which throws on bare hostnames.
// Now it uses parseRegistryUrl() which handles all formats.
test('bare hostname produces valid docker login host', () => {
const { host } = parseRegistryUrl('ghcr.io');
expect(host).toBe('ghcr.io');
});
test('https URL produces same host as bare hostname', () => {
const bare = parseRegistryUrl('ghcr.io');
const full = parseRegistryUrl('https://ghcr.io');
expect(bare.host).toBe(full.host);
});
test('local registry with port produces host:port', () => {
const { host } = parseRegistryUrl('registry.local:5000');
expect(host).toBe('registry.local:5000');
});
test('new URL() would throw on bare hostname (the old bug)', () => {
// This confirms the original bug: new URL('ghcr.io') throws
expect(() => new URL('ghcr.io')).toThrow();
// But parseRegistryUrl handles it fine
expect(() => parseRegistryUrl('ghcr.io')).not.toThrow();
});
});