feat(api): complete phase 2.4 control plane
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
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' });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user