test: cover orchestrator admin boundary
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
"local_restart": "bash scripts/local_restart.sh",
|
||||
"pro_restart": "bash scripts/pro_restart.sh",
|
||||
"build": "vite build",
|
||||
"test:orchestrator": "node --test server/orchestrator-routes.test.mjs",
|
||||
"preview": "node scripts/preview.mjs",
|
||||
"dev:preview": "vite preview"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { once } from 'node:events';
|
||||
import test from 'node:test';
|
||||
import { createAdminApp } from './app.mjs';
|
||||
|
||||
function createServices({ role = 'admin' } = {}) {
|
||||
const calls = {
|
||||
update: [],
|
||||
shadow: [],
|
||||
plans: [],
|
||||
detail: [],
|
||||
};
|
||||
const configState = {
|
||||
config: {
|
||||
mode: 'shadow',
|
||||
primaryEngine: 'langgraph',
|
||||
fallbackEngine: 'native',
|
||||
serviceUrl: 'http://127.0.0.1:8093',
|
||||
requestTimeoutMs: 5000,
|
||||
rolloutPercent: 0,
|
||||
userAllowlist: [],
|
||||
workflowAllowlist: ['code-run-v1'],
|
||||
fallbackToNative: true,
|
||||
requireHealthy: true,
|
||||
executionEnabled: false,
|
||||
},
|
||||
configVersion: 4,
|
||||
updatedBy: 'admin-user',
|
||||
updatedAt: 1,
|
||||
source: 'admin-db',
|
||||
runtime: {
|
||||
effective: true,
|
||||
shadowsLangGraph: true,
|
||||
executesLangGraph: false,
|
||||
executionHandoff: { enabled: false },
|
||||
},
|
||||
engines: [],
|
||||
executors: [],
|
||||
};
|
||||
|
||||
return {
|
||||
calls,
|
||||
services: {
|
||||
ready: Promise.resolve(),
|
||||
parseCookies: () => ({ test_session: 'token' }),
|
||||
USER_COOKIE: 'test_session',
|
||||
userLoginCookies: () => [],
|
||||
clearUserSessionCookie: () => {},
|
||||
resolveCookieDomainForRequest: () => undefined,
|
||||
userAuth: {
|
||||
getMe: async () => ({ id: 'john-id', username: 'john', role }),
|
||||
},
|
||||
orchestratorConfigService: {
|
||||
getAdminConfig: async () => configState,
|
||||
updateAdminConfig: async (config, context) => {
|
||||
calls.update.push({ config, context });
|
||||
return { ...configState, config, updatedBy: context.updatedBy };
|
||||
},
|
||||
getRuntimeState: async ({ probe }) => ({
|
||||
...configState,
|
||||
serviceHealth: { ok: true, status: 'healthy', probe },
|
||||
}),
|
||||
},
|
||||
orchestratorObservabilityService: {
|
||||
listShadowRuns: async (query) => {
|
||||
calls.shadow.push(query);
|
||||
return { metrics: { observations: 0 }, runs: [] };
|
||||
},
|
||||
listExecutionPlans: async (query) => {
|
||||
calls.plans.push(query);
|
||||
return { metrics: { decisions: 0 }, plans: [] };
|
||||
},
|
||||
getCanaryReadiness: async () => ({
|
||||
ready: false,
|
||||
recommendation: 'keep_shadow',
|
||||
}),
|
||||
getShadowRun: async (runId) => {
|
||||
calls.detail.push(runId);
|
||||
return runId === 'missing' ? null : { native: { runId }, remote: { available: true } };
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function startApp(options) {
|
||||
const harness = createServices(options);
|
||||
const server = createAdminApp(harness.services).listen(0, '127.0.0.1');
|
||||
await once(server, 'listening');
|
||||
const address = server.address();
|
||||
assert.ok(address && typeof address === 'object');
|
||||
return {
|
||||
...harness,
|
||||
request: (path, init = {}) => fetch(`http://127.0.0.1:${address.port}${path}`, {
|
||||
...init,
|
||||
headers: {
|
||||
Cookie: 'test_session=token',
|
||||
'Content-Type': 'application/json',
|
||||
...init.headers,
|
||||
},
|
||||
}),
|
||||
close: () => new Promise((resolve, reject) => {
|
||||
server.close((error) => error ? reject(error) : resolve());
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
test('admin can read orchestrator config, runtime and observability endpoints', async (t) => {
|
||||
const app = await startApp();
|
||||
t.after(app.close);
|
||||
|
||||
const config = await app.request('/admin-api/orchestrator/config');
|
||||
assert.equal(config.status, 200);
|
||||
assert.equal((await config.json()).config.mode, 'shadow');
|
||||
|
||||
const runtime = await app.request('/admin-api/orchestrator/runtime');
|
||||
assert.equal(runtime.status, 200);
|
||||
assert.deepEqual((await runtime.json()).serviceHealth, {
|
||||
ok: true,
|
||||
status: 'healthy',
|
||||
probe: true,
|
||||
});
|
||||
|
||||
const shadow = await app.request(
|
||||
'/admin-api/orchestrator/shadow-runs?hours=24&limit=50&status=failed',
|
||||
);
|
||||
assert.equal(shadow.status, 200);
|
||||
assert.deepEqual(app.calls.shadow, [{ hours: '24', limit: '50', status: 'failed' }]);
|
||||
|
||||
const plans = await app.request(
|
||||
'/admin-api/orchestrator/execution-plans?hours=168&limit=20&selection=native',
|
||||
);
|
||||
assert.equal(plans.status, 200);
|
||||
assert.deepEqual(app.calls.plans, [{ hours: '168', limit: '20', selection: 'native' }]);
|
||||
|
||||
const readiness = await app.request('/admin-api/orchestrator/canary-readiness');
|
||||
assert.equal(readiness.status, 200);
|
||||
assert.equal((await readiness.json()).recommendation, 'keep_shadow');
|
||||
|
||||
const detail = await app.request('/admin-api/orchestrator/shadow-runs/run-1');
|
||||
assert.equal(detail.status, 200);
|
||||
assert.deepEqual(app.calls.detail, ['run-1']);
|
||||
|
||||
const missing = await app.request('/admin-api/orchestrator/shadow-runs/missing');
|
||||
assert.equal(missing.status, 404);
|
||||
});
|
||||
|
||||
test('admin config update records the authenticated administrator', async (t) => {
|
||||
const app = await startApp();
|
||||
t.after(app.close);
|
||||
|
||||
const response = await app.request('/admin-api/orchestrator/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ config: { mode: 'off', executionEnabled: false } }),
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
assert.deepEqual(app.calls.update, [{
|
||||
config: { mode: 'off', executionEnabled: false },
|
||||
context: { updatedBy: 'john-id' },
|
||||
}]);
|
||||
});
|
||||
|
||||
test('ordinary users cannot access or mutate orchestrator controls', async (t) => {
|
||||
const app = await startApp({ role: 'user' });
|
||||
t.after(app.close);
|
||||
|
||||
const read = await app.request('/admin-api/orchestrator/config');
|
||||
assert.equal(read.status, 403);
|
||||
|
||||
const update = await app.request('/admin-api/orchestrator/config', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ config: { mode: 'active', executionEnabled: true } }),
|
||||
});
|
||||
assert.equal(update.status, 403);
|
||||
assert.equal(app.calls.update.length, 0);
|
||||
});
|
||||
Reference in New Issue
Block a user