Files
iot-dc3/dc3-cli/test/session.test.ts
pnoker eae86b58fc refactor(storage)!: unify platform persistence on reactive postgresql
- migrate auth, manager, data, and agentic persistence to R2DBC
- hard cut pagination, facade, gateway, web, and CLI contracts
- remove legacy database and TSDB adapters and document validation gates

BREAKING CHANGE: platform persistence now requires PostgreSQL 18 with TimescaleDB and the new reactive API contracts.
2026-09-01 22:03:16 +08:00

121 lines
4.4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
const fetchCalls: Array<{ url: string; init: RequestInit }> = [];
vi.mock('../src/core/config-manager.js', () => ({
configManager: {
getActiveProfile: vi.fn(async () => ({ gateway: 'http://gw.test/', tenant: 't', username: 'u', credential_store: 'env' })),
load: vi.fn(async () => ({ current_profile: 'default' })),
getSettings: vi.fn(async () => ({ renewal_threshold_hours: 12, output_format: 'json', color: false, retry_count: 1 })),
},
}));
vi.mock('../src/core/token-manager.js', () => ({
tokenManager: {
getState: vi.fn(async () => null),
needsRenewal: vi.fn(async () => false),
},
}));
import { Command } from 'commander';
import { registerSessionCommand, registerActionCommand } from '../src/commands/session.js';
function buildProgram(): Command {
const program = new Command();
program.exitOverride();
registerSessionCommand(program);
registerActionCommand(program);
return program;
}
async function run(args: string[]): Promise<string> {
const program = buildProgram();
let out = '';
vi.spyOn(process.stdout, 'write').mockImplementation((chunk) => {
out += String(chunk);
return true;
});
vi.spyOn(process, 'exit').mockImplementation(((code?: number) => undefined) as never);
try {
await program.parseAsync(args, { from: 'user' });
} finally {
(process.stdout.write as ReturnType<typeof vi.spyOn>).mockRestore();
}
return out;
}
describe('session command group', () => {
beforeEach(() => {
fetchCalls.length = 0;
vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => {
fetchCalls.push({ url, init: init ?? {} });
return new Response(JSON.stringify({ ok: true, data: {} }), { status: 200 });
}));
});
it('list posts paging body to /agentic/session/list', async () => {
await run(['session', 'list', '--offset', '5', '--limit', '5']);
expect(fetchCalls[0].url).toBe('http://gw.test/api/v3/agentic/session/list');
expect(JSON.parse(String(fetchCalls[0].init.body))).toEqual({
offset: 5,
limit: 5,
});
});
it('get/messages encode conversation_id as a query param', async () => {
await run(['session', 'get', 'conv A&B']);
await run(['session', 'messages', 'conv A&B']);
expect(fetchCalls[0].url).toBe(
'http://gw.test/api/v3/agentic/session/get_by_conversation_id?conversation_id=conv%20A%26B',
);
expect(fetchCalls[1].url).toContain('/agentic/message/list?conversation_id=conv%20A%26B');
});
it('rename posts the update body with conversation_id param', async () => {
await run(['session', 'rename', 'c1', '--name', 'new-name']);
expect(fetchCalls[0].url).toBe(
'http://gw.test/api/v3/agentic/session/update?conversation_id=c1',
);
expect(JSON.parse(String(fetchCalls[0].init.body))).toEqual({ title: 'new-name' });
});
it('delete posts to the delete route with the id param', async () => {
await run(['session', 'delete', 'c9']);
expect(fetchCalls[0].init.method).toBe('POST');
expect(fetchCalls[0].url).toContain('/agentic/session/delete?conversation_id=c9');
});
});
describe('action approval loop', () => {
beforeEach(() => {
fetchCalls.length = 0;
vi.stubGlobal('fetch', vi.fn(async (url: string, init?: RequestInit) => {
fetchCalls.push({ url, init: init ?? {} });
return new Response(JSON.stringify({ ok: true, data: {} }), { status: 200 });
}));
});
it('pending scopes by conversation_id', async () => {
await run(['action', 'pending', '--conversation-id', 'conv-1']);
expect(fetchCalls[0].url).toBe(
'http://gw.test/api/v3/agentic/action/pending?conversation_id=conv-1',
);
});
it('confirm and reject post action_id as query param', async () => {
await run(['action', 'confirm', 'a-1']);
await run(['action', 'reject', 'a-2']);
expect(fetchCalls[0].init.method).toBe('POST');
expect(fetchCalls[0].url).toBe('http://gw.test/api/v3/agentic/action/confirm?action_id=a-1');
expect(fetchCalls[1].init.method).toBe('POST');
expect(fetchCalls[1].url).toBe('http://gw.test/api/v3/agentic/action/reject?action_id=a-2');
});
it('pending forwards offset and limit when supplied', async () => {
await run(['action', 'pending', '--conversation-id', 'conv-1', '--offset', '10', '--limit', '5']);
expect(fetchCalls[0].url).toBe(
'http://gw.test/api/v3/agentic/action/pending?conversation_id=conv-1&offset=10&limit=5',
);
});
});