ktx/packages/cli/test/io/result-table.test.ts
Kevin Messiaen 0e2e6c7d90
refactor(mongo-query): consolidate query types and drop unsafe cast
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
2026-07-11 21:19:55 +07:00

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');
});
});