Files
multi-simadmin/apps/api/src/control-plane.test.ts
T

171 lines
5.9 KiB
TypeScript

import Database from 'better-sqlite3';
import { afterEach, describe, expect, it } from 'vitest';
import { buildControlPlaneApp } from './control-plane.js';
import { migrateDatabase } from './infrastructure/database/migrations.js';
import { UpstreamError } from './infrastructure/transport/upstream-error.js';
import { SafeInstanceTransport } from './infrastructure/transport/safe-instance-transport.js';
import { createSafeControlPlaneUpstream } from './infrastructure/transport/safe-control-plane-upstream.js';
import type { SecretStore } from './infrastructure/secrets/secret-store.js';
const dbs: Database.Database[] = [];
afterEach(async () => {
for (const db of dbs.splice(0)) db.close();
});
class Store implements SecretStore {
async set() {
return '';
}
async get() {
return '[REDACTED]';
}
async delete() {
return false;
}
}
describe('buildControlPlaneApp', () => {
it('registers the durable event route fail-closed when no authentication dependency is supplied', async () => {
const db = new Database(':memory:');
db.pragma('foreign_keys=ON');
migrateDatabase(db);
dbs.push(db);
const app = buildControlPlaneApp({
db,
store: new Store(),
upstream: {
get: async () => ({ status: 200, headers: {}, body: '' }),
request: async () => ({ status: 200, headers: {}, body: '' }),
postNetworkRegisterAuto: async () => ({ status: 200 }),
},
});
const response = await app.inject({ method: 'GET', url: '/api/v1/events' });
expect(response.statusCode).toBe(401);
expect(response.headers['content-type']).toContain('application/problem+json');
expect(response.json()).toMatchObject({ status: 401, code: 'UNAUTHORIZED' });
await app.close();
});
it('passes an explicit event-stream authenticator to durable cursor replay', async () => {
const db = new Database(':memory:');
db.pragma('foreign_keys=ON');
migrateDatabase(db);
dbs.push(db);
const app = buildControlPlaneApp({
db,
store: new Store(),
upstream: {
get: async () => ({ status: 200, headers: {}, body: '' }),
request: async () => ({ status: 200, headers: {}, body: '' }),
postNetworkRegisterAuto: async () => ({ status: 200 }),
},
authenticateEventStream: (request) => request.headers.authorization === 'Bearer allowed',
});
const response = await app.inject({
method: 'GET',
url: '/api/v1/events',
headers: {
authorization: 'Bearer allowed',
'last-event-id': 'unknown-cursor',
},
});
expect(response.statusCode).toBe(409);
expect(response.headers['content-type']).toContain('application/problem+json');
expect(response.json()).toMatchObject({
status: 409,
code: 'EVENT_POSITION_UNAVAILABLE',
});
await app.close();
});
it('assembles instance CRUD, connection checks, login and logout without listening or reading external config', async () => {
const db = new Database(':memory:');
db.pragma('foreign_keys=ON');
migrateDatabase(db);
dbs.push(db);
const app = buildControlPlaneApp({
db,
store: new Store(),
upstream: createSafeControlPlaneUpstream(
new SafeInstanceTransport({
resolve: async () => [{ address: '192.168.1.10', family: 4 }],
request: async (request) => ({
status: request.url.endsWith('/api/health') ? 401 : 200,
headers: { 'set-cookie': 'simadmin_session=opaque' },
body: '',
}),
}),
),
now: () => new Date('2026-07-16T12:00:00.000Z'),
});
const created = await app.inject({
method: 'POST',
url: '/api/v1/instances',
payload: { name: 'A', origin: 'http://192.168.1.10:8080' },
});
expect(created.statusCode).toBe(201);
const tested = await app.inject({
method: 'POST',
url: '/api/v1/instances/' + created.json().id + '/test-connection',
});
expect(tested.json()).toMatchObject({ authenticated: false });
const insecureLogin = await app.inject({
method: 'POST',
url: '/api/v1/instances/' + created.json().id + '/login',
payload: { password: 'never-leak-this' },
});
expect(insecureLogin.statusCode).toBe(400);
expect(insecureLogin.json()).toMatchObject({
title: 'Bad Request',
status: 400,
code: 'UPSTREAM_INSECURE_AUTH',
});
expect(insecureLogin.body).not.toContain('never-leak-this');
expect(insecureLogin.body).not.toContain('192.168.1.10');
await app.close();
});
it('maps typed upstream failures to redacted gateway Problem Details', async () => {
const db = new Database(':memory:');
db.pragma('foreign_keys=ON');
migrateDatabase(db);
dbs.push(db);
const app = buildControlPlaneApp({
db,
store: new Store(),
upstream: {
get: async () => {
throw new UpstreamError('UPSTREAM_UNAVAILABLE', {
cause: new Error('ECONNREFUSED 192.168.1.99 cookie=simadmin_session=secret'),
});
},
request: async () => ({ status: 200, headers: {}, body: '' }),
postNetworkRegisterAuto: async () => ({ status: 200 }),
},
});
const created = await app.inject({
method: 'POST',
url: '/api/v1/instances',
payload: { name: 'B', origin: 'http://192.168.1.99' },
});
const response = await app.inject({
method: 'POST',
url: '/api/v1/instances/' + created.json().id + '/test-connection',
});
expect(response.statusCode).toBe(502);
expect(response.headers['content-type']).toContain('application/problem+json');
expect(response.json()).toMatchObject({
title: 'Bad Gateway',
status: 502,
code: 'UPSTREAM_UNAVAILABLE',
});
expect(response.body).not.toContain('ECONNREFUSED');
expect(response.body).not.toContain('192.168.1.99');
expect(response.body).not.toContain('simadmin_session');
await app.close();
});
});