feat(gateway): add observability sources, series, and service log RPCs
This commit is contained in:
@@ -12,6 +12,7 @@ import { createConfigHandlers, redactConfig } from './config.js';
|
||||
import { createPairingHandlers } from './pairing.js';
|
||||
import type { LocalBackendStatus, LocalBackendControlResult } from './localBackends.js';
|
||||
import type { DockerDependencyStatus, DockerDependencyControlResult } from './dockerDependencies.js';
|
||||
import type { ObservabilitySource, ObservabilitySeriesSnapshot, ServiceLogSnapshot } from './observability.js';
|
||||
import { PairingManager } from '../../channels/pairing.js';
|
||||
import { LaneQueue } from '../lane-queue.js';
|
||||
import { CanvasStore } from '../canvas-store.js';
|
||||
@@ -375,6 +376,125 @@ describe('system handlers', () => {
|
||||
expect(getPath(result.result, 'action')).toBe('restart');
|
||||
});
|
||||
|
||||
it('system.observabilitySources returns empty list when callback is not provided', async () => {
|
||||
const req: GatewayRequest = { id: 48, method: 'system.observabilitySources' };
|
||||
const result = await handlers['system.observabilitySources'](req) as GatewayResponse;
|
||||
expect(getPath(result.result, 'sources')).toEqual([]);
|
||||
});
|
||||
|
||||
it('system.observabilitySources returns source list from callback', async () => {
|
||||
const getObservabilitySources = vi.fn(async (): Promise<ObservabilitySource[]> => ([
|
||||
{
|
||||
id: 'systemd:flynn',
|
||||
name: 'Flynn daemon',
|
||||
kind: 'systemd_system',
|
||||
runtime: 'systemd_system',
|
||||
status: 'running',
|
||||
graphCapable: true,
|
||||
logCapable: true,
|
||||
},
|
||||
]));
|
||||
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getObservabilitySources,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = { id: 49, method: 'system.observabilitySources' };
|
||||
const result = await handlers['system.observabilitySources'](req) as GatewayResponse;
|
||||
expect(getObservabilitySources).toHaveBeenCalledTimes(1);
|
||||
expect(getPath(result.result, 'sources', '0', 'id')).toBe('systemd:flynn');
|
||||
});
|
||||
|
||||
it('system.observabilitySeries validates sourceIds parameter', async () => {
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getObservabilitySeries: vi.fn(),
|
||||
});
|
||||
|
||||
const result = await handlers['system.observabilitySeries']({
|
||||
id: 50,
|
||||
method: 'system.observabilitySeries',
|
||||
params: { sourceIds: 'not-an-array' as unknown as string[] },
|
||||
}) as GatewayError;
|
||||
|
||||
expect(result.error.code).toBe(ErrorCode.InvalidRequest);
|
||||
});
|
||||
|
||||
it('system.observabilitySeries forwards query to callback', async () => {
|
||||
const snapshot: ObservabilitySeriesSnapshot = {
|
||||
generatedAt: 123,
|
||||
windowMinutes: 60,
|
||||
bucketSeconds: 30,
|
||||
series: [
|
||||
{
|
||||
sourceId: 'systemd:flynn',
|
||||
points: [{ ts: 100, stateCode: 3, healthCode: 2, errorCount: 0, restartCount: 1 }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const getObservabilitySeries = vi.fn(async () => snapshot);
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getObservabilitySeries,
|
||||
});
|
||||
|
||||
const req: GatewayRequest = {
|
||||
id: 51,
|
||||
method: 'system.observabilitySeries',
|
||||
params: { windowMinutes: 120, bucketSeconds: 60, sourceIds: ['systemd:flynn'] },
|
||||
};
|
||||
const result = await handlers['system.observabilitySeries'](req) as GatewayResponse;
|
||||
expect(getObservabilitySeries).toHaveBeenCalledWith({
|
||||
windowMinutes: 120,
|
||||
bucketSeconds: 60,
|
||||
sourceIds: ['systemd:flynn'],
|
||||
});
|
||||
expect(getPath(result.result, 'series', '0', 'points', '0', 'restartCount')).toBe(1);
|
||||
});
|
||||
|
||||
it('system.serviceLogs validates required sourceId', async () => {
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getServiceLogs: vi.fn(),
|
||||
});
|
||||
|
||||
const result = await handlers['system.serviceLogs']({
|
||||
id: 52,
|
||||
method: 'system.serviceLogs',
|
||||
params: { lines: 100 },
|
||||
}) as GatewayError;
|
||||
expect(result.error.code).toBe(ErrorCode.InvalidRequest);
|
||||
});
|
||||
|
||||
it('system.serviceLogs forwards request to callback', async () => {
|
||||
const snapshot: ServiceLogSnapshot = {
|
||||
sourceId: 'docker:whisper',
|
||||
fetchedAt: 123,
|
||||
redacted: false,
|
||||
truncated: false,
|
||||
lines: [{ ts: 100, level: 'warn', text: 'queue depth high' }],
|
||||
};
|
||||
const getServiceLogs = vi.fn(async (): Promise<ServiceLogSnapshot> => snapshot);
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getServiceLogs,
|
||||
});
|
||||
const req: GatewayRequest = {
|
||||
id: 53,
|
||||
method: 'system.serviceLogs',
|
||||
params: { sourceId: 'docker:whisper', lines: 50, sinceSeconds: 600 },
|
||||
};
|
||||
const result = await handlers['system.serviceLogs'](req) as GatewayResponse;
|
||||
expect(getServiceLogs).toHaveBeenCalledWith({
|
||||
sourceId: 'docker:whisper',
|
||||
lines: 50,
|
||||
sinceSeconds: 600,
|
||||
});
|
||||
expect(getPath(result.result, 'lines', '0', 'text')).toBe('queue depth high');
|
||||
});
|
||||
|
||||
it('system.presence returns empty result when getPresence is not provided', async () => {
|
||||
const req: GatewayRequest = { id: 4, method: 'system.presence' };
|
||||
const result = await handlers['system.presence'](req) as GatewayResponse;
|
||||
|
||||
Reference in New Issue
Block a user