mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-25 12:01:03 +02:00
Route the Mongo read path through one canonical request/result shape and remove the `as unknown as` narrowing flagged in review. - Add optional executeQuery to KtxScanConnector and narrow by capability in the runner, replacing the double cast with a driver + method guard. - Move KtxMongoQueryInput/KtxMongoQueryResult into context/scan/types.ts; delete the duplicate runner request type, the inline MCP-port input, and the KtxMongoQueryResponse alias. KtxMongoQueryArgs now extends the base. - Inline the pass-through executeMongoQuery wrapper. - Correct the --output default in the mongo-query and sql CLI docs: it is TTY/CI/KTX_OUTPUT-derived, not unconditionally pretty. - Add tests: database override, empty result, parseLimitOption bounds, bigint/plural-row/empty-header rendering, and mongo_query_completed telemetry fields on both outcomes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ga2tvQ2YCEphmY8HM5Guzq
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { formatValue, printResultTable } from '../../src/io/result-table.js';
|
|
import type { KtxCliIo } from '../../src/cli-runtime.js';
|
|
|
|
function captureIo(): { io: KtxCliIo; out: () => string } {
|
|
let buffer = '';
|
|
const io = {
|
|
stdout: { write: (s: string) => { buffer += s; return true; } },
|
|
stderr: { write: () => true },
|
|
} as unknown as KtxCliIo;
|
|
return { io, out: () => buffer };
|
|
}
|
|
|
|
describe('formatValue', () => {
|
|
it('renders null/undefined as empty, scalars as strings, objects as JSON', () => {
|
|
expect(formatValue(null)).toBe('');
|
|
expect(formatValue(undefined)).toBe('');
|
|
expect(formatValue('x')).toBe('x');
|
|
expect(formatValue(42)).toBe('42');
|
|
expect(formatValue(true)).toBe('true');
|
|
expect(formatValue(10n)).toBe('10');
|
|
expect(formatValue({ a: 1 })).toBe(JSON.stringify({ a: 1 }));
|
|
});
|
|
});
|
|
|
|
describe('printResultTable', () => {
|
|
const table = { connectionId: 'mongo', headers: ['_id', 'city'], rows: [['a1', 'NY']], rowCount: 1 };
|
|
|
|
it('json mode prints the structured payload', () => {
|
|
const { io, out } = captureIo();
|
|
printResultTable(table, 'json', io);
|
|
expect(JSON.parse(out())).toEqual(table);
|
|
});
|
|
|
|
it('plain mode prints tab-separated headers and rows', () => {
|
|
const { io, out } = captureIo();
|
|
printResultTable(table, 'plain', io);
|
|
expect(out()).toBe('_id\tcity\na1\tNY\n');
|
|
});
|
|
|
|
it('pretty mode prints a header rule and a singular row count', () => {
|
|
const { io, out } = captureIo();
|
|
printResultTable(table, 'pretty', io);
|
|
expect(out()).toContain('_id');
|
|
expect(out()).toContain('1 row\n');
|
|
});
|
|
|
|
it('pretty mode aligns multiple rows and pluralizes the row count', () => {
|
|
const { io, out } = captureIo();
|
|
const multi = {
|
|
connectionId: 'mongo',
|
|
headers: ['id', 'city'],
|
|
rows: [
|
|
['1', 'NY'],
|
|
['1000', 'Indianapolis'],
|
|
],
|
|
rowCount: 2,
|
|
};
|
|
printResultTable(multi, 'pretty', io);
|
|
// Column widened to the longest cell ("1000"), so "1" is right-padded to 4 chars.
|
|
expect(out()).toContain('1 NY');
|
|
expect(out()).toContain('2 rows\n');
|
|
});
|
|
|
|
it('pretty mode omits the header rule when there are no headers', () => {
|
|
const { io, out } = captureIo();
|
|
printResultTable({ connectionId: 'mongo', headers: [], rows: [], rowCount: 0 }, 'pretty', io);
|
|
expect(out()).toBe('\n0 rows\n');
|
|
});
|
|
});
|