Files
sencho/backend/src/__tests__/proxy-pilot-agent-role-header.test.ts
T
Anso 5cc4566eb1 test(rbac): add five-role parity coverage across proxy, WebSocket, and mixed-version surfaces
Adds a reusable five-role persona fixture and extends RBAC test matrix to cover proxy, WebSocket, Pilot-agent, and mixed-version transport surfaces.
2026-07-31 23:06:57 -04:00

106 lines
4.1 KiB
TypeScript

/**
* Pilot-agent-mode header parity: the createRemoteProxyMiddleware code path
* for pilot_agent remotes is identical to proxy mode (the only difference is
* an empty apiToken from NodeRegistry.getProxyTarget). Stub the singleton so
* a pilot_agent node routes through a loopback capture server and assert the
* same PROXY_ROLE_HEADER is forwarded.
*/
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import http from 'http';
import request from 'supertest';
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';
import { PROXY_ROLE_HEADER } from '../services/license-headers';
import { seedPersonas } from './fixtures/personas';
import { DatabaseService } from '../services/DatabaseService';
describe('pilot-agent-mode proxy role header parity', () => {
let tmpDir: string;
let app: import('express').Express;
let server: http.Server;
let captured: http.IncomingHttpHeaders | null = null;
let pilotNodeId: number;
let personas: ReturnType<typeof seedPersonas>;
beforeAll(async () => {
tmpDir = await setupTestDb();
({ app } = await import('../index'));
personas = seedPersonas(DatabaseService.getInstance());
// Loopback capture server that advertises cross-node-rbac.
server = http.createServer((req, res) => {
if (req.url?.startsWith('/api/meta')) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ version: '0.96.0', capabilities: ['cross-node-rbac'] }));
return;
}
captured = req.headers;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end('[]');
});
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
const port = (server.address() as import('net').AddressInfo).port;
pilotNodeId = DatabaseService.getInstance().addNode({
name: 'pilot-header-test',
type: 'remote',
mode: 'pilot_agent',
compose_dir: '/tmp',
is_default: false,
api_url: '',
api_token: '',
});
// getProxyTarget returns null for pilot_agent without a live bridge.
// Stub it to return the capture-server URL with an empty apiToken — the
// exact shape a real PilotTunnelBridge produces at runtime.
const { NodeRegistry } = await import('../services/NodeRegistry');
const registry = NodeRegistry.getInstance();
const orig = registry.getProxyTarget.bind(registry);
vi.spyOn(registry, 'getProxyTarget').mockImplementation((nid: number) => {
if (nid === pilotNodeId) return { apiUrl: `http://127.0.0.1:${port}`, apiToken: '' };
return orig(nid);
});
});
afterAll(async () => {
vi.restoreAllMocks();
await new Promise<void>((resolve) => server.close(() => resolve()));
cleanupTestDb(tmpDir);
});
it('forwards the deployer session role through the pilot-agent proxy path', async () => {
captured = null;
const res = await request(app)
.get('/api/stacks')
.set('Authorization', personas.deployer.bearer)
.set('x-node-id', String(pilotNodeId));
expect(res.status).toBe(200);
// The capture server must have received the request.
expect(captured).not.toBeNull();
expect(captured?.[PROXY_ROLE_HEADER]).toBe('deployer');
});
it('overwrites a smuggled admin header with the real deployer role on pilot path', async () => {
captured = null;
const res = await request(app)
.get('/api/stacks')
.set('Authorization', personas.deployer.bearer)
.set('x-node-id', String(pilotNodeId))
.set(PROXY_ROLE_HEADER, 'admin'); // smuggled
expect(res.status).toBe(200);
expect(captured).not.toBeNull();
expect(captured?.[PROXY_ROLE_HEADER]).toBe('deployer');
});
it('forwards the admin session role through the pilot-agent proxy path', async () => {
captured = null;
const res = await request(app)
.get('/api/stacks')
.set('Authorization', personas.admin.bearer)
.set('x-node-id', String(pilotNodeId));
expect(res.status).toBe(200);
expect(captured).not.toBeNull();
expect(captured?.[PROXY_ROLE_HEADER]).toBe('admin');
});
});