From c1d462ea41d4086cb6facb808131a51cb85730ff Mon Sep 17 00:00:00 2001 From: Sergey Galuza Date: Sat, 11 Apr 2026 18:27:39 +0200 Subject: [PATCH] test(cliproxy): add catalog --json tests and clarify flag priority - Add unit tests verifying JSON output structure, field filtering, and minified format - Document that --json takes priority over subcommands (refresh/reset) Built [OnSteroids](https://onsteroids.ai) --- src/commands/cliproxy/index.ts | 2 + .../commands/cliproxy-catalog-json.test.ts | 64 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 tests/unit/commands/cliproxy-catalog-json.test.ts diff --git a/src/commands/cliproxy/index.ts b/src/commands/cliproxy/index.ts index bad626b4..52c4cb02 100644 --- a/src/commands/cliproxy/index.ts +++ b/src/commands/cliproxy/index.ts @@ -147,6 +147,8 @@ export async function handleCliproxyCommand(args: string[]): Promise { // Catalog commands if (command === 'catalog') { + // --json takes priority over subcommands (refresh/reset) — it always + // outputs the current resolved catalog regardless of other arguments. if (hasAnyFlag(remainingArgs, ['--json'])) { handleCatalogJson(); return; diff --git a/tests/unit/commands/cliproxy-catalog-json.test.ts b/tests/unit/commands/cliproxy-catalog-json.test.ts new file mode 100644 index 00000000..0e1928ff --- /dev/null +++ b/tests/unit/commands/cliproxy-catalog-json.test.ts @@ -0,0 +1,64 @@ +import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; + +let originalConsoleLog: typeof console.log; +let capturedOutput: string[]; + +beforeEach(() => { + originalConsoleLog = console.log; + capturedOutput = []; + console.log = (...args: unknown[]) => { + capturedOutput.push(args.map(String).join(' ')); + }; +}); + +afterEach(() => { + console.log = originalConsoleLog; +}); + +describe('cliproxy catalog --json output', () => { + it('outputs valid JSON mapping provider names to model arrays', async () => { + const { handleCatalogJson } = await import( + `../../../src/commands/cliproxy/catalog-subcommand?catalog-json-format=${Date.now()}` + ); + + handleCatalogJson(); + + expect(capturedOutput).toHaveLength(1); + const parsed = JSON.parse(capturedOutput[0]) as Record< + string, + Array<{ id: string; name: string }> + >; + + // Must be a non-empty object (static catalog always has providers) + expect(typeof parsed).toBe('object'); + expect(Object.keys(parsed).length).toBeGreaterThan(0); + + // Every provider entry must be an array of { id, name } objects + for (const [provider, models] of Object.entries(parsed)) { + expect(Array.isArray(models)).toBe(true); + expect(models.length).toBeGreaterThan(0); + for (const model of models) { + expect(typeof model.id).toBe('string'); + expect(typeof model.name).toBe('string'); + // Only id and name — no extra metadata leaks + expect(Object.keys(model).sort()).toEqual(['id', 'name']); + } + // Provider key should be a non-empty string + expect(provider.length).toBeGreaterThan(0); + } + }); + + it('outputs minified JSON (single line, no whitespace formatting)', async () => { + const { handleCatalogJson } = await import( + `../../../src/commands/cliproxy/catalog-subcommand?catalog-json-minified=${Date.now()}` + ); + + handleCatalogJson(); + + const output = capturedOutput[0]; + // Minified JSON has no newlines + expect(output.includes('\n')).toBe(false); + // Valid JSON roundtrip + expect(JSON.stringify(JSON.parse(output))).toBe(output); + }); +});