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