Files
electric-cherry/tests/unit/komodo.test.ts
T
joshandClaude Opus 4.7 15ea2ddfce extractUdpPort: handle Komodo's newline-string ports form
Real Komodo GetDeployment responses render config.ports as a single
newline-delimited string ("28963:28963/udp\n28963:28963/tcp\n"), not as
the array shapes we already handled. The walker only descended into
arrays and objects, so the string was skipped entirely and every Komodo
probe on a real deployment fell through to no-udp-in-response.

When the walker hits a value under a ports-keyed field that's a string,
split it on \r\n , ; and treat the resulting parts as a preferred string
array — the existing "host:container/udp" and "container/udp" regexes
handle each part.

Fixture pulled verbatim from the t5-nacht-1 response the fleet UI
surfaced. Also covers single-line, host-network (no host prefix), and
TCP-only-shouldn't-match cases.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-12 10:59:07 -04:00

122 lines
4.8 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { extractUdpPort } from '../../src/lib/server/integrations/komodo.js';
describe('extractUdpPort', () => {
it('returns the published port from docker-compose-style object entries', () => {
const payload = {
state: {
container: {
ports: [
{ protocol: 'tcp', published: 3000, target: 3000 },
{ protocol: 'udp', published: 4976, target: 28960 }
]
}
}
};
expect(extractUdpPort(payload)).toBe(4976);
});
it('parses docker-run-style "host:container/proto" strings', () => {
const payload = { ports: ['3074:3074/tcp', '4977:28960/udp'] };
expect(extractUdpPort(payload)).toBe(4977);
});
it('picks the UDP entry even when TCP appears first', () => {
const payload = {
deeply: {
nested: {
ports: [
{ Protocol: 'tcp', PublishedPort: 80 },
{ Protocol: 'udp', PublishedPort: 5555 }
]
}
}
};
expect(extractUdpPort(payload)).toBe(5555);
});
it('returns null when no UDP mapping exists', () => {
const payload = { ports: [{ protocol: 'tcp', published: 3000 }] };
expect(extractUdpPort(payload)).toBeNull();
});
it('survives cyclic references', () => {
const cyc: Record<string, unknown> = {};
cyc.self = cyc;
expect(extractUdpPort(cyc)).toBeNull();
});
it('recognizes HostPort / hostPort / host_port on object entries', () => {
expect(extractUdpPort({ ports: [{ protocol: 'udp', HostPort: 28962 }] })).toBe(28962);
expect(extractUdpPort({ ports: [{ protocol: 'udp', hostPort: 28963 }] })).toBe(28963);
expect(extractUdpPort({ ports: [{ protocol: 'udp', host_port: 28964 }] })).toBe(28964);
});
it('recognizes `type` alongside `protocol` as the protocol field', () => {
expect(extractUdpPort({ ports: [{ type: 'UDP', published: 28965 }] })).toBe(28965);
});
it('parses host-network "container/udp" strings', () => {
expect(extractUdpPort({ ports: ['28966/udp'] })).toBe(28966);
});
it('handles docker-inspect container-keyed PortBindings shape', () => {
const payload = {
NetworkSettings: {
Ports: {
'28960/udp': [{ HostIp: '0.0.0.0', HostPort: '28967' }],
'3074/tcp': [{ HostIp: '0.0.0.0', HostPort: '3074' }]
}
}
};
expect(extractUdpPort(payload)).toBe(28967);
});
it('prefers `ports`-keyed arrays over unrelated arrays discovered deeper', () => {
// The unrelated array happens to contain a matching entry — the walker
// must still prefer the real `ports` list.
const payload = {
labels: [{ protocol: 'udp', published: 9999 }],
state: { container: { ports: [{ protocol: 'udp', published: 28968 }] } }
};
expect(extractUdpPort(payload)).toBe(28968);
});
it('splits Komodo`s newline-delimited string form of `config.ports`', () => {
// Verbatim shape from a real Komodo GetDeployment response — the
// container publishes UDP 28963 and TCP 28963 as a single string.
const payload = {
name: 't5-nacht-1',
config: {
image: { type: 'Image', params: { image: 'ghcr.io/ayymoss/plutainer:latest' } },
network: 'komodo_default',
ports: '28963:28963/udp\n28963:28963/tcp\n'
}
};
expect(extractUdpPort(payload)).toBe(28963);
});
it('handles a single-line string-form ports value', () => {
expect(extractUdpPort({ config: { ports: '28969:28960/udp' } })).toBe(28969);
});
it('handles a host-network string-form ports value (no host prefix)', () => {
expect(extractUdpPort({ config: { ports: '28970/udp\n' } })).toBe(28970);
});
it('ignores TCP-only string-form ports entries', () => {
expect(extractUdpPort({ config: { ports: '80:80/tcp\n443:443/tcp' } })).toBeNull();
});
});
describe('komodoResolvePortDetailed', () => {
it('is importable and has the discriminated-union shape at the type level', async () => {
// Runtime import shim just to prove it's exported. Behavior is
// covered by the fixture-based extractUdpPort tests above plus manual
// end-to-end verification against a live Komodo.
const mod = await import('../../src/lib/server/integrations/komodo.js');
expect(typeof mod.komodoResolvePortDetailed).toBe('function');
expect(typeof mod.komodoResolveGamePort).toBe('function');
});
});