239 lines
8.6 KiB
TypeScript
239 lines
8.6 KiB
TypeScript
|
|
import { describe, it, expect } from 'vitest';
|
||
|
|
import { parseCommand, parseCommandSchema, validateCommand } from '../../src/utils/command';
|
||
|
|
|
||
|
|
describe('parseCommandSchema', () => {
|
||
|
|
it('should parse empty schema', () => {
|
||
|
|
const schema = parseCommandSchema('');
|
||
|
|
expect(schema).toEqual({
|
||
|
|
name: '',
|
||
|
|
params: [],
|
||
|
|
options: [],
|
||
|
|
flags: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse command name only', () => {
|
||
|
|
const schema = parseCommandSchema('move');
|
||
|
|
expect(schema).toEqual({
|
||
|
|
name: 'move',
|
||
|
|
params: [],
|
||
|
|
options: [],
|
||
|
|
flags: [],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse required params', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> <to>');
|
||
|
|
expect(schema.params).toEqual([
|
||
|
|
{ name: 'from', required: true, variadic: false },
|
||
|
|
{ name: 'to', required: true, variadic: false },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse optional params', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [to]');
|
||
|
|
expect(schema.params).toEqual([
|
||
|
|
{ name: 'from', required: true, variadic: false },
|
||
|
|
{ name: 'to', required: false, variadic: false },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse variadic params', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [targets...]');
|
||
|
|
expect(schema.params).toEqual([
|
||
|
|
{ name: 'from', required: true, variadic: false },
|
||
|
|
{ name: 'targets', required: false, variadic: true },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse required variadic params', () => {
|
||
|
|
const schema = parseCommandSchema('move <targets...>');
|
||
|
|
expect(schema.params).toEqual([
|
||
|
|
{ name: 'targets', required: true, variadic: true },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse long flags', () => {
|
||
|
|
const schema = parseCommandSchema('move [--force] [--quiet]');
|
||
|
|
expect(schema.flags).toEqual([
|
||
|
|
{ name: 'force' },
|
||
|
|
{ name: 'quiet' },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse short flags', () => {
|
||
|
|
const schema = parseCommandSchema('move [-f] [-q]');
|
||
|
|
expect(schema.flags).toEqual([
|
||
|
|
{ name: 'f', short: 'f' },
|
||
|
|
{ name: 'q', short: 'q' },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse long options', () => {
|
||
|
|
const schema = parseCommandSchema('move --x <value> [--y value]');
|
||
|
|
expect(schema.options).toEqual([
|
||
|
|
{ name: 'x', required: true },
|
||
|
|
{ name: 'y', required: false },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse short options', () => {
|
||
|
|
const schema = parseCommandSchema('move -x <value> [-y value]');
|
||
|
|
expect(schema.options).toEqual([
|
||
|
|
{ name: 'x', short: 'x', required: true },
|
||
|
|
{ name: 'y', short: 'y', required: false },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should parse mixed schema', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> <to> [--force] [-f] [--speed <val>] [-s val]');
|
||
|
|
expect(schema).toEqual({
|
||
|
|
name: 'move',
|
||
|
|
params: [
|
||
|
|
{ name: 'from', required: true, variadic: false },
|
||
|
|
{ name: 'to', required: true, variadic: false },
|
||
|
|
],
|
||
|
|
flags: [
|
||
|
|
{ name: 'force' },
|
||
|
|
{ name: 'f', short: 'f' },
|
||
|
|
],
|
||
|
|
options: [
|
||
|
|
{ name: 'speed', required: false },
|
||
|
|
{ name: 's', short: 's', required: false },
|
||
|
|
],
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should handle complex schema', () => {
|
||
|
|
const schema = parseCommandSchema('place <piece> <region> [x...] [--rotate <angle>] [--force] [-f]');
|
||
|
|
expect(schema.name).toBe('place');
|
||
|
|
expect(schema.params).toHaveLength(3);
|
||
|
|
expect(schema.flags).toHaveLength(2);
|
||
|
|
expect(schema.options).toHaveLength(1);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('validateCommand', () => {
|
||
|
|
it('should validate correct command', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> <to>');
|
||
|
|
const command = parseCommand('move meeple1 region1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should reject wrong command name', () => {
|
||
|
|
const schema = parseCommandSchema('move <from>');
|
||
|
|
const command = parseCommand('place meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({
|
||
|
|
valid: false,
|
||
|
|
errors: expect.arrayContaining([
|
||
|
|
expect.stringContaining('命令名称不匹配'),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should reject missing required params', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> <to>');
|
||
|
|
const command = parseCommand('move meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({
|
||
|
|
valid: false,
|
||
|
|
errors: expect.arrayContaining([
|
||
|
|
expect.stringContaining('参数不足'),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should accept optional params missing', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [to]');
|
||
|
|
const command = parseCommand('move meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should reject extra params without variadic', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> <to>');
|
||
|
|
const command = parseCommand('move meeple1 region1 extra');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({
|
||
|
|
valid: false,
|
||
|
|
errors: expect.arrayContaining([
|
||
|
|
expect.stringContaining('参数过多'),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should accept extra params with variadic', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [targets...]');
|
||
|
|
const command = parseCommand('move meeple1 region1 region2 region3');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should reject missing required option', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> --speed <val>');
|
||
|
|
const command = parseCommand('move meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({
|
||
|
|
valid: false,
|
||
|
|
errors: expect.arrayContaining([
|
||
|
|
expect.stringContaining('缺少必需选项'),
|
||
|
|
]),
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should accept present required option', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> --speed <val>');
|
||
|
|
const command = parseCommand('move meeple1 --speed 10');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should accept optional option missing', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [--speed [val]]');
|
||
|
|
const command = parseCommand('move meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should accept flags present or not', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> [--force]');
|
||
|
|
const cmd1 = parseCommand('move meeple1');
|
||
|
|
const cmd2 = parseCommand('move meeple1 --force');
|
||
|
|
expect(validateCommand(cmd1, schema)).toEqual({ valid: true });
|
||
|
|
expect(validateCommand(cmd2, schema)).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should validate short form option', () => {
|
||
|
|
const schema = parseCommandSchema('move <from> -s <val>');
|
||
|
|
const command = parseCommand('move meeple1 -s 10');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result).toEqual({ valid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should provide detailed error messages', () => {
|
||
|
|
const schema = parseCommandSchema('place <piece> <region> --rotate <angle>');
|
||
|
|
const command = parseCommand('place meeple1');
|
||
|
|
const result = validateCommand(command, schema);
|
||
|
|
expect(result.valid).toBe(false);
|
||
|
|
if (!result.valid) {
|
||
|
|
expect(result.errors.length).toBeGreaterThanOrEqual(1);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('integration', () => {
|
||
|
|
it('should work together parse and validate', () => {
|
||
|
|
const schemaStr = 'place <piece> <region> [--x <val>] [--y [val]] [--force] [-f]';
|
||
|
|
const schema = parseCommandSchema(schemaStr);
|
||
|
|
|
||
|
|
const validCmd = parseCommand('place meeple1 board --x 5 --force');
|
||
|
|
expect(validateCommand(validCmd, schema)).toEqual({ valid: true });
|
||
|
|
|
||
|
|
const invalidCmd = parseCommand('place meeple1');
|
||
|
|
const result = validateCommand(invalidCmd, schema);
|
||
|
|
expect(result.valid).toBe(false);
|
||
|
|
});
|
||
|
|
});
|