Files
dockhand-upstream/tests/pangolin-urls.test.ts
T

177 lines
6.6 KiB
TypeScript

import { describe, expect, test } from 'bun:test';
import { extractPangolinUrls } from '../src/lib/utils/pangolin-urls';
/**
* Scheme is decided by the `ssl` label ONLY (verified against Pangolin's source):
* ssl=true -> https, ssl=false -> http.
* ssl absent -> scope default: public = https, private = http.
* The `protocol`/`mode` label is the resource TYPE, never the scheme, and is ignored.
*/
describe('extractPangolinUrls', () => {
test('public resource, ssl=false -> http', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.grafana.name': 'Grafana',
'pangolin.public-resources.grafana.full-domain': 'grafana.example.com',
'pangolin.public-resources.grafana.ssl': 'false',
'pangolin.public-resources.grafana.targets[0].method': 'http',
'pangolin.public-resources.grafana.targets[0].port': '3000'
});
expect(out).toEqual([
{ url: 'http://grafana.example.com', resource: 'grafana', scope: 'public', displayName: 'Grafana' }
]);
});
test('public resource, ssl=true -> https', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.app.full-domain': 'app.example.com',
'pangolin.public-resources.app.ssl': 'true'
});
expect(out[0].url).toBe('https://app.example.com');
});
test('public resource, ssl ABSENT -> https (public default)', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.app.full-domain': 'app.example.com'
});
expect(out).toEqual([
{ url: 'https://app.example.com', resource: 'app', scope: 'public', displayName: undefined }
]);
});
test('the reporter case (#1331): protocol=http but ssl absent -> https, protocol ignored', () => {
// protocol is the resource TYPE, not the scheme. A public resource with a
// full-domain and no ssl label is https regardless of protocol=http.
const out = extractPangolinUrls({
'pangolin.public-resources.r.protocol': 'http',
'pangolin.public-resources.r.full-domain': 'sub.domain.tld'
});
expect(out[0].url).toBe('https://sub.domain.tld');
});
test('protocol=http with an explicit ssl=false -> http (ssl wins, not protocol)', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.r.protocol': 'http',
'pangolin.public-resources.r.ssl': 'false',
'pangolin.public-resources.r.full-domain': 'r.example.com'
});
expect(out[0].url).toBe('http://r.example.com');
});
test('private resource, ssl ABSENT -> http (private default)', () => {
const out = extractPangolinUrls({
'pangolin.private-resources.internal.full-domain': 'internal.lan'
});
expect(out).toEqual([
{ url: 'http://internal.lan', resource: 'internal', scope: 'private', displayName: undefined }
]);
});
test('private resource, ssl=true -> https', () => {
const out = extractPangolinUrls({
'pangolin.private-resources.internal.full-domain': 'internal.lan',
'pangolin.private-resources.internal.ssl': 'true'
});
expect(out[0].url).toBe('https://internal.lan');
});
test('unparseable ssl value falls back to the scope default', () => {
// public default = https
const pub = extractPangolinUrls({
'pangolin.public-resources.r.full-domain': 'r.example.com',
'pangolin.public-resources.r.ssl': 'gibberish'
});
expect(pub[0].url).toBe('https://r.example.com');
// private default = http
const priv = extractPangolinUrls({
'pangolin.private-resources.r.full-domain': 'r.lan',
'pangolin.private-resources.r.ssl': 'gibberish'
});
expect(priv[0].url).toBe('http://r.lan');
});
test('ssl value is case-insensitive', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.r.full-domain': 'r.example.com',
'pangolin.public-resources.r.ssl': ' FALSE '
});
expect(out[0].url).toBe('http://r.example.com');
});
test('multiple resources on the same container yield multiple URLs', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.a.full-domain': 'a.example.com',
'pangolin.public-resources.b.full-domain': 'b.example.com',
'pangolin.public-resources.b.ssl': 'false'
});
expect(out).toHaveLength(2);
const urls = out.map((p) => p.url).sort();
expect(urls).toEqual(['http://b.example.com', 'https://a.example.com']);
});
test('same resource name under both scopes is treated as two resources', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.dual.full-domain': 'pub.example.com',
'pangolin.private-resources.dual.full-domain': 'priv.lan'
});
expect(out).toHaveLength(2);
const scopes = out.map((p) => p.scope).sort();
expect(scopes).toEqual(['private', 'public']);
// And each gets its scope default scheme.
expect(out.find((p) => p.scope === 'public')!.url).toBe('https://pub.example.com');
expect(out.find((p) => p.scope === 'private')!.url).toBe('http://priv.lan');
});
test('skips resources with no full-domain', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.no-domain.name': 'Incomplete',
'pangolin.public-resources.no-domain.ssl': 'false'
});
expect(out).toEqual([]);
});
test('dedupes identical URLs across resources', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.x.full-domain': 'same.example.com',
'pangolin.public-resources.y.full-domain': 'same.example.com'
});
expect(out).toHaveLength(1);
});
test('ignores unrelated labels including legacy pangolin.proxy-resources.*', () => {
const out = extractPangolinUrls({
'org.opencontainers.image.source': 'https://github.com/foo/bar',
'dockhand.url': 'https://example.com',
'traefik.http.routers.x.rule': 'Host(`x.example.com`)',
'pangolin.proxy-resources.r.full-domain': 'should-be-ignored.example.com',
'pangolin.public-resources.r.full-domain': 'r.example.com'
});
expect(out).toEqual([
{ url: 'https://r.example.com', resource: 'r', scope: 'public', displayName: undefined }
]);
});
test('whitespace in the full-domain is trimmed', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.s.full-domain': ' spaced.example.com ',
'pangolin.public-resources.s.ssl': 'false'
});
expect(out[0].url).toBe('http://spaced.example.com');
});
test('null / empty inputs return empty array', () => {
expect(extractPangolinUrls(null)).toEqual([]);
expect(extractPangolinUrls(undefined)).toEqual([]);
expect(extractPangolinUrls({})).toEqual([]);
});
test('targets[N].* labels do not introduce extra resources', () => {
const out = extractPangolinUrls({
'pangolin.public-resources.grafana.full-domain': 'grafana.example.com',
'pangolin.public-resources.grafana.targets[0].method': 'http',
'pangolin.public-resources.grafana.targets[0].port': '3000'
});
expect(out).toHaveLength(1);
expect(out[0].resource).toBe('grafana');
});
});