Skip to content

Commit 9ddc102

Browse files
NianJiuZstclaude
andcommitted
test: add unit tests for config export-schema logic
Test generateToolSchema() pure function and registry command filtering rather than the command itself to avoid a circular dependency (export-schema → registry → export-schema). Covers: - Schema name, description, input_schema structure - Type inference (string, number, boolean, array) - Required flag properties - Registry getAllCommands() filtering (skip auth/config/update) - All filtered commands produce valid schemas Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a5126a8 commit 9ddc102

1 file changed

Lines changed: 107 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { describe, it, expect } from 'bun:test';
2+
import { generateToolSchema } from '../../../src/utils/schema';
3+
import { registry } from '../../../src/registry';
4+
5+
type JsonSchema = Record<string, unknown>;
6+
type ToolSchema = { name: string; description?: string; input_schema: JsonSchema };
7+
8+
function getSchema(cmdName: string): ToolSchema {
9+
const { command } = registry.resolve(cmdName.split(' '));
10+
return generateToolSchema(command) as unknown as ToolSchema;
11+
}
12+
13+
function is(input: unknown): JsonSchema {
14+
return input as JsonSchema;
15+
}
16+
17+
describe('generateToolSchema', () => {
18+
it('generates schema for text chat command', () => {
19+
const schema = getSchema('text chat');
20+
21+
expect(schema.name).toBe('mmx_text_chat');
22+
expect(schema.description).toBeDefined();
23+
expect(schema.input_schema).toBeDefined();
24+
expect(schema.input_schema.type).toBe('object');
25+
expect(schema.input_schema.properties).toBeDefined();
26+
});
27+
28+
it('includes required flag properties', () => {
29+
const schema = getSchema('image generate');
30+
31+
const ischema = schema.input_schema;
32+
const props = is(ischema.properties);
33+
expect(props.prompt).toBeDefined();
34+
expect(is(props.prompt).type).toBe('string');
35+
expect(ischema.required).toContain('prompt');
36+
});
37+
38+
it('infers number type from numeric flags', () => {
39+
const schema = getSchema('text chat');
40+
41+
const props = is(schema.input_schema.properties);
42+
const maxTokens = Object.entries(props).find(([key]) => key === 'maxTokens');
43+
expect(maxTokens).toBeDefined();
44+
expect(is(maxTokens![1]!).type).toBe('number');
45+
});
46+
47+
it('infers boolean type for flagless options', () => {
48+
const schema = getSchema('text chat');
49+
50+
const props = is(schema.input_schema.properties);
51+
expect(props.stream).toBeDefined();
52+
expect(is(props.stream).type).toBe('boolean');
53+
});
54+
55+
it('infers array type for repeatable options', () => {
56+
const schema = getSchema('text chat');
57+
58+
const props = is(schema.input_schema.properties);
59+
expect(props.message).toBeDefined();
60+
expect(is(props.message).type).toBe('array');
61+
});
62+
63+
it('name uses underscore-separated path', () => {
64+
const schema = getSchema('speech synthesize');
65+
66+
expect(schema.name).toBe('mmx_speech_synthesize');
67+
});
68+
});
69+
70+
describe('registry getAllCommands filtering', () => {
71+
it('returns all registered commands', () => {
72+
const commands = registry.getAllCommands();
73+
expect(commands.length).toBeGreaterThan(10);
74+
// Should contain real commands
75+
const names = commands.map(c => c.name);
76+
expect(names).toContain('text chat');
77+
expect(names).toContain('image generate');
78+
expect(names).toContain('speech synthesize');
79+
});
80+
81+
it('filters out auth, config, update prefixes', () => {
82+
const SKIP = ['auth ', 'config ', 'update'];
83+
const commands = registry.getAllCommands();
84+
const filtered = commands.filter(c => !SKIP.some(p => c.name.startsWith(p)));
85+
86+
const names = filtered.map(c => c.name);
87+
expect(names.every(n => !n.startsWith('auth '))).toBe(true);
88+
expect(names.every(n => !n.startsWith('config '))).toBe(true);
89+
// Real commands remain
90+
expect(names.some(n => n === 'text chat')).toBe(true);
91+
expect(names.some(n => n === 'image generate')).toBe(true);
92+
expect(names.some(n => n === 'music generate')).toBe(true);
93+
});
94+
95+
it('every filtered command generates valid schema', () => {
96+
const SKIP = ['auth ', 'config ', 'update'];
97+
const commands = registry.getAllCommands();
98+
const filtered = commands.filter(c => !SKIP.some(p => c.name.startsWith(p)));
99+
100+
for (const cmd of filtered) {
101+
const schema = generateToolSchema(cmd) as unknown as ToolSchema;
102+
expect(schema.name).toMatch(/^mmx_\w/);
103+
expect(schema.input_schema.type).toBe('object');
104+
expect(typeof schema.description).toBe('string');
105+
}
106+
});
107+
});

0 commit comments

Comments
 (0)