Files
multi-simadmin/apps/api/src/application/connections/connection-probe.test.ts
T

96 lines
3.6 KiB
TypeScript

import Database from 'better-sqlite3';
import { afterEach, describe, expect, it } from 'vitest';
import { InstanceService } from '../instances/instance-service.js';
import { migrateDatabase } from '../../infrastructure/database/migrations.js';
import type { SecretStore } from '../../infrastructure/secrets/secret-store.js';
import type { TransportResponse } from '../../infrastructure/transport/safe-instance-transport.js';
import { ConnectionProbe } from './connection-probe.js';
class Store implements SecretStore {
async set(key: { instanceId: string; purpose: string; slot?: string }) {
return `keychain://multi-simadmin/${Buffer.from(JSON.stringify([key.instanceId, key.purpose, key.slot])).toString('base64url')}`;
}
async get() {
return undefined;
}
async delete() {
return false;
}
}
const dbs: Database.Database[] = [];
afterEach(() => {
for (const db of dbs.splice(0)) db.close();
});
const fixture = (response: TransportResponse) => {
const db = new Database(':memory:');
db.pragma('foreign_keys=ON');
migrateDatabase(db);
dbs.push(db);
const instances = new InstanceService({
db,
store: new Store(),
idFactory: () => 'instance-1',
now: () => new Date('2026-07-16T12:00:00.000Z'),
});
const transport = {
get: async (url: string) => {
expect(url).toBe('http://192.168.1.20:3000/api/health');
return response;
},
};
return {
db,
instances,
probe: new ConnectionProbe({
db,
instances,
transport,
now: () => new Date('2026-07-16T12:00:00.000Z'),
}),
};
};
describe('ConnectionProbe', () => {
it('uses the unauthenticated health endpoint and records a reachable unauthenticated instance', async () => {
const { instances, probe } = fixture({ status: 200, headers: {}, body: '{}' });
await instances.create({ name: 'LAN', origin: 'http://192.168.1.20:3000' });
await expect(probe.test('instance-1')).resolves.toEqual({
instanceId: 'instance-1',
authenticated: true,
checkedAt: '2026-07-16T12:00:00.000Z',
});
});
it('treats 401 as reachable but not authenticated rather than offline', async () => {
const { instances, probe } = fixture({ status: 401, headers: {}, body: '{}' });
await instances.create({ name: 'LAN', origin: 'http://192.168.1.20:3000' });
await expect(probe.test('instance-1')).resolves.toMatchObject({ authenticated: false });
});
it('persists only a redacted connection freshness snapshot, not a blanket capability claim', async () => {
const { db, instances, probe } = fixture({ status: 401, headers: {}, body: '{}' });
await instances.create({ name: 'LAN', origin: 'http://192.168.1.20:3000' });
await probe.test('instance-1');
await probe.test('instance-1');
const snapshots = db
.prepare('SELECT category, state, payload_json, expires_at FROM status_snapshots')
.all() as {
category: string;
state: string;
payload_json: string;
expires_at: string;
}[];
expect(snapshots).toHaveLength(1);
expect(snapshots[0]).toMatchObject({ category: 'connection', state: 'fresh' });
expect(JSON.parse(snapshots[0]!.payload_json)).toEqual({
reachable: true,
authenticated: false,
});
expect(snapshots[0]!.expires_at).toBe('2026-07-16T12:00:30.000Z');
expect(db.prepare('SELECT * FROM capabilities').all()).toEqual([]);
});
it('does not invoke transport for an unknown instance', async () => {
const { probe } = fixture({ status: 200, headers: {}, body: '{}' });
await expect(probe.test('missing')).rejects.toMatchObject({ code: 'NOT_FOUND' });
});
});