test: cover orchestrator admin boundary
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||
"local_restart": "bash scripts/local_restart.sh",
|
"local_restart": "bash scripts/local_restart.sh",
|
||||||
"pro_restart": "bash scripts/pro_restart.sh",
|
"pro_restart": "bash scripts/pro_restart.sh",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
|
"test:orchestrator": "node --test server/orchestrator-routes.test.mjs",
|
||||||
"preview": "node scripts/preview.mjs",
|
"preview": "node scripts/preview.mjs",
|
||||||
"dev:preview": "vite preview"
|
"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