parser.test.js 8.58 KB
import { describe, it, expect } from 'vitest';
const { Parser } = require('../../core/parser');

describe('Parser', () => {
  // ── parseCalls: 正则解析(向后兼容) ──

  describe('parseCalls (regex)', () => {
    it('should parse single agent call', () => {
      const input = `
一些分析内容...

[CALL: coder]
实现一个 add 函数
[/CALL]

其他内容...
`;
      const result = Parser.parseCalls(input);

      expect(result).toHaveLength(1);
      expect(result[0]).toEqual({
        agent: 'coder',
        task: '实现一个 add 函数'
      });
    });

    it('should return only the last call when multiple calls exist (serial decision constraint)', () => {
      const input = `
[CALL: coder]
写代码
[/CALL]

[CALL: verifier]
验证代码
[/CALL]
`;
      const result = Parser.parseCalls(input);

      // 串行决策约束:只返回最后一个 CALL
      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('verifier');
    });

    it('should return empty array when no calls', () => {
      const input = '没有任何调用标记的内容';
      const result = Parser.parseCalls(input);

      expect(result).toEqual([]);
    });

    it('should handle agent name with spaces', () => {
      const input = '[CALL:  frontend-tester  ]\n测试任务\n[/CALL]';
      const result = Parser.parseCalls(input);

      expect(result[0].agent).toBe('frontend-tester');
    });

    it('should preserve multiline task content', () => {
      const input = `[CALL: coder]
第一行
第二行
第三行
[/CALL]`;
      const result = Parser.parseCalls(input);

      expect(result[0].task).toBe('第一行\n第二行\n第三行');
    });

    it('should skip placeholder agent names', () => {
      const input = `
[CALL: agent_name]
这是示例
[/CALL]

[CALL: coder]
真实任务
[/CALL]
`;
      const result = Parser.parseCalls(input);
      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('coder');
    });
  });

  // ── parseCalls: JSON 解析 (3.1) ──

  describe('parseCalls (JSON)', () => {
    it('should parse single call from JSON code block', () => {
      const input = `
分析完成,调用 coder 执行:

\`\`\`json
{ "agent": "coder", "task": "实现一个 add 函数" }
\`\`\`
`;
      const result = Parser.parseCalls(input);

      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('coder');
      expect(result[0].task).toBe('实现一个 add 函数');
    });

    it('should parse multiple calls from JSON code block', () => {
      const input = `
\`\`\`json
{
  "calls": [
    { "agent": "coder", "task": "写代码" },
    { "agent": "verifier", "task": "验证" }
  ]
}
\`\`\`
`;
      // 无 agents 配置时仍然串行约束
      const result = Parser.parseCalls(input);
      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('verifier');
    });

    it('should prefer JSON over regex when both present', () => {
      const input = `
\`\`\`json
{ "agent": "coder", "task": "JSON 任务" }
\`\`\`

[CALL: verifier]
正则任务
[/CALL]
`;
      const result = Parser.parseCalls(input);

      // JSON 优先
      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('coder');
      expect(result[0].task).toBe('JSON 任务');
    });

    it('should fallback to regex when JSON is invalid', () => {
      const input = `
\`\`\`json
{ invalid json content 
\`\`\`

[CALL: coder]
正则任务
[/CALL]
`;
      const result = Parser.parseCalls(input);

      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('coder');
      expect(result[0].task).toBe('正则任务');
    });
  });

  // ── parseCalls: parallelGroup (3.5) ──

  describe('parseCalls (parallelGroup)', () => {
    const agentsWithParallel = {
      coder: { type: 'executor' },
      'validator-style': { type: 'validator', parallelGroup: 'audit' },
      'validator-test': { type: 'validator', parallelGroup: 'audit' },
      'validator-security': { type: 'validator', parallelGroup: 'audit' },
    };

    it('should allow parallel calls for same parallelGroup', () => {
      const input = `
[CALL: validator-style]
检查样式
[/CALL]

[CALL: validator-test]
运行测试
[/CALL]

[CALL: validator-security]
安全审计
[/CALL]
`;
      const result = Parser.parseCalls(input, { agents: agentsWithParallel });

      // 同一个 parallelGroup,允许全部返回
      expect(result).toHaveLength(3);
    });

    it('should still enforce serial constraint for ungrouped agents', () => {
      const input = `
[CALL: coder]
写代码
[/CALL]

[CALL: validator-style]
检查样式
[/CALL]
`;
      const result = Parser.parseCalls(input, { agents: agentsWithParallel });

      // coder 没有 parallelGroup,严格串行
      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('validator-style');
    });

    it('should enforce serial when no agents config provided', () => {
      const input = `
[CALL: validator-style]
检查样式
[/CALL]

[CALL: validator-test]
运行测试
[/CALL]
`;
      const result = Parser.parseCalls(input); // 没有 agents 参数

      expect(result).toHaveLength(1);
      expect(result[0].agent).toBe('validator-test');
    });

    it('should work with JSON format and parallelGroup', () => {
      const input = `
\`\`\`json
{
  "calls": [
    { "agent": "validator-style", "task": "检查样式" },
    { "agent": "validator-test", "task": "运行测试" }
  ]
}
\`\`\`
`;
      const result = Parser.parseCalls(input, { agents: agentsWithParallel });

      // 同 parallelGroup,允许并行
      expect(result).toHaveLength(2);
    });
  });

  // ── parseResult ──

  describe('parseResult', () => {
    it('should parse DONE status', () => {
      const input = `
执行完成

[RESULT: DONE]
已创建 src/add.js 文件
[/RESULT]
`;
      const result = Parser.parseResult(input);

      expect(result).toEqual({
        status: 'DONE',
        message: '已创建 src/add.js 文件'
      });
    });

    it('should parse PASS status', () => {
      const input = '[RESULT: PASS]\n所有测试通过\n[/RESULT]';
      const result = Parser.parseResult(input);

      expect(result.status).toBe('PASS');
      expect(result.message).toBe('所有测试通过');
    });

    it('should parse FAIL status', () => {
      const input = '[RESULT: FAIL]\n测试失败:add(1,2) 返回 null\n[/RESULT]';
      const result = Parser.parseResult(input);

      expect(result.status).toBe('FAIL');
      expect(result.message).toContain('测试失败');
    });

    it('should parse ERROR status', () => {
      const input = '[RESULT: ERROR]\n文件不存在\n[/RESULT]';
      const result = Parser.parseResult(input);

      expect(result.status).toBe('ERROR');
    });

    it('should return ERROR when no result marker', () => {
      const input = '没有结果标记的输出';
      const result = Parser.parseResult(input);

      expect(result.status).toBe('ERROR');
      expect(result.message).toContain('No result marker');
    });

    it('should handle status with extra spaces', () => {
      const input = '[RESULT:  PASS  ]\n通过\n[/RESULT]';
      const result = Parser.parseResult(input);

      expect(result.status).toBe('PASS');
    });
  });

  // ── parseResult: JSON 解析 (3.1) ──

  describe('parseResult (JSON)', () => {
    it('should parse result from JSON code block', () => {
      const input = `
执行完成:

\`\`\`json
{ "status": "DONE", "message": "已创建文件" }
\`\`\`
`;
      const result = Parser.parseResult(input);

      expect(result.status).toBe('DONE');
      expect(result.message).toBe('已创建文件');
    });

    it('should prefer JSON over regex for result', () => {
      const input = `
\`\`\`json
{ "status": "PASS", "message": "JSON 结果" }
\`\`\`

[RESULT: FAIL]
正则结果
[/RESULT]
`;
      const result = Parser.parseResult(input);

      // JSON 优先
      expect(result.status).toBe('PASS');
      expect(result.message).toBe('JSON 结果');
    });

    it('should fallback to regex when JSON result is invalid', () => {
      const input = `
\`\`\`json
{ "invalid": "no status field" }
\`\`\`

[RESULT: DONE]
正则兜底
[/RESULT]
`;
      const result = Parser.parseResult(input);

      expect(result.status).toBe('DONE');
      expect(result.message).toBe('正则兜底');
    });

    it('should reject invalid status values in JSON', () => {
      const input = `
\`\`\`json
{ "status": "RUNNING", "message": "非法状态" }
\`\`\`

[RESULT: DONE]
正则兜底
[/RESULT]
`;
      const result = Parser.parseResult(input);

      // RUNNING 不是合法状态,JSON 解析应忽略
      expect(result.status).toBe('DONE');
    });
  });
});