103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import { describe, it, expect, vi, beforeAll, beforeEach } from 'vitest';
|
|
import { spawn } from 'child_process';
|
|
import { EventEmitter } from 'events';
|
|
import type { ChildProcess } from 'child_process';
|
|
|
|
vi.mock('child_process', () => ({
|
|
spawn: vi.fn(),
|
|
}));
|
|
|
|
class MockChildProcess extends EventEmitter {
|
|
exitCode: number | null = null;
|
|
killed = false;
|
|
unref = vi.fn();
|
|
kill = vi.fn((signal?: NodeJS.Signals) => {
|
|
this.killed = true;
|
|
this.emit('exit', signal === 'SIGKILL' ? 137 : 0, signal ?? null);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
const mockSpawn = vi.mocked(spawn);
|
|
|
|
describe('gateway discovery', () => {
|
|
let startGatewayDiscovery: typeof import('./discovery.js').startGatewayDiscovery;
|
|
|
|
beforeAll(async () => {
|
|
const mod = await import('./discovery.js');
|
|
startGatewayDiscovery = mod.startGatewayDiscovery;
|
|
});
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
it('starts avahi publisher with txt records', async () => {
|
|
const child = new MockChildProcess();
|
|
mockSpawn.mockReturnValueOnce(child as unknown as ChildProcess);
|
|
setTimeout(() => child.emit('spawn'), 0);
|
|
|
|
const handle = await startGatewayDiscovery({
|
|
serviceName: 'flynn-gateway',
|
|
serviceType: '_flynn._tcp',
|
|
port: 18800,
|
|
txtRecord: { instance: 'pid-123', version: '0.1.0' },
|
|
});
|
|
|
|
expect(mockSpawn).toHaveBeenCalledWith('avahi-publish-service', [
|
|
'flynn-gateway',
|
|
'_flynn._tcp',
|
|
'18800',
|
|
'instance=pid-123',
|
|
'version=0.1.0',
|
|
], { stdio: 'ignore' });
|
|
expect(child.unref).toHaveBeenCalledOnce();
|
|
|
|
await handle.stop();
|
|
expect(child.kill).toHaveBeenCalledWith('SIGTERM');
|
|
});
|
|
|
|
it('falls back to dns-sd when avahi is unavailable', async () => {
|
|
const avahiChild = new MockChildProcess();
|
|
const dnsChild = new MockChildProcess();
|
|
mockSpawn.mockReturnValueOnce(avahiChild as unknown as ChildProcess);
|
|
mockSpawn.mockReturnValueOnce(dnsChild as unknown as ChildProcess);
|
|
|
|
setTimeout(() => avahiChild.emit('error', new Error('ENOENT')), 0);
|
|
setTimeout(() => dnsChild.emit('spawn'), 0);
|
|
|
|
const handle = await startGatewayDiscovery({
|
|
serviceName: 'flynn-gateway',
|
|
serviceType: '_flynn._tcp',
|
|
port: 18800,
|
|
});
|
|
|
|
expect(mockSpawn).toHaveBeenNthCalledWith(2, 'dns-sd', [
|
|
'-R',
|
|
'flynn-gateway',
|
|
'_flynn._tcp',
|
|
'local',
|
|
'18800',
|
|
], { stdio: 'ignore' });
|
|
|
|
await handle.stop();
|
|
expect(dnsChild.kill).toHaveBeenCalledWith('SIGTERM');
|
|
});
|
|
|
|
it('throws when no supported advertiser command is available', async () => {
|
|
const avahiChild = new MockChildProcess();
|
|
const dnsChild = new MockChildProcess();
|
|
mockSpawn.mockReturnValueOnce(avahiChild as unknown as ChildProcess);
|
|
mockSpawn.mockReturnValueOnce(dnsChild as unknown as ChildProcess);
|
|
|
|
setTimeout(() => avahiChild.emit('error', new Error('ENOENT')), 0);
|
|
setTimeout(() => dnsChild.emit('error', new Error('ENOENT')), 0);
|
|
|
|
await expect(startGatewayDiscovery({
|
|
serviceName: 'flynn-gateway',
|
|
serviceType: '_flynn._tcp',
|
|
port: 18800,
|
|
})).rejects.toThrow(/Failed to start mDNS advertiser/);
|
|
});
|
|
});
|