107 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
});
|