182 lines
6.2 KiB
JavaScript
182 lines
6.2 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
|
import { baseInputSchema, validateToolInput, assertValidToolInput } from '../../src/tools/schemas.js';
|
|
|
|
const validMinimal = { question: 'hello' };
|
|
const validFull = {
|
|
question: 'review this',
|
|
context: 'some context',
|
|
constraints: ['no external deps'],
|
|
expectedOutput: 'bullet points',
|
|
projectSummary: 'MVP phase 1',
|
|
taskSummary: 'implement schema validation',
|
|
relevantFiles: [
|
|
{ path: 'src/tools/schemas.js', content: 'import ...' },
|
|
{ path: 'test/schemas.test.js', content: 'import ...', language: 'javascript' },
|
|
],
|
|
logs: 'error: something broke',
|
|
};
|
|
|
|
describe('baseInputSchema', () => {
|
|
it('validates a minimal input successfully', () => {
|
|
const result = baseInputSchema.safeParse(validMinimal);
|
|
expect(result.success).toBe(true);
|
|
if (result.success) expect(result.data.question).toBe('hello');
|
|
});
|
|
|
|
it('validates a full input with all fields', () => {
|
|
const result = baseInputSchema.safeParse(validFull);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('accepts question with only whitespace (min(1) not trim+min(1))', () => {
|
|
const result = baseInputSchema.safeParse({ question: ' ' });
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('rejects empty string question', () => {
|
|
const result = baseInputSchema.safeParse({ question: '' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects question with null', () => {
|
|
const result = baseInputSchema.safeParse({ question: null });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects question with undefined', () => {
|
|
const result = baseInputSchema.safeParse({ question: undefined });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects question with a number', () => {
|
|
const result = baseInputSchema.safeParse({ question: 123 });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects missing question field entirely', () => {
|
|
const result = baseInputSchema.safeParse({ context: 'only context' });
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('accepts constraints as valid string[]', () => {
|
|
const input = { question: 'q', constraints: ['a', 'b'] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('rejects constraints with non-string elements', () => {
|
|
// @ts-ignore - testing runtime invalid input
|
|
const input = { question: 'q', constraints: ['a', 123] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('accepts relevantFiles with valid file objects', () => {
|
|
const input = { question: 'q', relevantFiles: [{ path: 'a.js', content: 'code' }] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('accepts empty relevantFiles array', () => {
|
|
const input = { question: 'q', relevantFiles: [] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(true);
|
|
});
|
|
|
|
it('rejects relevantFiles with missing path', () => {
|
|
// @ts-ignore - testing runtime invalid input
|
|
const input = { question: 'q', relevantFiles: [{ content: 'code' }] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects relevantFiles with non-string content', () => {
|
|
// @ts-ignore - testing runtime invalid input
|
|
const input = { question: 'q', relevantFiles: [{ path: 'a.js', content: 123 }] };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects input with unknown keys (strict mode)', () => {
|
|
// @ts-ignore - testing runtime invalid input
|
|
const input = { question: 'q', unknownField: true };
|
|
const result = baseInputSchema.safeParse(input);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects null as raw input', () => {
|
|
const result = baseInputSchema.safeParse(null);
|
|
expect(result.success).toBe(false);
|
|
});
|
|
|
|
it('rejects a string as raw input', () => {
|
|
const result = baseInputSchema.safeParse('just a string');
|
|
expect(result.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('validateToolInput', () => {
|
|
it('returns ok:true with data for valid input', () => {
|
|
const result = validateToolInput(validMinimal);
|
|
expect(result.ok).toBe(true);
|
|
if (result.ok) expect(result.data.question).toBe('hello');
|
|
});
|
|
|
|
it('returns ok:false with errors array for invalid input', () => {
|
|
const result = validateToolInput({ question: '' });
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(Array.isArray(result.errors)).toBe(true);
|
|
});
|
|
|
|
it('error messages include dot-notation path segments', () => {
|
|
const result = validateToolInput({ question: 'q', relevantFiles: [{ content: 1 }] });
|
|
expect(!result.ok && result.errors.some((e) => e.includes('.'))).toBe(true);
|
|
});
|
|
|
|
it('returns ok:false for null input', () => {
|
|
const result = validateToolInput(null);
|
|
expect(result.ok).toBe(false);
|
|
});
|
|
|
|
it('returns ok:false for unknown keys', () => {
|
|
// @ts-ignore - testing runtime invalid input
|
|
const result = validateToolInput({ question: 'q', extraKey: true });
|
|
expect(result.ok).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('assertValidToolInput', () => {
|
|
it('returns validated data for valid input', () => {
|
|
const result = assertValidToolInput(validMinimal);
|
|
expect(typeof result).toBe('object');
|
|
expect(result.question).toBe('hello');
|
|
});
|
|
|
|
it('throws Error with kind "ValidationError" on invalid input', () => {
|
|
expect(() => assertValidToolInput({ question: '' })).toThrow('Input validation error');
|
|
});
|
|
|
|
it('thrown error has .kind === "ValidationError"', () => {
|
|
let caught = null;
|
|
try {
|
|
assertValidToolInput(null);
|
|
} catch (e) {
|
|
caught = e;
|
|
}
|
|
expect(caught).not.toBeNull();
|
|
expect(/** @type{Error} */ (caught).kind).toBe('ValidationError');
|
|
});
|
|
|
|
it('thrown error message includes validation details', () => {
|
|
let caughtMsg = '';
|
|
try {
|
|
// @ts-ignore - testing runtime invalid input
|
|
assertValidToolInput({ question: 'q', badField: true });
|
|
} catch (e) {
|
|
caughtMsg = /** @type{Error} */ (e).message;
|
|
}
|
|
expect(caughtMsg).toContain('badField');
|
|
expect(caughtMsg).toContain('Input validation error');
|
|
});
|
|
});
|