87edd6e35b
- 从模板引擎中移除这两个函数的注册 - 从 bash 工具描述中移除对应的模板变量 - 删除不再需要的 git-instructions.txt 文件 - 更新单元测试,移除相关测试用例
244 lines
8.1 KiB
TypeScript
244 lines
8.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
renderTemplate,
|
|
renderPromptTemplate,
|
|
createToolDescriptionContext,
|
|
} from '../../../src/template/renderer.js';
|
|
import type { ExtendedTemplateContext } from '../../../src/template/types.js';
|
|
import {
|
|
CUSTOM_TIMEOUT_MS,
|
|
MAX_TIMEOUT_MS,
|
|
MAX_OUTPUT_CHARS,
|
|
} from '../../../src/constants/bash-tool.js';
|
|
|
|
describe('Template Renderer - 模板渲染器', () => {
|
|
describe('基础变量替换', () => {
|
|
it('替换简单变量', () => {
|
|
const result = renderTemplate('Hello ${name}!', { name: 'World' });
|
|
expect(result).toBe('Hello World!');
|
|
});
|
|
|
|
it('替换嵌套属性', () => {
|
|
const result = renderTemplate('${user.name} is ${user.age}', {
|
|
user: { name: 'Alice', age: 30 },
|
|
});
|
|
expect(result).toBe('Alice is 30');
|
|
});
|
|
|
|
it('处理未定义变量 - 默认空字符串', () => {
|
|
const result = renderTemplate('Value: ${undefined_var}', {});
|
|
expect(result).toBe('Value: ');
|
|
});
|
|
|
|
it('处理未定义变量 - 自定义默认值', () => {
|
|
const result = renderTemplate('Value: ${undefined_var}', {}, { undefinedValue: 'N/A' });
|
|
expect(result).toBe('Value: N/A');
|
|
});
|
|
});
|
|
|
|
describe('三元表达式', () => {
|
|
it('条件为真时返回 trueValue', () => {
|
|
const result = renderTemplate('${isActive ? "Active" : "Inactive"}', { isActive: true });
|
|
expect(result).toBe('Active');
|
|
});
|
|
|
|
it('条件为假时返回 falseValue', () => {
|
|
const result = renderTemplate('${isActive ? "Active" : "Inactive"}', { isActive: false });
|
|
expect(result).toBe('Inactive');
|
|
});
|
|
|
|
it('空字符串被视为假', () => {
|
|
const result = renderTemplate('${value ? "yes" : "no"}', { value: '' });
|
|
expect(result).toBe('no');
|
|
});
|
|
});
|
|
|
|
describe('函数调用', () => {
|
|
it('调用注册的函数', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {
|
|
GET_VALUE: () => 42,
|
|
},
|
|
};
|
|
const result = renderTemplate('Value: ${GET_VALUE()}', context);
|
|
expect(result).toBe('Value: 42');
|
|
});
|
|
|
|
it('调用返回字符串的函数', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {
|
|
GET_MESSAGE: () => 'Hello from function',
|
|
},
|
|
};
|
|
const result = renderTemplate('${GET_MESSAGE()}', context);
|
|
expect(result).toBe('Hello from function');
|
|
});
|
|
|
|
it('未注册的函数返回空字符串', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {},
|
|
};
|
|
const result = renderTemplate('Value: ${UNKNOWN_FUNC()}', context);
|
|
expect(result).toBe('Value: ');
|
|
});
|
|
|
|
it('没有 __functions__ 时返回空字符串', () => {
|
|
const result = renderTemplate('Value: ${SOME_FUNC()}', {});
|
|
expect(result).toBe('Value: ');
|
|
});
|
|
});
|
|
|
|
describe('算术运算(除法)', () => {
|
|
it('数值除法', () => {
|
|
const result = renderTemplate('${value / 1000}', { value: 60000 });
|
|
expect(result).toBe('60');
|
|
});
|
|
|
|
it('函数调用结果除法', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {
|
|
GET_MS: () => 120000,
|
|
},
|
|
};
|
|
const result = renderTemplate('${GET_MS() / 60000} minutes', context);
|
|
expect(result).toBe('2 minutes');
|
|
});
|
|
|
|
it('结果向下取整', () => {
|
|
const result = renderTemplate('${value / 1000}', { value: 12345 });
|
|
expect(result).toBe('12');
|
|
});
|
|
|
|
it('除以零不进行除法运算', () => {
|
|
// 除以零时,divMatch 匹配但 divisor === 0,会跳过除法逻辑
|
|
// 然后尝试作为变量处理 "value / 0",找不到返回空
|
|
const result = renderTemplate('${value / 0}', { value: 100 });
|
|
expect(result).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('组合表达式', () => {
|
|
it('函数调用 + 除法 + 字符串', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {
|
|
TIMEOUT_MS: () => 600000,
|
|
},
|
|
};
|
|
const result = renderTemplate(
|
|
'Timeout: ${TIMEOUT_MS()}ms (${TIMEOUT_MS()/60000} minutes)',
|
|
context
|
|
);
|
|
expect(result).toBe('Timeout: 600000ms (10 minutes)');
|
|
});
|
|
|
|
it('多个函数调用', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
__functions__: {
|
|
MIN_VAL: () => 10,
|
|
MAX_VAL: () => 100,
|
|
},
|
|
};
|
|
const result = renderTemplate('Range: ${MIN_VAL()} - ${MAX_VAL()}', context);
|
|
expect(result).toBe('Range: 10 - 100');
|
|
});
|
|
|
|
it('函数调用 + 变量混合', () => {
|
|
const context: ExtendedTemplateContext = {
|
|
toolName: 'bash',
|
|
__functions__: {
|
|
GET_TIMEOUT: () => 120000,
|
|
},
|
|
};
|
|
const result = renderTemplate(
|
|
'Use ${toolName} with timeout ${GET_TIMEOUT()}ms',
|
|
context
|
|
);
|
|
expect(result).toBe('Use bash with timeout 120000ms');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createToolDescriptionContext - 工具描述上下文', () => {
|
|
it('包含工具名称变量', () => {
|
|
const context = createToolDescriptionContext();
|
|
expect(context.custom?.BASH_TOOL_NAME).toBe('bash');
|
|
expect(context.custom?.GREP_TOOL_NAME).toBe('grep');
|
|
expect(context.custom?.GLOB_TOOL_NAME).toBe('glob');
|
|
});
|
|
|
|
it('包含注册的模板函数', () => {
|
|
const context = createToolDescriptionContext();
|
|
expect(context.__functions__).toBeDefined();
|
|
expect(typeof context.__functions__.CUSTOM_TIMEOUT_MS).toBe('function');
|
|
expect(typeof context.__functions__.MAX_TIMEOUT_MS).toBe('function');
|
|
expect(typeof context.__functions__.MAX_OUTPUT_CHARS).toBe('function');
|
|
});
|
|
|
|
it('函数返回正确的配置值', () => {
|
|
const context = createToolDescriptionContext();
|
|
expect(context.__functions__.CUSTOM_TIMEOUT_MS()).toBe(CUSTOM_TIMEOUT_MS);
|
|
expect(context.__functions__.MAX_TIMEOUT_MS()).toBe(MAX_TIMEOUT_MS);
|
|
expect(context.__functions__.MAX_OUTPUT_CHARS()).toBe(MAX_OUTPUT_CHARS);
|
|
});
|
|
});
|
|
|
|
describe('renderPromptTemplate - 提示词模板渲染', () => {
|
|
it('渲染带函数调用的模板', () => {
|
|
const context = createToolDescriptionContext();
|
|
const template = 'Timeout: ${CUSTOM_TIMEOUT_MS()}ms (${CUSTOM_TIMEOUT_MS()/60000} minutes)';
|
|
const result = renderPromptTemplate(template, context);
|
|
expect(result).toBe(`Timeout: ${CUSTOM_TIMEOUT_MS}ms (${Math.floor(CUSTOM_TIMEOUT_MS / 60000)} minutes)`);
|
|
});
|
|
|
|
it('渲染带工具名变量的模板', () => {
|
|
const context = createToolDescriptionContext();
|
|
const template = 'Use ${BASH_TOOL_NAME} or ${GREP_TOOL_NAME}';
|
|
const result = renderPromptTemplate(template, context);
|
|
expect(result).toBe('Use bash or grep');
|
|
});
|
|
|
|
it('渲染完整的 bash 描述模板片段', () => {
|
|
const context = createToolDescriptionContext();
|
|
const template = `Commands timeout after \${MAX_TIMEOUT_MS()}ms (\${MAX_TIMEOUT_MS()/60000} minutes).
|
|
Output truncated at \${MAX_OUTPUT_CHARS()} chars.
|
|
Use \${GREP_TOOL_NAME} for search.`;
|
|
|
|
const result = renderPromptTemplate(template, context);
|
|
|
|
expect(result).toContain(`${MAX_TIMEOUT_MS}ms`);
|
|
expect(result).toContain(`${Math.floor(MAX_TIMEOUT_MS / 60000)} minutes`);
|
|
expect(result).toContain(`${MAX_OUTPUT_CHARS} chars`);
|
|
expect(result).toContain('grep for search');
|
|
});
|
|
});
|
|
|
|
describe('回归测试 - 确保现有功能正常', () => {
|
|
it('字符串字面量中的变量插值', () => {
|
|
const result = renderTemplate('${cond ? "Value is ${val}" : "No value"}', {
|
|
cond: true,
|
|
val: 42,
|
|
});
|
|
expect(result).toBe('Value is 42');
|
|
});
|
|
|
|
it('布尔值处理', () => {
|
|
expect(renderTemplate('${flag}', { flag: true })).toBe('true');
|
|
expect(renderTemplate('${flag}', { flag: false })).toBe('');
|
|
});
|
|
|
|
it('数字值处理', () => {
|
|
expect(renderTemplate('${num}', { num: 123 })).toBe('123');
|
|
expect(renderTemplate('${num}', { num: 0 })).toBe('0');
|
|
});
|
|
|
|
it('嵌套对象访问', () => {
|
|
const result = renderTemplate('${a.b.c}', { a: { b: { c: 'deep' } } });
|
|
expect(result).toBe('deep');
|
|
});
|
|
|
|
it('多个模板变量', () => {
|
|
const result = renderTemplate('${a} + ${b} = ${c}', { a: 1, b: 2, c: 3 });
|
|
expect(result).toBe('1 + 2 = 3');
|
|
});
|
|
});
|