import { Command } from '@commander-js/extra-typings'; import { describe, expect, it, vi } from 'vitest'; import type { KtxCliCommandContext } from '../../src/cli-program.js'; import { registerWikiCommands } from '../../src/commands/knowledge-commands.js'; import { registerSlCommands } from '../../src/commands/sl-commands.js'; function makeContext(overrides: Partial = {}): KtxCliCommandContext { let exitCode = 0; return { io: { stdout: { write: vi.fn() }, stderr: { write: vi.fn() }, }, deps: {}, packageInfo: { name: '@kaelio/ktx', version: '0.0.0-test' }, setExitCode: (code) => { exitCode = code; }, runInit: vi.fn(), writeDebug: vi.fn(), ...overrides, get exitCode() { return exitCode; }, } as KtxCliCommandContext; } describe('wiki and sl read command routing', () => { it('routes wiki read through the knowledge runner', async () => { const program = new Command().exitOverride().option('--project-dir '); const knowledge = vi.fn(async () => 0); const context = makeContext({ deps: { knowledge } }); registerWikiCommands(program, context); await expect( program.parseAsync(['--project-dir', '/tmp/ktx-project', 'wiki', 'read', 'metrics-revenue'], { from: 'user', }), ).resolves.toBe(program); expect(knowledge).toHaveBeenCalledWith( { command: 'read', projectDir: '/tmp/ktx-project', key: 'metrics-revenue', userId: 'local', }, context.io, ); }); it('routes wiki read with the parent --user-id option', async () => { const program = new Command().exitOverride().option('--project-dir '); const knowledge = vi.fn(async () => 0); const context = makeContext({ deps: { knowledge } }); registerWikiCommands(program, context); await expect( program.parseAsync( ['--project-dir', '/tmp/ktx-project', 'wiki', '--user-id', 'alex', 'read', 'handoff'], { from: 'user' }, ), ).resolves.toBe(program); expect(knowledge).toHaveBeenCalledWith( { command: 'read', projectDir: '/tmp/ktx-project', key: 'handoff', userId: 'alex', }, context.io, ); }); it('routes sl read through the semantic-layer runner', async () => { const program = new Command().exitOverride().option('--project-dir '); const sl = vi.fn(async () => 0); const context = makeContext({ deps: { sl } }); registerSlCommands(program, context); await expect( program.parseAsync( ['--project-dir', '/tmp/ktx-project', 'sl', '--connection-id', 'warehouse', 'read', 'orders'], { from: 'user' }, ), ).resolves.toBe(program); expect(sl).toHaveBeenCalledWith( { command: 'read', projectDir: '/tmp/ktx-project', connectionId: 'warehouse', sourceName: 'orders', }, context.io, ); }); it('routes sl read without --connection-id through the semantic-layer runner', async () => { const program = new Command().exitOverride().option('--project-dir '); const sl = vi.fn(async () => 0); const context = makeContext({ deps: { sl } }); registerSlCommands(program, context); await expect( program.parseAsync(['--project-dir', '/tmp/ktx-project', 'sl', 'read', 'orders'], { from: 'user' }), ).resolves.toBe(program); expect(sl).toHaveBeenCalledWith( { command: 'read', projectDir: '/tmp/ktx-project', connectionId: undefined, sourceName: 'orders', }, context.io, ); }); it('routes sl validate without --connection-id through the semantic-layer runner', async () => { const program = new Command().exitOverride().option('--project-dir '); const sl = vi.fn(async () => 0); const context = makeContext({ deps: { sl } }); registerSlCommands(program, context); await expect( program.parseAsync(['--project-dir', '/tmp/ktx-project', 'sl', 'validate', 'orders'], { from: 'user' }), ).resolves.toBe(program); expect(sl).toHaveBeenCalledWith( { command: 'validate', projectDir: '/tmp/ktx-project', connectionId: undefined, sourceName: 'orders', }, context.io, ); }); it('keeps sl query requiring --connection-id before invoking the runner', async () => { const program = new Command().exitOverride().option('--project-dir '); const sl = vi.fn(async () => 0); const context = makeContext({ deps: { sl } }); registerSlCommands(program, context); await expect( program.parseAsync( ['--project-dir', '/tmp/ktx-project', 'sl', 'query', '--measure', 'orders.count'], { from: 'user' }, ), ).rejects.toThrow("error: required option '--connection-id ' not specified"); expect(sl).not.toHaveBeenCalled(); }); });