Skip to content

Commit c61e3b6

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 c61e3b6

1 file changed

Lines changed: 105 additions & 0 deletions

File tree

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

0 commit comments

Comments
 (0)