Files
electric-cherry/tests/unit/komodo-health.test.ts
T

107 lines
4.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { komodoInspectHealthy } from '../../src/lib/server/integrations/komodo.js';
vi.mock('../../src/lib/server/config.js', () => ({
config: {
komodo: { address: 'http://komodo.test', apiKey: 'test', apiSecret: 'test' },
httpTimeoutMs: 1000
},
komodoConfigured: () => true
}));
vi.mock('../../src/lib/server/fleet/servers.js', () => ({
getServer: () => ({ komodo_type: 'deployment', komodo_name: 'saved-resource' })
}));
const fetchMock = vi.fn<typeof fetch>();
beforeEach(() => {
fetchMock.mockReset();
vi.stubGlobal('fetch', fetchMock);
});
afterEach(() => {
vi.unstubAllGlobals();
vi.useRealTimers();
});
describe('Komodo health endpoint fallback', () => {
it('limits an unreachable probe to one timeout and preserves its diagnosis', async () => {
vi.useFakeTimers();
fetchMock.mockImplementation((_url, options) => new Promise((_resolve, reject) => {
options?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')));
}));
const result = komodoInspectHealthy('server');
await vi.advanceTimersByTimeAsync(1000);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(await result).toMatchObject({
ok: false,
reason: 'timed out after 1000ms dialing http://komodo.test/read/GetDeploymentContainer'
});
});
it('preserves the network cause without probing alternate endpoints', async () => {
fetchMock.mockRejectedValue(new TypeError('fetch failed', {
cause: Object.assign(new Error('connection refused'), { code: 'ECONNREFUSED' })
}));
expect(await komodoInspectHealthy('server')).toMatchObject({
ok: false,
reason: expect.stringContaining('ECONNREFUSED: connection refused')
});
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it.each([400, 401, 403, 429, 500, 502, 503])('stops on HTTP %i without hiding the response', async status => {
fetchMock.mockResolvedValue(new Response('request failed', { status }));
expect(await komodoInspectHealthy('server')).toEqual({
ok: false, reason: `HTTP ${status}`, snippet: 'request failed'
});
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it.each([
[404, 'Not Found'],
[400, 'unknown variant `GetDeploymentContainer`'],
[405, 'unsupported method'],
[500, 'unknown method'],
[501, 'method not found']
])('tries a compatible endpoint after HTTP %i: %s', async (status, message) => {
fetchMock
.mockResolvedValueOnce(new Response(message as string, { status: status as number }))
.mockResolvedValueOnce(Response.json({ State: { Status: 'running' } }));
expect(await komodoInspectHealthy('server')).toEqual({ ok: true, state: 'running' });
expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
'http://komodo.test/read/GetDeploymentContainer',
'http://komodo.test/read/InspectDeploymentContainer'
]);
});
it('continues past successful responses with an unknown shape', async () => {
fetchMock
.mockResolvedValueOnce(Response.json({ config: { image: 'server' } }))
.mockResolvedValueOnce(Response.json({ State: { Status: 'running' } }));
expect(await komodoInspectHealthy('server')).toEqual({ ok: true, state: 'running' });
expect(fetchMock).toHaveBeenCalledTimes(2);
});
it('does not overwrite an unhealthy container state with a later response', async () => {
fetchMock.mockResolvedValueOnce(Response.json({ State: { Status: 'exited' } }));
expect(await komodoInspectHealthy('server')).toMatchObject({ ok: false, state: 'exited', reason: 'container state: exited' });
expect(fetchMock).toHaveBeenCalledTimes(1);
});
it('preserves malformed response diagnostics', async () => {
fetchMock.mockResolvedValueOnce(new Response('not json'));
expect(await komodoInspectHealthy('server')).toMatchObject({
ok: false, reason: expect.stringContaining('unparseable json:'), snippet: 'not json'
});
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});