feat: 更新命令名称为dpml-prompt,保持PromptX品牌名称
This commit is contained in:
@ -1,313 +1,313 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { spawn } = require('child_process')
|
||||
const fs = require('fs').promises
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
describe('PromptX CLI - E2E Tests', () => {
|
||||
const CLI_PATH = path.resolve(__dirname, '../../bin/promptx.js');
|
||||
let tempDir;
|
||||
const CLI_PATH = path.resolve(__dirname, '../../bin/promptx.js')
|
||||
let tempDir
|
||||
|
||||
beforeAll(async () => {
|
||||
// 创建临时测试目录
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-e2e-'));
|
||||
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-e2e-'))
|
||||
|
||||
// 创建测试项目结构
|
||||
const promptDir = path.join(tempDir, 'prompt');
|
||||
await fs.mkdir(promptDir, { recursive: true });
|
||||
|
||||
const coreDir = path.join(promptDir, 'core');
|
||||
await fs.mkdir(coreDir, { recursive: true });
|
||||
|
||||
const promptDir = path.join(tempDir, 'prompt')
|
||||
await fs.mkdir(promptDir, { recursive: true })
|
||||
|
||||
const coreDir = path.join(promptDir, 'core')
|
||||
await fs.mkdir(coreDir, { recursive: true })
|
||||
|
||||
// 创建测试文件
|
||||
await fs.writeFile(
|
||||
path.join(coreDir, 'test-core.md'),
|
||||
'# Core Prompt\n\n这是核心提示词。'
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, 'bootstrap.md'),
|
||||
'# Bootstrap\n\n这是启动文件。'
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// 清理临时目录
|
||||
await fs.rm(tempDir, { recursive: true });
|
||||
});
|
||||
await fs.rm(tempDir, { recursive: true })
|
||||
})
|
||||
|
||||
/**
|
||||
* 运行CLI命令的辅助函数
|
||||
*/
|
||||
function runCommand(args, options = {}) {
|
||||
function runCommand (args, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn('node', [CLI_PATH, ...args], {
|
||||
cwd: options.cwd || tempDir,
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
env: { ...process.env, ...options.env }
|
||||
});
|
||||
})
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
stdout += data.toString()
|
||||
})
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
stderr += data.toString()
|
||||
})
|
||||
|
||||
child.on('close', (code) => {
|
||||
resolve({
|
||||
code,
|
||||
stdout,
|
||||
stderr
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
child.on('error', reject);
|
||||
child.on('error', reject)
|
||||
|
||||
// 如果需要输入,发送输入数据
|
||||
if (options.input) {
|
||||
child.stdin.write(options.input);
|
||||
child.stdin.end();
|
||||
child.stdin.write(options.input)
|
||||
child.stdin.end()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
describe('基础命令测试', () => {
|
||||
test('应该显示帮助信息', async () => {
|
||||
const result = await runCommand(['--help']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('PromptX CLI');
|
||||
expect(result.stdout).toContain('Usage:');
|
||||
expect(result.stdout).toContain('hello');
|
||||
expect(result.stdout).toContain('learn');
|
||||
expect(result.stdout).toContain('recall');
|
||||
expect(result.stdout).toContain('remember');
|
||||
});
|
||||
const result = await runCommand(['--help'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('PromptX CLI')
|
||||
expect(result.stdout).toContain('Usage:')
|
||||
expect(result.stdout).toContain('hello')
|
||||
expect(result.stdout).toContain('learn')
|
||||
expect(result.stdout).toContain('recall')
|
||||
expect(result.stdout).toContain('remember')
|
||||
})
|
||||
|
||||
test('应该显示版本信息', async () => {
|
||||
const result = await runCommand(['--version']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toMatch(/\d+\.\d+\.\d+/);
|
||||
});
|
||||
});
|
||||
const result = await runCommand(['--version'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toMatch(/\d+\.\d+\.\d+/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('hello 命令 - 系统入口', () => {
|
||||
test('应该显示欢迎信息', async () => {
|
||||
const result = await runCommand(['hello']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('👋');
|
||||
expect(result.stdout).toContain('PromptX');
|
||||
expect(result.stdout).toContain('AI助手');
|
||||
});
|
||||
const result = await runCommand(['hello'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('👋')
|
||||
expect(result.stdout).toContain('PromptX')
|
||||
expect(result.stdout).toContain('AI助手')
|
||||
})
|
||||
|
||||
test('应该支持个性化问候', async () => {
|
||||
const result = await runCommand(['hello', '--name', '张三']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('张三');
|
||||
});
|
||||
const result = await runCommand(['hello', '--name', '张三'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('张三')
|
||||
})
|
||||
|
||||
test('应该显示系统状态', async () => {
|
||||
const result = await runCommand(['hello', '--status']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toMatch(/工作目录:/);
|
||||
expect(result.stdout).toMatch(/资源协议:/);
|
||||
});
|
||||
});
|
||||
const result = await runCommand(['hello', '--status'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toMatch(/工作目录:/)
|
||||
expect(result.stdout).toMatch(/资源协议:/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('learn 命令 - 资源学习', () => {
|
||||
test('应该加载prompt协议资源', async () => {
|
||||
const result = await runCommand(['learn', '@prompt://bootstrap']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('学习资源');
|
||||
expect(result.stdout).toContain('@prompt://bootstrap');
|
||||
});
|
||||
const result = await runCommand(['learn', '@prompt://bootstrap'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('学习资源')
|
||||
expect(result.stdout).toContain('@prompt://bootstrap')
|
||||
})
|
||||
|
||||
test('应该加载文件资源', async () => {
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('这是启动文件');
|
||||
});
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('这是启动文件')
|
||||
})
|
||||
|
||||
test('应该支持带参数的资源加载', async () => {
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md?line=1']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('# Bootstrap');
|
||||
expect(result.stdout).not.toContain('这是启动文件');
|
||||
});
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md?line=1'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('# Bootstrap')
|
||||
expect(result.stdout).not.toContain('这是启动文件')
|
||||
})
|
||||
|
||||
test('应该处理无效资源引用', async () => {
|
||||
const result = await runCommand(['learn', 'invalid-reference']);
|
||||
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stderr).toContain('资源引用格式错误');
|
||||
});
|
||||
const result = await runCommand(['learn', 'invalid-reference'])
|
||||
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.stderr).toContain('资源引用格式错误')
|
||||
})
|
||||
|
||||
test('应该处理不存在的文件', async () => {
|
||||
const result = await runCommand(['learn', '@file://nonexistent.md']);
|
||||
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stderr).toContain('Failed to read file');
|
||||
});
|
||||
});
|
||||
const result = await runCommand(['learn', '@file://nonexistent.md'])
|
||||
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.stderr).toContain('Failed to read file')
|
||||
})
|
||||
})
|
||||
|
||||
describe('recall 命令 - 记忆检索', () => {
|
||||
test('应该显示基本的记忆检索功能', async () => {
|
||||
const result = await runCommand(['recall', 'test']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('🔍 正在检索记忆');
|
||||
});
|
||||
const result = await runCommand(['recall', 'test'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('🔍 正在检索记忆')
|
||||
})
|
||||
|
||||
test('应该支持记忆类型指定', async () => {
|
||||
const result = await runCommand(['recall', 'test', '--type', 'semantic']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('semantic');
|
||||
});
|
||||
const result = await runCommand(['recall', 'test', '--type', 'semantic'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('semantic')
|
||||
})
|
||||
|
||||
test('应该支持模糊搜索', async () => {
|
||||
const result = await runCommand(['recall', 'test', '--fuzzy']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('模糊搜索');
|
||||
});
|
||||
});
|
||||
const result = await runCommand(['recall', 'test', '--fuzzy'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('模糊搜索')
|
||||
})
|
||||
})
|
||||
|
||||
describe('remember 命令 - 记忆存储', () => {
|
||||
test('应该存储新的记忆', async () => {
|
||||
const result = await runCommand(['remember', 'test-memory', 'This is a test memory']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('🧠 正在存储记忆');
|
||||
expect(result.stdout).toContain('test-memory');
|
||||
});
|
||||
const result = await runCommand(['remember', 'test-memory', 'This is a test memory'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('🧠 正在存储记忆')
|
||||
expect(result.stdout).toContain('test-memory')
|
||||
})
|
||||
|
||||
test('应该支持记忆类型指定', async () => {
|
||||
const result = await runCommand([
|
||||
'remember',
|
||||
'procedure-test',
|
||||
'How to test',
|
||||
'--type',
|
||||
'remember',
|
||||
'procedure-test',
|
||||
'How to test',
|
||||
'--type',
|
||||
'procedural'
|
||||
]);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('procedural');
|
||||
});
|
||||
])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('procedural')
|
||||
})
|
||||
|
||||
test('应该支持标签添加', async () => {
|
||||
const result = await runCommand([
|
||||
'remember',
|
||||
'tagged-memory',
|
||||
'remember',
|
||||
'tagged-memory',
|
||||
'Tagged content',
|
||||
'--tags',
|
||||
'test,example'
|
||||
]);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('tags');
|
||||
});
|
||||
});
|
||||
])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('tags')
|
||||
})
|
||||
})
|
||||
|
||||
describe('错误处理和边界情况', () => {
|
||||
test('应该处理无效命令', async () => {
|
||||
const result = await runCommand(['invalid-command']);
|
||||
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stderr).toContain('Unknown command');
|
||||
});
|
||||
const result = await runCommand(['invalid-command'])
|
||||
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.stderr).toContain('Unknown command')
|
||||
})
|
||||
|
||||
test('应该处理缺少参数的情况', async () => {
|
||||
const result = await runCommand(['learn']);
|
||||
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stderr).toContain('Missing required argument');
|
||||
});
|
||||
const result = await runCommand(['learn'])
|
||||
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.stderr).toContain('Missing required argument')
|
||||
})
|
||||
|
||||
test('应该处理权限错误', async () => {
|
||||
// 创建一个没有权限的文件
|
||||
const restrictedFile = path.join(tempDir, 'restricted.md');
|
||||
await fs.writeFile(restrictedFile, 'restricted content');
|
||||
await fs.chmod(restrictedFile, 0o000);
|
||||
|
||||
const result = await runCommand(['learn', '@file://restricted.md']);
|
||||
|
||||
expect(result.code).toBe(1);
|
||||
expect(result.stderr).toContain('EACCES');
|
||||
|
||||
const restrictedFile = path.join(tempDir, 'restricted.md')
|
||||
await fs.writeFile(restrictedFile, 'restricted content')
|
||||
await fs.chmod(restrictedFile, 0o000)
|
||||
|
||||
const result = await runCommand(['learn', '@file://restricted.md'])
|
||||
|
||||
expect(result.code).toBe(1)
|
||||
expect(result.stderr).toContain('EACCES')
|
||||
|
||||
// 恢复权限以便清理
|
||||
await fs.chmod(restrictedFile, 0o644);
|
||||
});
|
||||
});
|
||||
await fs.chmod(restrictedFile, 0o644)
|
||||
})
|
||||
})
|
||||
|
||||
describe('工作流集成测试', () => {
|
||||
test('应该支持完整的AI认知循环', async () => {
|
||||
// 1. Hello - 建立连接
|
||||
const helloResult = await runCommand(['hello', '--name', 'E2E测试']);
|
||||
expect(helloResult.code).toBe(0);
|
||||
|
||||
const helloResult = await runCommand(['hello', '--name', 'E2E测试'])
|
||||
expect(helloResult.code).toBe(0)
|
||||
|
||||
// 2. Learn - 学习资源
|
||||
const learnResult = await runCommand(['learn', '@file://bootstrap.md']);
|
||||
expect(learnResult.code).toBe(0);
|
||||
|
||||
const learnResult = await runCommand(['learn', '@file://bootstrap.md'])
|
||||
expect(learnResult.code).toBe(0)
|
||||
|
||||
// 3. Remember - 存储记忆
|
||||
const rememberResult = await runCommand([
|
||||
'remember',
|
||||
'e2e-test',
|
||||
'remember',
|
||||
'e2e-test',
|
||||
'E2E测试记忆',
|
||||
'--type',
|
||||
'episodic'
|
||||
]);
|
||||
expect(rememberResult.code).toBe(0);
|
||||
|
||||
])
|
||||
expect(rememberResult.code).toBe(0)
|
||||
|
||||
// 4. Recall - 检索记忆
|
||||
const recallResult = await runCommand(['recall', 'e2e-test']);
|
||||
expect(recallResult.code).toBe(0);
|
||||
});
|
||||
const recallResult = await runCommand(['recall', 'e2e-test'])
|
||||
expect(recallResult.code).toBe(0)
|
||||
})
|
||||
|
||||
test('应该支持资源链式学习', async () => {
|
||||
// 创建链式引用文件
|
||||
const chainFile = path.join(tempDir, 'chain.md');
|
||||
await fs.writeFile(chainFile, '@file://bootstrap.md');
|
||||
|
||||
const result = await runCommand(['learn', '@file://chain.md']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('这是启动文件');
|
||||
});
|
||||
});
|
||||
const chainFile = path.join(tempDir, 'chain.md')
|
||||
await fs.writeFile(chainFile, '@file://bootstrap.md')
|
||||
|
||||
const result = await runCommand(['learn', '@file://chain.md'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('这是启动文件')
|
||||
})
|
||||
})
|
||||
|
||||
describe('输出格式和交互', () => {
|
||||
test('应该支持JSON输出格式', async () => {
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md', '--format', 'json']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(() => JSON.parse(result.stdout)).not.toThrow();
|
||||
});
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md', '--format', 'json'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(() => JSON.parse(result.stdout)).not.toThrow()
|
||||
})
|
||||
|
||||
test('应该支持静默模式', async () => {
|
||||
const result = await runCommand(['hello', '--quiet']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout.trim()).toBe('');
|
||||
});
|
||||
const result = await runCommand(['hello', '--quiet'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout.trim()).toBe('')
|
||||
})
|
||||
|
||||
test('应该支持详细输出模式', async () => {
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md', '--verbose']);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toContain('DEBUG');
|
||||
});
|
||||
});
|
||||
});
|
||||
const result = await runCommand(['learn', '@file://bootstrap.md', '--verbose'])
|
||||
|
||||
expect(result.code).toBe(0)
|
||||
expect(result.stdout).toContain('DEBUG')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,357 +1,357 @@
|
||||
const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol');
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol')
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
|
||||
describe('PackageProtocol', () => {
|
||||
let packageProtocol;
|
||||
const originalEnv = process.env;
|
||||
const projectRoot = process.cwd(); // PromptX项目根目录
|
||||
let packageProtocol
|
||||
const originalEnv = process.env
|
||||
const projectRoot = process.cwd() // PromptX项目根目录
|
||||
|
||||
beforeEach(() => {
|
||||
packageProtocol = new PackageProtocol();
|
||||
packageProtocol = new PackageProtocol()
|
||||
// 重置环境变量
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
process.env = { ...originalEnv }
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
packageProtocol.clearCache();
|
||||
process.env = originalEnv;
|
||||
});
|
||||
packageProtocol.clearCache()
|
||||
process.env = originalEnv
|
||||
})
|
||||
|
||||
describe('基础功能', () => {
|
||||
test('应该正确初始化协议', () => {
|
||||
expect(packageProtocol.name).toBe('package');
|
||||
expect(packageProtocol.installModeCache).toBeInstanceOf(Map);
|
||||
});
|
||||
expect(packageProtocol.name).toBe('package')
|
||||
expect(packageProtocol.installModeCache).toBeInstanceOf(Map)
|
||||
})
|
||||
|
||||
test('应该提供协议信息', () => {
|
||||
const info = packageProtocol.getProtocolInfo();
|
||||
expect(info.name).toBe('package');
|
||||
expect(info.description).toContain('包协议');
|
||||
expect(info.examples).toContain('@package://package.json');
|
||||
expect(info.examples).toContain('@package://src/index.js');
|
||||
expect(info.installModes).toContain('development');
|
||||
});
|
||||
const info = packageProtocol.getProtocolInfo()
|
||||
expect(info.name).toBe('package')
|
||||
expect(info.description).toContain('包协议')
|
||||
expect(info.examples).toContain('@package://package.json')
|
||||
expect(info.examples).toContain('@package://src/index.js')
|
||||
expect(info.installModes).toContain('development')
|
||||
})
|
||||
|
||||
test('应该支持缓存', () => {
|
||||
expect(packageProtocol.enableCache).toBe(true);
|
||||
expect(packageProtocol.cache).toBeInstanceOf(Map);
|
||||
expect(packageProtocol.installModeCache).toBeInstanceOf(Map);
|
||||
});
|
||||
});
|
||||
expect(packageProtocol.enableCache).toBe(true)
|
||||
expect(packageProtocol.cache).toBeInstanceOf(Map)
|
||||
expect(packageProtocol.installModeCache).toBeInstanceOf(Map)
|
||||
})
|
||||
})
|
||||
|
||||
describe('安装模式检测', () => {
|
||||
test('应该检测开发模式', () => {
|
||||
// 设置开发环境
|
||||
process.env.NODE_ENV = 'development';
|
||||
packageProtocol.clearCache();
|
||||
|
||||
const mode = packageProtocol.detectInstallMode();
|
||||
expect(mode).toBe('development');
|
||||
});
|
||||
process.env.NODE_ENV = 'development'
|
||||
packageProtocol.clearCache()
|
||||
|
||||
const mode = packageProtocol.detectInstallMode()
|
||||
expect(mode).toBe('development')
|
||||
})
|
||||
|
||||
test('应该检测npx执行模式', () => {
|
||||
// 模拟npx环境
|
||||
process.env.npm_execpath = '/usr/local/bin/npx';
|
||||
packageProtocol.clearCache();
|
||||
|
||||
const mode = packageProtocol.detectInstallMode();
|
||||
expect(mode).toBe('npx');
|
||||
});
|
||||
process.env.npm_execpath = '/usr/local/bin/npx'
|
||||
packageProtocol.clearCache()
|
||||
|
||||
const mode = packageProtocol.detectInstallMode()
|
||||
expect(mode).toBe('npx')
|
||||
})
|
||||
|
||||
test('应该缓存检测结果', () => {
|
||||
const mode1 = packageProtocol.detectInstallMode();
|
||||
const mode2 = packageProtocol.detectInstallMode();
|
||||
|
||||
expect(mode1).toBe(mode2);
|
||||
expect(packageProtocol.installModeCache.size).toBe(1);
|
||||
});
|
||||
const mode1 = packageProtocol.detectInstallMode()
|
||||
const mode2 = packageProtocol.detectInstallMode()
|
||||
|
||||
expect(mode1).toBe(mode2)
|
||||
expect(packageProtocol.installModeCache.size).toBe(1)
|
||||
})
|
||||
|
||||
test('检测结果应该是有效的安装模式', () => {
|
||||
const mode = packageProtocol.detectInstallMode();
|
||||
const validModes = ['development', 'local', 'global', 'npx', 'monorepo', 'link'];
|
||||
expect(validModes).toContain(mode);
|
||||
});
|
||||
});
|
||||
const mode = packageProtocol.detectInstallMode()
|
||||
const validModes = ['development', 'local', 'global', 'npx', 'monorepo', 'link']
|
||||
expect(validModes).toContain(mode)
|
||||
})
|
||||
})
|
||||
|
||||
describe('NPX执行检测', () => {
|
||||
test('应该通过npm_execpath检测npx', () => {
|
||||
process.env.npm_execpath = '/path/to/npx';
|
||||
expect(packageProtocol._isNpxExecution()).toBe(true);
|
||||
});
|
||||
process.env.npm_execpath = '/path/to/npx'
|
||||
expect(packageProtocol._isNpxExecution()).toBe(true)
|
||||
})
|
||||
|
||||
test('应该通过npm_config_cache检测npx', () => {
|
||||
process.env.npm_config_cache = '/tmp/_npx/cache';
|
||||
expect(packageProtocol._isNpxExecution()).toBe(true);
|
||||
});
|
||||
process.env.npm_config_cache = '/tmp/_npx/cache'
|
||||
expect(packageProtocol._isNpxExecution()).toBe(true)
|
||||
})
|
||||
|
||||
test('正常情况下应该返回false', () => {
|
||||
delete process.env.npm_execpath;
|
||||
delete process.env.npm_config_cache;
|
||||
expect(packageProtocol._isNpxExecution()).toBe(false);
|
||||
});
|
||||
});
|
||||
delete process.env.npm_execpath
|
||||
delete process.env.npm_config_cache
|
||||
expect(packageProtocol._isNpxExecution()).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('全局安装检测', () => {
|
||||
test('应该检测常见的全局路径', () => {
|
||||
// 这个测试在实际环境中可能会失败,因为我们无法轻易改变__dirname
|
||||
const result = packageProtocol._isGlobalInstall();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
});
|
||||
const result = packageProtocol._isGlobalInstall()
|
||||
expect(typeof result).toBe('boolean')
|
||||
})
|
||||
})
|
||||
|
||||
describe('开发模式检测', () => {
|
||||
test('应该通过NODE_ENV检测开发模式', () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
expect(packageProtocol._isDevelopmentMode()).toBe(true);
|
||||
});
|
||||
process.env.NODE_ENV = 'development'
|
||||
expect(packageProtocol._isDevelopmentMode()).toBe(true)
|
||||
})
|
||||
|
||||
test('应该检测非node_modules目录', () => {
|
||||
// 当前测试环境应该不在node_modules中
|
||||
const result = packageProtocol._isDevelopmentMode();
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
});
|
||||
const result = packageProtocol._isDevelopmentMode()
|
||||
expect(typeof result).toBe('boolean')
|
||||
})
|
||||
})
|
||||
|
||||
describe('包查找功能', () => {
|
||||
test('应该能找到package.json', () => {
|
||||
const packageJsonPath = packageProtocol.findPackageJson();
|
||||
expect(packageJsonPath).toBeTruthy();
|
||||
expect(packageJsonPath).toMatch(/package\.json$/);
|
||||
});
|
||||
const packageJsonPath = packageProtocol.findPackageJson()
|
||||
expect(packageJsonPath).toBeTruthy()
|
||||
expect(packageJsonPath).toMatch(/package\.json$/)
|
||||
})
|
||||
|
||||
test('应该能找到根package.json', () => {
|
||||
const rootPackageJsonPath = packageProtocol.findRootPackageJson();
|
||||
expect(rootPackageJsonPath).toBeTruthy();
|
||||
expect(rootPackageJsonPath).toMatch(/package\.json$/);
|
||||
});
|
||||
const rootPackageJsonPath = packageProtocol.findRootPackageJson()
|
||||
expect(rootPackageJsonPath).toBeTruthy()
|
||||
expect(rootPackageJsonPath).toMatch(/package\.json$/)
|
||||
})
|
||||
|
||||
test('查找不存在的package.json应该返回null', () => {
|
||||
const result = packageProtocol.findPackageJson('/nonexistent/path');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
const result = packageProtocol.findPackageJson('/nonexistent/path')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('包根目录获取', () => {
|
||||
test('应该能获取包根目录', async () => {
|
||||
const packageRoot = await packageProtocol.getPackageRoot();
|
||||
expect(packageRoot).toBeTruthy();
|
||||
expect(typeof packageRoot).toBe('string');
|
||||
expect(path.isAbsolute(packageRoot)).toBe(true);
|
||||
});
|
||||
const packageRoot = await packageProtocol.getPackageRoot()
|
||||
expect(packageRoot).toBeTruthy()
|
||||
expect(typeof packageRoot).toBe('string')
|
||||
expect(path.isAbsolute(packageRoot)).toBe(true)
|
||||
})
|
||||
|
||||
test('项目根目录查找应该工作正常', () => {
|
||||
const root = packageProtocol._findProjectRoot();
|
||||
expect(root).toBeTruthy();
|
||||
expect(path.isAbsolute(root)).toBe(true);
|
||||
});
|
||||
});
|
||||
const root = packageProtocol._findProjectRoot()
|
||||
expect(root).toBeTruthy()
|
||||
expect(path.isAbsolute(root)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径解析', () => {
|
||||
test('应该解析package.json路径', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('package.json');
|
||||
expect(resolved).toMatch(/package\.json$/);
|
||||
expect(path.isAbsolute(resolved)).toBe(true);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('package.json')
|
||||
expect(resolved).toMatch(/package\.json$/)
|
||||
expect(path.isAbsolute(resolved)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该解析src目录路径', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('src/index.js');
|
||||
expect(resolved).toContain('src');
|
||||
expect(resolved).toMatch(/index\.js$/);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('src/index.js')
|
||||
expect(resolved).toContain('src')
|
||||
expect(resolved).toMatch(/index\.js$/)
|
||||
})
|
||||
|
||||
test('应该解析prompt目录路径', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('prompt/core/thought.md');
|
||||
expect(resolved).toContain('prompt');
|
||||
expect(resolved).toContain('core');
|
||||
expect(resolved).toMatch(/thought\.md$/);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('prompt/core/thought.md')
|
||||
expect(resolved).toContain('prompt')
|
||||
expect(resolved).toContain('core')
|
||||
expect(resolved).toMatch(/thought\.md$/)
|
||||
})
|
||||
|
||||
test('空路径应该返回包根目录', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('');
|
||||
expect(path.isAbsolute(resolved)).toBe(true);
|
||||
expect(resolved).toBeTruthy();
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('')
|
||||
expect(path.isAbsolute(resolved)).toBe(true)
|
||||
expect(resolved).toBeTruthy()
|
||||
})
|
||||
|
||||
test('只有空格的路径应该返回包根目录', async () => {
|
||||
const resolved = await packageProtocol.resolvePath(' ');
|
||||
expect(path.isAbsolute(resolved)).toBe(true);
|
||||
expect(resolved).toBeTruthy();
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath(' ')
|
||||
expect(path.isAbsolute(resolved)).toBe(true)
|
||||
expect(resolved).toBeTruthy()
|
||||
})
|
||||
|
||||
test('应该使用缓存', async () => {
|
||||
const path1 = await packageProtocol.resolvePath('package.json');
|
||||
const path2 = await packageProtocol.resolvePath('package.json');
|
||||
|
||||
expect(path1).toBe(path2);
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
const path1 = await packageProtocol.resolvePath('package.json')
|
||||
const path2 = await packageProtocol.resolvePath('package.json')
|
||||
|
||||
expect(path1).toBe(path2)
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径安全检查', () => {
|
||||
test('应该阻止目录遍历攻击', async () => {
|
||||
await expect(
|
||||
packageProtocol.resolvePath('../../../etc/passwd')
|
||||
).rejects.toThrow('路径安全检查失败');
|
||||
});
|
||||
).rejects.toThrow('路径安全检查失败')
|
||||
})
|
||||
|
||||
test('正常的相对路径应该被允许', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('src/lib/utils.js');
|
||||
expect(resolved).toContain('src');
|
||||
expect(resolved).toContain('lib');
|
||||
expect(resolved).toMatch(/utils\.js$/);
|
||||
});
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('src/lib/utils.js')
|
||||
expect(resolved).toContain('src')
|
||||
expect(resolved).toContain('lib')
|
||||
expect(resolved).toMatch(/utils\.js$/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('资源存在性检查', () => {
|
||||
test('存在的文件应该返回true', async () => {
|
||||
const exists = await packageProtocol.exists('package.json');
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
const exists = await packageProtocol.exists('package.json')
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
test('不存在的文件应该返回false', async () => {
|
||||
const exists = await packageProtocol.exists('nonexistent.txt');
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
const exists = await packageProtocol.exists('nonexistent.txt')
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('内容加载', () => {
|
||||
test('应该能加载package.json内容', async () => {
|
||||
const result = await packageProtocol.loadContent('package.json');
|
||||
|
||||
expect(result).toHaveProperty('content');
|
||||
expect(result).toHaveProperty('path');
|
||||
expect(result).toHaveProperty('protocol', 'package');
|
||||
expect(result).toHaveProperty('installMode');
|
||||
expect(result).toHaveProperty('metadata');
|
||||
|
||||
expect(result.metadata).toHaveProperty('size');
|
||||
expect(result.metadata).toHaveProperty('lastModified');
|
||||
expect(result.metadata).toHaveProperty('absolutePath');
|
||||
expect(result.metadata).toHaveProperty('relativePath');
|
||||
|
||||
const result = await packageProtocol.loadContent('package.json')
|
||||
|
||||
expect(result).toHaveProperty('content')
|
||||
expect(result).toHaveProperty('path')
|
||||
expect(result).toHaveProperty('protocol', 'package')
|
||||
expect(result).toHaveProperty('installMode')
|
||||
expect(result).toHaveProperty('metadata')
|
||||
|
||||
expect(result.metadata).toHaveProperty('size')
|
||||
expect(result.metadata).toHaveProperty('lastModified')
|
||||
expect(result.metadata).toHaveProperty('absolutePath')
|
||||
expect(result.metadata).toHaveProperty('relativePath')
|
||||
|
||||
// 验证内容是有效的JSON
|
||||
expect(() => JSON.parse(result.content)).not.toThrow();
|
||||
});
|
||||
expect(() => JSON.parse(result.content)).not.toThrow()
|
||||
})
|
||||
|
||||
test('加载不存在的文件应该抛出错误', async () => {
|
||||
await expect(
|
||||
packageProtocol.loadContent('nonexistent.txt')
|
||||
).rejects.toThrow('包资源不存在');
|
||||
});
|
||||
).rejects.toThrow('包资源不存在')
|
||||
})
|
||||
|
||||
test('返回的metadata应该包含正确信息', async () => {
|
||||
const result = await packageProtocol.loadContent('package.json');
|
||||
|
||||
expect(result.metadata.size).toBe(result.content.length);
|
||||
expect(result.metadata.lastModified.constructor.name).toBe('Date');
|
||||
expect(path.isAbsolute(result.metadata.absolutePath)).toBe(true);
|
||||
expect(result.metadata.relativePath).toBe('package.json');
|
||||
});
|
||||
});
|
||||
const result = await packageProtocol.loadContent('package.json')
|
||||
|
||||
expect(result.metadata.size).toBe(result.content.length)
|
||||
expect(result.metadata.lastModified.constructor.name).toBe('Date')
|
||||
expect(path.isAbsolute(result.metadata.absolutePath)).toBe(true)
|
||||
expect(result.metadata.relativePath).toBe('package.json')
|
||||
})
|
||||
})
|
||||
|
||||
describe('查询参数支持', () => {
|
||||
test('应该支持查询参数', async () => {
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('encoding', 'utf8');
|
||||
|
||||
const resolved = await packageProtocol.resolvePath('package.json', queryParams);
|
||||
expect(resolved).toMatch(/package\.json$/);
|
||||
});
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('encoding', 'utf8')
|
||||
|
||||
const resolved = await packageProtocol.resolvePath('package.json', queryParams)
|
||||
expect(resolved).toMatch(/package\.json$/)
|
||||
})
|
||||
|
||||
test('相同路径但不同查询参数应该有不同的缓存', async () => {
|
||||
const queryParams1 = new QueryParams();
|
||||
queryParams1.set('test', 'value1');
|
||||
|
||||
const queryParams2 = new QueryParams();
|
||||
queryParams2.set('test', 'value2');
|
||||
|
||||
await packageProtocol.resolvePath('package.json', queryParams1);
|
||||
await packageProtocol.resolvePath('package.json', queryParams2);
|
||||
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(1);
|
||||
});
|
||||
});
|
||||
const queryParams1 = new QueryParams()
|
||||
queryParams1.set('test', 'value1')
|
||||
|
||||
const queryParams2 = new QueryParams()
|
||||
queryParams2.set('test', 'value2')
|
||||
|
||||
await packageProtocol.resolvePath('package.json', queryParams1)
|
||||
await packageProtocol.resolvePath('package.json', queryParams2)
|
||||
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('调试信息', () => {
|
||||
test('应该提供完整的调试信息', () => {
|
||||
const debugInfo = packageProtocol.getDebugInfo();
|
||||
|
||||
expect(debugInfo).toHaveProperty('protocol', 'package');
|
||||
expect(debugInfo).toHaveProperty('installMode');
|
||||
expect(debugInfo).toHaveProperty('packageRoot');
|
||||
expect(debugInfo).toHaveProperty('currentWorkingDirectory');
|
||||
expect(debugInfo).toHaveProperty('moduleDirectory');
|
||||
expect(debugInfo).toHaveProperty('environment');
|
||||
expect(debugInfo).toHaveProperty('cacheSize');
|
||||
|
||||
expect(debugInfo.environment).toHaveProperty('NODE_ENV');
|
||||
expect(debugInfo.environment).toHaveProperty('npm_execpath');
|
||||
expect(debugInfo.environment).toHaveProperty('npm_config_cache');
|
||||
});
|
||||
});
|
||||
const debugInfo = packageProtocol.getDebugInfo()
|
||||
|
||||
expect(debugInfo).toHaveProperty('protocol', 'package')
|
||||
expect(debugInfo).toHaveProperty('installMode')
|
||||
expect(debugInfo).toHaveProperty('packageRoot')
|
||||
expect(debugInfo).toHaveProperty('currentWorkingDirectory')
|
||||
expect(debugInfo).toHaveProperty('moduleDirectory')
|
||||
expect(debugInfo).toHaveProperty('environment')
|
||||
expect(debugInfo).toHaveProperty('cacheSize')
|
||||
|
||||
expect(debugInfo.environment).toHaveProperty('NODE_ENV')
|
||||
expect(debugInfo.environment).toHaveProperty('npm_execpath')
|
||||
expect(debugInfo.environment).toHaveProperty('npm_config_cache')
|
||||
})
|
||||
})
|
||||
|
||||
describe('缓存管理', () => {
|
||||
test('应该能清理所有缓存', async () => {
|
||||
// 生成一些缓存
|
||||
await packageProtocol.resolvePath('package.json');
|
||||
packageProtocol.detectInstallMode();
|
||||
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(0);
|
||||
expect(packageProtocol.installModeCache.size).toBeGreaterThan(0);
|
||||
|
||||
packageProtocol.clearCache();
|
||||
|
||||
expect(packageProtocol.cache.size).toBe(0);
|
||||
expect(packageProtocol.installModeCache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
await packageProtocol.resolvePath('package.json')
|
||||
packageProtocol.detectInstallMode()
|
||||
|
||||
expect(packageProtocol.cache.size).toBeGreaterThan(0)
|
||||
expect(packageProtocol.installModeCache.size).toBeGreaterThan(0)
|
||||
|
||||
packageProtocol.clearCache()
|
||||
|
||||
expect(packageProtocol.cache.size).toBe(0)
|
||||
expect(packageProtocol.installModeCache.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('错误处理', () => {
|
||||
test('文件系统错误应该被正确处理', async () => {
|
||||
// 尝试访问一个权限不足的路径(如果存在的话)
|
||||
const result = await packageProtocol.exists('../../../root/.ssh/id_rsa');
|
||||
expect(typeof result).toBe('boolean');
|
||||
});
|
||||
const result = await packageProtocol.exists('../../../root/.ssh/id_rsa')
|
||||
expect(typeof result).toBe('boolean')
|
||||
})
|
||||
|
||||
test('路径解析错误应该包含有用信息', async () => {
|
||||
try {
|
||||
await packageProtocol.resolvePath('../../../etc/passwd');
|
||||
await packageProtocol.resolvePath('../../../etc/passwd')
|
||||
} catch (error) {
|
||||
expect(error.message).toContain('路径安全检查失败');
|
||||
expect(error.message).toContain('路径安全检查失败')
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('边界情况', () => {
|
||||
test('深层嵌套路径应该正确处理', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('src/lib/core/resource/protocols/test.js');
|
||||
expect(resolved).toContain('src');
|
||||
expect(resolved).toContain('lib');
|
||||
expect(resolved).toContain('core');
|
||||
expect(resolved).toContain('resource');
|
||||
expect(resolved).toContain('protocols');
|
||||
expect(resolved).toMatch(/test\.js$/);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('src/lib/core/resource/protocols/test.js')
|
||||
expect(resolved).toContain('src')
|
||||
expect(resolved).toContain('lib')
|
||||
expect(resolved).toContain('core')
|
||||
expect(resolved).toContain('resource')
|
||||
expect(resolved).toContain('protocols')
|
||||
expect(resolved).toMatch(/test\.js$/)
|
||||
})
|
||||
|
||||
test('特殊字符路径应该被正确处理', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('assets/images/logo-2024.png');
|
||||
expect(resolved).toContain('assets');
|
||||
expect(resolved).toContain('images');
|
||||
expect(resolved).toMatch(/logo-2024\.png$/);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('assets/images/logo-2024.png')
|
||||
expect(resolved).toContain('assets')
|
||||
expect(resolved).toContain('images')
|
||||
expect(resolved).toMatch(/logo-2024\.png$/)
|
||||
})
|
||||
|
||||
test('带有空格的路径应该被正确处理', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('docs/user guide.md');
|
||||
expect(resolved).toContain('docs');
|
||||
expect(resolved).toMatch(/user guide\.md$/);
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('docs/user guide.md')
|
||||
expect(resolved).toContain('docs')
|
||||
expect(resolved).toMatch(/user guide\.md$/)
|
||||
})
|
||||
|
||||
test('中文路径应该被正确处理', async () => {
|
||||
const resolved = await packageProtocol.resolvePath('文档/说明.md');
|
||||
expect(resolved).toContain('文档');
|
||||
expect(resolved).toMatch(/说明\.md$/);
|
||||
});
|
||||
});
|
||||
});
|
||||
const resolved = await packageProtocol.resolvePath('文档/说明.md')
|
||||
expect(resolved).toContain('文档')
|
||||
expect(resolved).toMatch(/说明\.md$/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,245 +1,245 @@
|
||||
const ProjectProtocol = require('../../../../lib/core/resource/protocols/ProjectProtocol');
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const ProjectProtocol = require('../../../../lib/core/resource/protocols/ProjectProtocol')
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types')
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
|
||||
describe('ProjectProtocol', () => {
|
||||
let projectProtocol;
|
||||
const projectRoot = process.cwd(); // PromptX项目根目录
|
||||
const promptxPath = path.join(projectRoot, '.promptx');
|
||||
let projectProtocol
|
||||
const projectRoot = process.cwd() // PromptX项目根目录
|
||||
const promptxPath = path.join(projectRoot, '.promptx')
|
||||
|
||||
beforeEach(() => {
|
||||
projectProtocol = new ProjectProtocol();
|
||||
});
|
||||
projectProtocol = new ProjectProtocol()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
projectProtocol.clearCache();
|
||||
});
|
||||
projectProtocol.clearCache()
|
||||
})
|
||||
|
||||
describe('基础功能', () => {
|
||||
test('应该正确初始化协议', () => {
|
||||
expect(projectProtocol.name).toBe('project');
|
||||
expect(projectProtocol.projectDirs).toBeDefined();
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('root');
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('src');
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('lib');
|
||||
});
|
||||
expect(projectProtocol.name).toBe('project')
|
||||
expect(projectProtocol.projectDirs).toBeDefined()
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('root')
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('src')
|
||||
expect(Object.keys(projectProtocol.projectDirs)).toContain('lib')
|
||||
})
|
||||
|
||||
test('应该提供协议信息', () => {
|
||||
const info = projectProtocol.getProtocolInfo();
|
||||
expect(info.name).toBe('project');
|
||||
expect(info.description).toContain('项目协议');
|
||||
expect(info.projectMarker).toBe('.promptx');
|
||||
expect(info.supportedDirectories).toContain('src');
|
||||
const info = projectProtocol.getProtocolInfo()
|
||||
expect(info.name).toBe('project')
|
||||
expect(info.description).toContain('项目协议')
|
||||
expect(info.projectMarker).toBe('.promptx')
|
||||
expect(info.supportedDirectories).toContain('src')
|
||||
expect(info.examples).toEqual(expect.arrayContaining([
|
||||
expect.stringContaining('project://src/')
|
||||
]));
|
||||
});
|
||||
]))
|
||||
})
|
||||
|
||||
test('应该提供支持的查询参数', () => {
|
||||
const params = projectProtocol.getSupportedParams();
|
||||
expect(params.from).toContain('指定搜索起始目录');
|
||||
expect(params.create).toContain('如果目录不存在是否创建');
|
||||
expect(params.line).toContain('行范围');
|
||||
});
|
||||
});
|
||||
const params = projectProtocol.getSupportedParams()
|
||||
expect(params.from).toContain('指定搜索起始目录')
|
||||
expect(params.create).toContain('如果目录不存在是否创建')
|
||||
expect(params.line).toContain('行范围')
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径验证', () => {
|
||||
test('应该验证有效的项目路径', () => {
|
||||
expect(projectProtocol.validatePath('src/index.js')).toBe(true);
|
||||
expect(projectProtocol.validatePath('lib/utils')).toBe(true);
|
||||
expect(projectProtocol.validatePath('docs')).toBe(true);
|
||||
expect(projectProtocol.validatePath('root/package.json')).toBe(true);
|
||||
});
|
||||
expect(projectProtocol.validatePath('src/index.js')).toBe(true)
|
||||
expect(projectProtocol.validatePath('lib/utils')).toBe(true)
|
||||
expect(projectProtocol.validatePath('docs')).toBe(true)
|
||||
expect(projectProtocol.validatePath('root/package.json')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的项目路径', () => {
|
||||
expect(projectProtocol.validatePath('invalid/path')).toBe(false);
|
||||
expect(projectProtocol.validatePath('unknown')).toBe(false);
|
||||
expect(projectProtocol.validatePath('')).toBe(false);
|
||||
expect(projectProtocol.validatePath(null)).toBe(false);
|
||||
});
|
||||
expect(projectProtocol.validatePath('invalid/path')).toBe(false)
|
||||
expect(projectProtocol.validatePath('unknown')).toBe(false)
|
||||
expect(projectProtocol.validatePath('')).toBe(false)
|
||||
expect(projectProtocol.validatePath(null)).toBe(false)
|
||||
})
|
||||
|
||||
test('应该验证项目目录类型', () => {
|
||||
const supportedDirs = Object.keys(projectProtocol.projectDirs);
|
||||
const supportedDirs = Object.keys(projectProtocol.projectDirs)
|
||||
supportedDirs.forEach(dir => {
|
||||
expect(projectProtocol.validatePath(`${dir}/test.js`)).toBe(true);
|
||||
expect(projectProtocol.validatePath(dir)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(projectProtocol.validatePath(`${dir}/test.js`)).toBe(true)
|
||||
expect(projectProtocol.validatePath(dir)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('项目根目录查找', () => {
|
||||
test('应该找到当前项目的根目录', async () => {
|
||||
const root = await projectProtocol.findProjectRoot();
|
||||
expect(root).toBe(projectRoot);
|
||||
});
|
||||
const root = await projectProtocol.findProjectRoot()
|
||||
expect(root).toBe(projectRoot)
|
||||
})
|
||||
|
||||
test('应该从子目录找到项目根目录', async () => {
|
||||
const subDir = path.join(projectRoot, 'src', 'lib');
|
||||
const root = await projectProtocol.findProjectRoot(subDir);
|
||||
expect(root).toBe(projectRoot);
|
||||
});
|
||||
const subDir = path.join(projectRoot, 'src', 'lib')
|
||||
const root = await projectProtocol.findProjectRoot(subDir)
|
||||
expect(root).toBe(projectRoot)
|
||||
})
|
||||
|
||||
test('应该缓存项目根目录结果', async () => {
|
||||
const root1 = await projectProtocol.findProjectRoot();
|
||||
const root2 = await projectProtocol.findProjectRoot();
|
||||
expect(root1).toBe(root2);
|
||||
expect(root1).toBe(projectRoot);
|
||||
});
|
||||
const root1 = await projectProtocol.findProjectRoot()
|
||||
const root2 = await projectProtocol.findProjectRoot()
|
||||
expect(root1).toBe(root2)
|
||||
expect(root1).toBe(projectRoot)
|
||||
})
|
||||
|
||||
test('应该处理未找到项目根目录的情况', async () => {
|
||||
// 使用系统临时目录测试
|
||||
const tempDir = '/tmp';
|
||||
const root = await projectProtocol.findProjectRoot(tempDir);
|
||||
expect(root).toBeNull();
|
||||
});
|
||||
});
|
||||
const tempDir = '/tmp'
|
||||
const root = await projectProtocol.findProjectRoot(tempDir)
|
||||
expect(root).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径解析', () => {
|
||||
test('应该解析src目录路径', async () => {
|
||||
const resolvedPath = await projectProtocol.resolvePath('src/index.js');
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'index.js'));
|
||||
});
|
||||
const resolvedPath = await projectProtocol.resolvePath('src/index.js')
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'index.js'))
|
||||
})
|
||||
|
||||
test('应该解析lib目录路径', async () => {
|
||||
const resolvedPath = await projectProtocol.resolvePath('lib/core/resource');
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'lib', 'core', 'resource'));
|
||||
});
|
||||
const resolvedPath = await projectProtocol.resolvePath('lib/core/resource')
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'lib', 'core', 'resource'))
|
||||
})
|
||||
|
||||
test('应该解析根目录路径', async () => {
|
||||
const resolvedPath = await projectProtocol.resolvePath('root/package.json');
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'package.json'));
|
||||
});
|
||||
const resolvedPath = await projectProtocol.resolvePath('root/package.json')
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'package.json'))
|
||||
})
|
||||
|
||||
test('应该解析目录路径(无文件名)', async () => {
|
||||
const resolvedPath = await projectProtocol.resolvePath('src');
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src'));
|
||||
});
|
||||
const resolvedPath = await projectProtocol.resolvePath('src')
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src'))
|
||||
})
|
||||
|
||||
test('应该拒绝不支持的目录类型', async () => {
|
||||
await expect(projectProtocol.resolvePath('invalid/path')).rejects.toThrow('不支持的项目目录类型');
|
||||
});
|
||||
await expect(projectProtocol.resolvePath('invalid/path')).rejects.toThrow('不支持的项目目录类型')
|
||||
})
|
||||
|
||||
test('应该处理安全路径检查', async () => {
|
||||
await expect(projectProtocol.resolvePath('src/../../../etc/passwd')).rejects.toThrow('安全错误');
|
||||
});
|
||||
await expect(projectProtocol.resolvePath('src/../../../etc/passwd')).rejects.toThrow('安全错误')
|
||||
})
|
||||
|
||||
test('应该支持from参数指定起始目录', async () => {
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('from', projectRoot);
|
||||
|
||||
const resolvedPath = await projectProtocol.resolvePath('src/test.js', queryParams);
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'test.js'));
|
||||
});
|
||||
});
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('from', projectRoot)
|
||||
|
||||
const resolvedPath = await projectProtocol.resolvePath('src/test.js', queryParams)
|
||||
expect(resolvedPath).toBe(path.join(projectRoot, 'src', 'test.js'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('内容加载', () => {
|
||||
test('应该加载存在的文件内容', async () => {
|
||||
const packageJsonPath = path.join(projectRoot, 'package.json');
|
||||
const content = await projectProtocol.loadFileContent(packageJsonPath);
|
||||
expect(content).toContain('promptx');
|
||||
});
|
||||
const packageJsonPath = path.join(projectRoot, 'package.json')
|
||||
const content = await projectProtocol.loadFileContent(packageJsonPath)
|
||||
expect(content).toContain('promptx')
|
||||
})
|
||||
|
||||
test('应该加载目录内容', async () => {
|
||||
const srcPath = path.join(projectRoot, 'src');
|
||||
const content = await projectProtocol.loadDirectoryContent(srcPath);
|
||||
expect(content).toContain('[DIR]');
|
||||
});
|
||||
const srcPath = path.join(projectRoot, 'src')
|
||||
const content = await projectProtocol.loadDirectoryContent(srcPath)
|
||||
expect(content).toContain('[DIR]')
|
||||
})
|
||||
|
||||
test('应该支持JSON格式的目录列表', async () => {
|
||||
const srcPath = path.join(projectRoot, 'src');
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('format', 'json');
|
||||
|
||||
const content = await projectProtocol.loadDirectoryContent(srcPath, queryParams);
|
||||
const parsed = JSON.parse(content);
|
||||
expect(Array.isArray(parsed)).toBe(true);
|
||||
});
|
||||
const srcPath = path.join(projectRoot, 'src')
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('format', 'json')
|
||||
|
||||
const content = await projectProtocol.loadDirectoryContent(srcPath, queryParams)
|
||||
const parsed = JSON.parse(content)
|
||||
expect(Array.isArray(parsed)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该支持类型过滤', async () => {
|
||||
const rootPath = projectRoot;
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('type', 'file');
|
||||
|
||||
const content = await projectProtocol.loadDirectoryContent(rootPath, queryParams);
|
||||
expect(content).toContain('[FILE]');
|
||||
expect(content).not.toContain('[DIR]');
|
||||
});
|
||||
const rootPath = projectRoot
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('type', 'file')
|
||||
|
||||
const content = await projectProtocol.loadDirectoryContent(rootPath, queryParams)
|
||||
expect(content).toContain('[FILE]')
|
||||
expect(content).not.toContain('[DIR]')
|
||||
})
|
||||
|
||||
test('应该处理不存在的文件', async () => {
|
||||
const nonExistentPath = path.join(projectRoot, 'nonexistent.txt');
|
||||
await expect(projectProtocol.loadContent(nonExistentPath)).rejects.toThrow('文件或目录不存在');
|
||||
});
|
||||
const nonExistentPath = path.join(projectRoot, 'nonexistent.txt')
|
||||
await expect(projectProtocol.loadContent(nonExistentPath)).rejects.toThrow('文件或目录不存在')
|
||||
})
|
||||
|
||||
test('应该支持exists=false参数', async () => {
|
||||
const nonExistentPath = path.join(projectRoot, 'nonexistent.txt');
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('exists', 'false');
|
||||
|
||||
const content = await projectProtocol.loadContent(nonExistentPath, queryParams);
|
||||
expect(content).toBe('');
|
||||
});
|
||||
});
|
||||
const nonExistentPath = path.join(projectRoot, 'nonexistent.txt')
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('exists', 'false')
|
||||
|
||||
const content = await projectProtocol.loadContent(nonExistentPath, queryParams)
|
||||
expect(content).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('完整协议解析', () => {
|
||||
test('应该完整解析project://协议', async () => {
|
||||
const content = await projectProtocol.resolve('root/package.json');
|
||||
expect(content).toContain('promptx');
|
||||
});
|
||||
const content = await projectProtocol.resolve('root/package.json')
|
||||
expect(content).toContain('promptx')
|
||||
})
|
||||
|
||||
test('应该处理带查询参数的协议', async () => {
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('format', 'json');
|
||||
|
||||
const content = await projectProtocol.resolve('src', queryParams);
|
||||
const parsed = JSON.parse(content);
|
||||
expect(Array.isArray(parsed)).toBe(true);
|
||||
});
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('format', 'json')
|
||||
|
||||
const content = await projectProtocol.resolve('src', queryParams)
|
||||
const parsed = JSON.parse(content)
|
||||
expect(Array.isArray(parsed)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该应用行过滤', async () => {
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('line', '1-3');
|
||||
|
||||
const content = await projectProtocol.resolve('root/package.json', queryParams);
|
||||
const lines = content.split('\n');
|
||||
expect(lines.length).toBe(3);
|
||||
});
|
||||
});
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('line', '1-3')
|
||||
|
||||
const content = await projectProtocol.resolve('root/package.json', queryParams)
|
||||
const lines = content.split('\n')
|
||||
expect(lines.length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe('项目信息', () => {
|
||||
test('应该获取项目信息', async () => {
|
||||
const info = await projectProtocol.getProjectInfo();
|
||||
expect(info.projectRoot).toBe(projectRoot);
|
||||
expect(info.promptxPath).toBe(promptxPath);
|
||||
expect(info.directories).toBeDefined();
|
||||
expect(info.directories.root.exists).toBe(true);
|
||||
expect(info.directories.src.exists).toBe(true);
|
||||
});
|
||||
const info = await projectProtocol.getProjectInfo()
|
||||
expect(info.projectRoot).toBe(projectRoot)
|
||||
expect(info.promptxPath).toBe(promptxPath)
|
||||
expect(info.directories).toBeDefined()
|
||||
expect(info.directories.root.exists).toBe(true)
|
||||
expect(info.directories.src.exists).toBe(true)
|
||||
})
|
||||
|
||||
test('应该标识不存在的目录', async () => {
|
||||
const info = await projectProtocol.getProjectInfo();
|
||||
const info = await projectProtocol.getProjectInfo()
|
||||
// 有些目录可能不存在,应该正确标识
|
||||
Object.values(info.directories).forEach(dir => {
|
||||
expect(dir).toHaveProperty('exists');
|
||||
expect(dir).toHaveProperty('path');
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(dir).toHaveProperty('exists')
|
||||
expect(dir).toHaveProperty('path')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('缓存管理', () => {
|
||||
test('应该提供缓存统计', () => {
|
||||
const stats = projectProtocol.getCacheStats();
|
||||
expect(stats.protocol).toBe('project');
|
||||
expect(typeof stats.size).toBe('number');
|
||||
expect(typeof stats.enabled).toBe('boolean');
|
||||
});
|
||||
const stats = projectProtocol.getCacheStats()
|
||||
expect(stats.protocol).toBe('project')
|
||||
expect(typeof stats.size).toBe('number')
|
||||
expect(typeof stats.enabled).toBe('boolean')
|
||||
})
|
||||
|
||||
test('应该能清除缓存', async () => {
|
||||
await projectProtocol.findProjectRoot(); // 填充缓存
|
||||
expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0);
|
||||
|
||||
projectProtocol.clearCache();
|
||||
expect(projectProtocol.projectRootCache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
await projectProtocol.findProjectRoot() // 填充缓存
|
||||
expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0)
|
||||
|
||||
projectProtocol.clearCache()
|
||||
expect(projectProtocol.projectRootCache.size).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,338 +1,338 @@
|
||||
const PromptProtocol = require('../../../../lib/core/resource/protocols/PromptProtocol');
|
||||
const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol');
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types');
|
||||
const PromptProtocol = require('../../../../lib/core/resource/protocols/PromptProtocol')
|
||||
const PackageProtocol = require('../../../../lib/core/resource/protocols/PackageProtocol')
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types')
|
||||
|
||||
describe('PromptProtocol', () => {
|
||||
let promptProtocol;
|
||||
let mockPackageProtocol;
|
||||
let promptProtocol
|
||||
let mockPackageProtocol
|
||||
|
||||
beforeEach(() => {
|
||||
promptProtocol = new PromptProtocol();
|
||||
|
||||
promptProtocol = new PromptProtocol()
|
||||
|
||||
// 创建模拟的 PackageProtocol
|
||||
mockPackageProtocol = {
|
||||
getPackageRoot: jest.fn().mockResolvedValue('/mock/package/root'),
|
||||
loadContent: jest.fn(),
|
||||
exists: jest.fn()
|
||||
};
|
||||
|
||||
promptProtocol.setPackageProtocol(mockPackageProtocol);
|
||||
});
|
||||
}
|
||||
|
||||
promptProtocol.setPackageProtocol(mockPackageProtocol)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
promptProtocol.clearCache();
|
||||
});
|
||||
promptProtocol.clearCache()
|
||||
})
|
||||
|
||||
describe('基础功能', () => {
|
||||
test('应该正确初始化协议', () => {
|
||||
expect(promptProtocol.name).toBe('prompt');
|
||||
expect(promptProtocol.registry).toBeInstanceOf(Map);
|
||||
expect(promptProtocol.registry.size).toBeGreaterThan(0);
|
||||
});
|
||||
expect(promptProtocol.name).toBe('prompt')
|
||||
expect(promptProtocol.registry).toBeInstanceOf(Map)
|
||||
expect(promptProtocol.registry.size).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('应该提供协议信息', () => {
|
||||
const info = promptProtocol.getProtocolInfo();
|
||||
|
||||
expect(info.name).toBe('prompt');
|
||||
expect(info.description).toContain('PromptX内置提示词资源协议');
|
||||
expect(info.location).toBe('prompt://{resource_id}');
|
||||
expect(info.availableResources).toContain('protocols');
|
||||
expect(info.availableResources).toContain('core');
|
||||
expect(info.availableResources).toContain('domain');
|
||||
expect(info.availableResources).toContain('bootstrap');
|
||||
});
|
||||
const info = promptProtocol.getProtocolInfo()
|
||||
|
||||
expect(info.name).toBe('prompt')
|
||||
expect(info.description).toContain('PromptX内置提示词资源协议')
|
||||
expect(info.location).toBe('prompt://{resource_id}')
|
||||
expect(info.availableResources).toContain('protocols')
|
||||
expect(info.availableResources).toContain('core')
|
||||
expect(info.availableResources).toContain('domain')
|
||||
expect(info.availableResources).toContain('bootstrap')
|
||||
})
|
||||
|
||||
test('应该设置包协议依赖', () => {
|
||||
const newMockPackage = { test: 'protocol' };
|
||||
promptProtocol.setPackageProtocol(newMockPackage);
|
||||
expect(promptProtocol.packageProtocol).toBe(newMockPackage);
|
||||
});
|
||||
const newMockPackage = { test: 'protocol' }
|
||||
promptProtocol.setPackageProtocol(newMockPackage)
|
||||
expect(promptProtocol.packageProtocol).toBe(newMockPackage)
|
||||
})
|
||||
|
||||
test('应该提供支持的查询参数', () => {
|
||||
const params = promptProtocol.getSupportedParams();
|
||||
expect(params.merge).toContain('是否合并多个文件内容');
|
||||
expect(params.separator).toContain('文件间分隔符');
|
||||
expect(params.include_filename).toContain('是否包含文件名标题');
|
||||
});
|
||||
});
|
||||
const params = promptProtocol.getSupportedParams()
|
||||
expect(params.merge).toContain('是否合并多个文件内容')
|
||||
expect(params.separator).toContain('文件间分隔符')
|
||||
expect(params.include_filename).toContain('是否包含文件名标题')
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径验证', () => {
|
||||
test('应该验证有效的资源路径', () => {
|
||||
expect(promptProtocol.validatePath('protocols')).toBe(true);
|
||||
expect(promptProtocol.validatePath('core')).toBe(true);
|
||||
expect(promptProtocol.validatePath('domain')).toBe(true);
|
||||
expect(promptProtocol.validatePath('bootstrap')).toBe(true);
|
||||
});
|
||||
expect(promptProtocol.validatePath('protocols')).toBe(true)
|
||||
expect(promptProtocol.validatePath('core')).toBe(true)
|
||||
expect(promptProtocol.validatePath('domain')).toBe(true)
|
||||
expect(promptProtocol.validatePath('bootstrap')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的资源路径', () => {
|
||||
expect(promptProtocol.validatePath('invalid')).toBe(false);
|
||||
expect(promptProtocol.validatePath('unknown')).toBe(false);
|
||||
expect(promptProtocol.validatePath('')).toBe(false);
|
||||
expect(promptProtocol.validatePath(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
expect(promptProtocol.validatePath('invalid')).toBe(false)
|
||||
expect(promptProtocol.validatePath('unknown')).toBe(false)
|
||||
expect(promptProtocol.validatePath('')).toBe(false)
|
||||
expect(promptProtocol.validatePath(null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径解析', () => {
|
||||
test('应该解析有效的资源路径', async () => {
|
||||
const resolved = await promptProtocol.resolvePath('protocols');
|
||||
expect(resolved).toBe('@package://prompt/protocol/**/*.md');
|
||||
});
|
||||
const resolved = await promptProtocol.resolvePath('protocols')
|
||||
expect(resolved).toBe('@package://prompt/protocol/**/*.md')
|
||||
})
|
||||
|
||||
test('应该解析所有注册的资源', async () => {
|
||||
for (const resourceId of promptProtocol.registry.keys()) {
|
||||
const resolved = await promptProtocol.resolvePath(resourceId);
|
||||
expect(resolved).toMatch(/@package:\/\//);
|
||||
const resolved = await promptProtocol.resolvePath(resourceId)
|
||||
expect(resolved).toMatch(/@package:\/\//)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
test('应该拒绝未注册的资源', async () => {
|
||||
await expect(promptProtocol.resolvePath('nonexistent'))
|
||||
.rejects.toThrow('未找到 prompt 资源: nonexistent');
|
||||
});
|
||||
});
|
||||
.rejects.toThrow('未找到 prompt 资源: nonexistent')
|
||||
})
|
||||
})
|
||||
|
||||
describe('单个文件加载', () => {
|
||||
test('应该加载单个文件', async () => {
|
||||
const mockContent = '# Test Content\n\nThis is test content.';
|
||||
const mockContent = '# Test Content\n\nThis is test content.'
|
||||
mockPackageProtocol.loadContent.mockResolvedValue({
|
||||
content: mockContent,
|
||||
metadata: { path: '/test/path' }
|
||||
});
|
||||
})
|
||||
|
||||
const content = await promptProtocol.loadSingleFile('@package://bootstrap.md');
|
||||
|
||||
expect(content).toBe(mockContent);
|
||||
expect(mockPackageProtocol.loadContent).toHaveBeenCalledWith('bootstrap.md', undefined);
|
||||
});
|
||||
const content = await promptProtocol.loadSingleFile('@package://bootstrap.md')
|
||||
|
||||
expect(content).toBe(mockContent)
|
||||
expect(mockPackageProtocol.loadContent).toHaveBeenCalledWith('bootstrap.md', undefined)
|
||||
})
|
||||
|
||||
test('应该处理加载错误', async () => {
|
||||
mockPackageProtocol.loadContent.mockRejectedValue(new Error('File not found'));
|
||||
mockPackageProtocol.loadContent.mockRejectedValue(new Error('File not found'))
|
||||
|
||||
await expect(promptProtocol.loadSingleFile('@package://nonexistent.md'))
|
||||
.rejects.toThrow('加载单个文件失败');
|
||||
});
|
||||
});
|
||||
.rejects.toThrow('加载单个文件失败')
|
||||
})
|
||||
})
|
||||
|
||||
describe('多个文件加载', () => {
|
||||
test('应该加载多个文件并合并', async () => {
|
||||
const fs = require('fs').promises;
|
||||
const glob = require('glob');
|
||||
|
||||
const fs = require('fs').promises
|
||||
const glob = require('glob')
|
||||
|
||||
// 模拟 glob 返回文件列表
|
||||
const mockFiles = [
|
||||
'/mock/package/root/prompt/protocol/dpml.protocol.md',
|
||||
'/mock/package/root/prompt/protocol/pateoas.protocol.md'
|
||||
];
|
||||
|
||||
]
|
||||
|
||||
jest.doMock('glob', () => ({
|
||||
...jest.requireActual('glob'),
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation((pattern, options, callback) => {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
callback(null, mockFiles);
|
||||
callback(null, mockFiles)
|
||||
})
|
||||
}));
|
||||
}))
|
||||
|
||||
// 模拟文件读取
|
||||
jest.spyOn(fs, 'readFile').mockImplementation((filePath) => {
|
||||
if (filePath.includes('dpml.protocol.md')) {
|
||||
return Promise.resolve('# DPML Protocol\n\nDPML content...');
|
||||
return Promise.resolve('# DPML Protocol\n\nDPML content...')
|
||||
} else if (filePath.includes('pateoas.protocol.md')) {
|
||||
return Promise.resolve('# PATEOAS Protocol\n\nPATEOAS content...');
|
||||
return Promise.resolve('# PATEOAS Protocol\n\nPATEOAS content...')
|
||||
}
|
||||
return Promise.reject(new Error('File not found'));
|
||||
});
|
||||
return Promise.reject(new Error('File not found'))
|
||||
})
|
||||
|
||||
const content = await promptProtocol.loadMultipleFiles('@package://prompt/protocol/**/*.md');
|
||||
|
||||
expect(content).toContain('# DPML Protocol');
|
||||
expect(content).toContain('# PATEOAS Protocol');
|
||||
expect(content).toContain('prompt/protocol/dpml.protocol.md');
|
||||
expect(content).toContain('prompt/protocol/pateoas.protocol.md');
|
||||
const content = await promptProtocol.loadMultipleFiles('@package://prompt/protocol/**/*.md')
|
||||
|
||||
expect(content).toContain('# DPML Protocol')
|
||||
expect(content).toContain('# PATEOAS Protocol')
|
||||
expect(content).toContain('prompt/protocol/dpml.protocol.md')
|
||||
expect(content).toContain('prompt/protocol/pateoas.protocol.md')
|
||||
|
||||
// 清理模拟
|
||||
fs.readFile.mockRestore();
|
||||
});
|
||||
fs.readFile.mockRestore()
|
||||
})
|
||||
|
||||
test('应该处理没有匹配文件的情况', async () => {
|
||||
const glob = require('glob');
|
||||
|
||||
const glob = require('glob')
|
||||
|
||||
jest.doMock('glob', () => ({
|
||||
...jest.requireActual('glob'),
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation((pattern, options, callback) => {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
callback(null, []); // 返回空文件列表
|
||||
callback(null, []) // 返回空文件列表
|
||||
})
|
||||
}));
|
||||
}))
|
||||
|
||||
await expect(promptProtocol.loadMultipleFiles('@package://prompt/nonexistent/**/*.md'))
|
||||
.rejects.toThrow('没有找到匹配的文件');
|
||||
});
|
||||
});
|
||||
.rejects.toThrow('没有找到匹配的文件')
|
||||
})
|
||||
})
|
||||
|
||||
describe('内容合并', () => {
|
||||
test('应该合并多个文件内容', () => {
|
||||
const contents = [
|
||||
{ path: 'file1.md', content: '# File 1\n\nContent 1' },
|
||||
{ path: 'file2.md', content: '# File 2\n\nContent 2' }
|
||||
];
|
||||
]
|
||||
|
||||
const merged = promptProtocol.mergeContents(contents);
|
||||
|
||||
expect(merged).toContain('# file1.md');
|
||||
expect(merged).toContain('# File 1');
|
||||
expect(merged).toContain('# file2.md');
|
||||
expect(merged).toContain('# File 2');
|
||||
expect(merged).toContain('---');
|
||||
});
|
||||
const merged = promptProtocol.mergeContents(contents)
|
||||
|
||||
expect(merged).toContain('# file1.md')
|
||||
expect(merged).toContain('# File 1')
|
||||
expect(merged).toContain('# file2.md')
|
||||
expect(merged).toContain('# File 2')
|
||||
expect(merged).toContain('---')
|
||||
})
|
||||
|
||||
test('应该支持不合并返回JSON', () => {
|
||||
const contents = [
|
||||
{ path: 'file1.md', content: 'Content 1' },
|
||||
{ path: 'file2.md', content: 'Content 2' }
|
||||
];
|
||||
]
|
||||
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('merge', 'false');
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('merge', 'false')
|
||||
|
||||
const result = promptProtocol.mergeContents(contents, queryParams);
|
||||
|
||||
expect(() => JSON.parse(result)).not.toThrow();
|
||||
const parsed = JSON.parse(result);
|
||||
expect(parsed).toHaveLength(2);
|
||||
expect(parsed[0]).toHaveProperty('path', 'file1.md');
|
||||
expect(parsed[0]).toHaveProperty('content', 'Content 1');
|
||||
});
|
||||
const result = promptProtocol.mergeContents(contents, queryParams)
|
||||
|
||||
expect(() => JSON.parse(result)).not.toThrow()
|
||||
const parsed = JSON.parse(result)
|
||||
expect(parsed).toHaveLength(2)
|
||||
expect(parsed[0]).toHaveProperty('path', 'file1.md')
|
||||
expect(parsed[0]).toHaveProperty('content', 'Content 1')
|
||||
})
|
||||
|
||||
test('应该支持自定义分隔符', () => {
|
||||
const contents = [
|
||||
{ path: 'file1.md', content: 'Content 1' },
|
||||
{ path: 'file2.md', content: 'Content 2' }
|
||||
];
|
||||
]
|
||||
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('separator', '\n\n===\n\n');
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('separator', '\n\n===\n\n')
|
||||
|
||||
const result = promptProtocol.mergeContents(contents, queryParams);
|
||||
|
||||
expect(result).toContain('===');
|
||||
expect(result).not.toContain('---');
|
||||
});
|
||||
const result = promptProtocol.mergeContents(contents, queryParams)
|
||||
|
||||
expect(result).toContain('===')
|
||||
expect(result).not.toContain('---')
|
||||
})
|
||||
|
||||
test('应该支持隐藏文件名', () => {
|
||||
const contents = [
|
||||
{ path: 'file1.md', content: 'Content 1' }
|
||||
];
|
||||
]
|
||||
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('include_filename', 'false');
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('include_filename', 'false')
|
||||
|
||||
const result = promptProtocol.mergeContents(contents, queryParams);
|
||||
|
||||
expect(result).not.toContain('# file1.md');
|
||||
expect(result).toContain('Content 1');
|
||||
});
|
||||
});
|
||||
const result = promptProtocol.mergeContents(contents, queryParams)
|
||||
|
||||
expect(result).not.toContain('# file1.md')
|
||||
expect(result).toContain('Content 1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('资源存在性检查', () => {
|
||||
test('应该检查单个文件是否存在', async () => {
|
||||
mockPackageProtocol.exists.mockResolvedValue(true);
|
||||
mockPackageProtocol.exists.mockResolvedValue(true)
|
||||
|
||||
const exists = await promptProtocol.exists('bootstrap');
|
||||
|
||||
expect(exists).toBe(true);
|
||||
expect(mockPackageProtocol.exists).toHaveBeenCalledWith('bootstrap.md', undefined);
|
||||
});
|
||||
const exists = await promptProtocol.exists('bootstrap')
|
||||
|
||||
expect(exists).toBe(true)
|
||||
expect(mockPackageProtocol.exists).toHaveBeenCalledWith('bootstrap.md', undefined)
|
||||
})
|
||||
|
||||
test('应该检查通配符文件是否存在', async () => {
|
||||
const glob = require('glob');
|
||||
|
||||
const glob = require('glob')
|
||||
|
||||
jest.doMock('glob', () => ({
|
||||
...jest.requireActual('glob'),
|
||||
__esModule: true,
|
||||
default: jest.fn().mockImplementation((pattern, options, callback) => {
|
||||
if (typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
callback = options
|
||||
options = {}
|
||||
}
|
||||
callback(null, ['/mock/file1.md', '/mock/file2.md']);
|
||||
callback(null, ['/mock/file1.md', '/mock/file2.md'])
|
||||
})
|
||||
}));
|
||||
}))
|
||||
|
||||
const exists = await promptProtocol.exists('protocols');
|
||||
|
||||
expect(exists).toBe(true);
|
||||
});
|
||||
const exists = await promptProtocol.exists('protocols')
|
||||
|
||||
expect(exists).toBe(true)
|
||||
})
|
||||
|
||||
test('应该处理不存在的资源', async () => {
|
||||
const exists = await promptProtocol.exists('nonexistent');
|
||||
|
||||
expect(exists).toBe(false);
|
||||
});
|
||||
});
|
||||
const exists = await promptProtocol.exists('nonexistent')
|
||||
|
||||
expect(exists).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('完整协议解析', () => {
|
||||
test('应该完整加载内容', async () => {
|
||||
mockPackageProtocol.loadContent.mockResolvedValue({
|
||||
content: '# Bootstrap Content\n\nThis is bootstrap.',
|
||||
metadata: {}
|
||||
});
|
||||
})
|
||||
|
||||
const content = await promptProtocol.loadContent('@package://bootstrap.md');
|
||||
|
||||
expect(content).toBe('# Bootstrap Content\n\nThis is bootstrap.');
|
||||
});
|
||||
const content = await promptProtocol.loadContent('@package://bootstrap.md')
|
||||
|
||||
expect(content).toBe('# Bootstrap Content\n\nThis is bootstrap.')
|
||||
})
|
||||
|
||||
test('应该处理缺少依赖的情况', async () => {
|
||||
const newProtocol = new PromptProtocol();
|
||||
const newProtocol = new PromptProtocol()
|
||||
// 不设置 packageProtocol 依赖
|
||||
|
||||
await expect(newProtocol.loadContent('@package://test.md'))
|
||||
.rejects.toThrow('PromptProtocol 需要 PackageProtocol 依赖');
|
||||
});
|
||||
});
|
||||
.rejects.toThrow('PromptProtocol 需要 PackageProtocol 依赖')
|
||||
})
|
||||
})
|
||||
|
||||
describe('工具方法', () => {
|
||||
test('应该列出所有可用资源', () => {
|
||||
const resources = promptProtocol.listResources();
|
||||
|
||||
expect(resources).toBeInstanceOf(Array);
|
||||
expect(resources.length).toBeGreaterThan(0);
|
||||
|
||||
const protocolsResource = resources.find(r => r.id === 'protocols');
|
||||
expect(protocolsResource).toBeDefined();
|
||||
expect(protocolsResource.description).toContain('DPML协议规范文档');
|
||||
});
|
||||
const resources = promptProtocol.listResources()
|
||||
|
||||
expect(resources).toBeInstanceOf(Array)
|
||||
expect(resources.length).toBeGreaterThan(0)
|
||||
|
||||
const protocolsResource = resources.find(r => r.id === 'protocols')
|
||||
expect(protocolsResource).toBeDefined()
|
||||
expect(protocolsResource.description).toContain('DPML协议规范文档')
|
||||
})
|
||||
|
||||
test('应该获取资源描述', () => {
|
||||
expect(promptProtocol.getResourceDescription('protocols')).toContain('DPML协议规范文档');
|
||||
expect(promptProtocol.getResourceDescription('core')).toContain('核心思维和执行模式');
|
||||
expect(promptProtocol.getResourceDescription('unknown')).toBe('未知资源');
|
||||
});
|
||||
});
|
||||
expect(promptProtocol.getResourceDescription('protocols')).toContain('DPML协议规范文档')
|
||||
expect(promptProtocol.getResourceDescription('core')).toContain('核心思维和执行模式')
|
||||
expect(promptProtocol.getResourceDescription('unknown')).toBe('未知资源')
|
||||
})
|
||||
})
|
||||
|
||||
describe('缓存管理', () => {
|
||||
test('应该提供缓存统计', () => {
|
||||
const stats = promptProtocol.getCacheStats();
|
||||
expect(stats.protocol).toBe('prompt');
|
||||
expect(typeof stats.size).toBe('number');
|
||||
expect(typeof stats.enabled).toBe('boolean');
|
||||
});
|
||||
const stats = promptProtocol.getCacheStats()
|
||||
expect(stats.protocol).toBe('prompt')
|
||||
expect(typeof stats.size).toBe('number')
|
||||
expect(typeof stats.enabled).toBe('boolean')
|
||||
})
|
||||
|
||||
test('应该能清除缓存', () => {
|
||||
// 模拟一些缓存数据
|
||||
promptProtocol.cache.set('test', 'value');
|
||||
expect(promptProtocol.cache.size).toBeGreaterThan(0);
|
||||
|
||||
promptProtocol.clearCache();
|
||||
expect(promptProtocol.cache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
promptProtocol.cache.set('test', 'value')
|
||||
expect(promptProtocol.cache.size).toBeGreaterThan(0)
|
||||
|
||||
promptProtocol.clearCache()
|
||||
expect(promptProtocol.cache.size).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,232 +1,232 @@
|
||||
const UserProtocol = require('../../../../lib/core/resource/protocols/UserProtocol');
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const UserProtocol = require('../../../../lib/core/resource/protocols/UserProtocol')
|
||||
const { QueryParams } = require('../../../../lib/core/resource/types')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
describe('UserProtocol', () => {
|
||||
let userProtocol;
|
||||
let userProtocol
|
||||
|
||||
beforeEach(() => {
|
||||
userProtocol = new UserProtocol();
|
||||
});
|
||||
userProtocol = new UserProtocol()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
userProtocol.clearCache();
|
||||
});
|
||||
userProtocol.clearCache()
|
||||
})
|
||||
|
||||
describe('基础功能', () => {
|
||||
test('应该正确初始化协议', () => {
|
||||
expect(userProtocol.name).toBe('user');
|
||||
expect(userProtocol.userDirs).toBeDefined();
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('home');
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('documents');
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('desktop');
|
||||
});
|
||||
expect(userProtocol.name).toBe('user')
|
||||
expect(userProtocol.userDirs).toBeDefined()
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('home')
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('documents')
|
||||
expect(Object.keys(userProtocol.userDirs)).toContain('desktop')
|
||||
})
|
||||
|
||||
test('应该提供协议信息', () => {
|
||||
const info = userProtocol.getProtocolInfo();
|
||||
expect(info.name).toBe('user');
|
||||
expect(info.description).toBeDefined();
|
||||
expect(info.location).toBe('user://{directory}/{path}');
|
||||
expect(info.examples).toBeInstanceOf(Array);
|
||||
expect(info.supportedDirectories).toContain('home');
|
||||
});
|
||||
const info = userProtocol.getProtocolInfo()
|
||||
expect(info.name).toBe('user')
|
||||
expect(info.description).toBeDefined()
|
||||
expect(info.location).toBe('user://{directory}/{path}')
|
||||
expect(info.examples).toBeInstanceOf(Array)
|
||||
expect(info.supportedDirectories).toContain('home')
|
||||
})
|
||||
|
||||
test('应该提供支持的参数列表', () => {
|
||||
const params = userProtocol.getSupportedParams();
|
||||
expect(params.line).toBeDefined();
|
||||
expect(params.format).toBeDefined();
|
||||
expect(params.exists).toBeDefined();
|
||||
expect(params.type).toBeDefined();
|
||||
});
|
||||
});
|
||||
const params = userProtocol.getSupportedParams()
|
||||
expect(params.line).toBeDefined()
|
||||
expect(params.format).toBeDefined()
|
||||
expect(params.exists).toBeDefined()
|
||||
expect(params.type).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径验证', () => {
|
||||
test('应该验证有效的用户目录路径', () => {
|
||||
expect(userProtocol.validatePath('home')).toBe(true);
|
||||
expect(userProtocol.validatePath('documents/notes.txt')).toBe(true);
|
||||
expect(userProtocol.validatePath('desktop/readme.md')).toBe(true);
|
||||
expect(userProtocol.validatePath('downloads/')).toBe(true);
|
||||
});
|
||||
expect(userProtocol.validatePath('home')).toBe(true)
|
||||
expect(userProtocol.validatePath('documents/notes.txt')).toBe(true)
|
||||
expect(userProtocol.validatePath('desktop/readme.md')).toBe(true)
|
||||
expect(userProtocol.validatePath('downloads/')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的用户目录路径', () => {
|
||||
expect(userProtocol.validatePath('invalid')).toBe(false);
|
||||
expect(userProtocol.validatePath('unknown/path')).toBe(false);
|
||||
expect(userProtocol.validatePath('')).toBe(false);
|
||||
expect(userProtocol.validatePath(null)).toBe(false);
|
||||
});
|
||||
});
|
||||
expect(userProtocol.validatePath('invalid')).toBe(false)
|
||||
expect(userProtocol.validatePath('unknown/path')).toBe(false)
|
||||
expect(userProtocol.validatePath('')).toBe(false)
|
||||
expect(userProtocol.validatePath(null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('路径解析', () => {
|
||||
test('应该解析home目录', async () => {
|
||||
const resolved = await userProtocol.resolvePath('home');
|
||||
expect(resolved).toBe(os.homedir());
|
||||
});
|
||||
const resolved = await userProtocol.resolvePath('home')
|
||||
expect(resolved).toBe(os.homedir())
|
||||
})
|
||||
|
||||
test('应该解析documents目录', async () => {
|
||||
const resolved = await userProtocol.resolvePath('documents');
|
||||
expect(resolved).toContain('Documents');
|
||||
expect(path.isAbsolute(resolved)).toBe(true);
|
||||
});
|
||||
const resolved = await userProtocol.resolvePath('documents')
|
||||
expect(resolved).toContain('Documents')
|
||||
expect(path.isAbsolute(resolved)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该解析带子路径的文件', async () => {
|
||||
const resolved = await userProtocol.resolvePath('documents/notes.txt');
|
||||
expect(resolved).toContain('Documents');
|
||||
expect(resolved).toContain('notes.txt');
|
||||
expect(path.isAbsolute(resolved)).toBe(true);
|
||||
});
|
||||
const resolved = await userProtocol.resolvePath('documents/notes.txt')
|
||||
expect(resolved).toContain('Documents')
|
||||
expect(resolved).toContain('notes.txt')
|
||||
expect(path.isAbsolute(resolved)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝不支持的目录类型', async () => {
|
||||
await expect(userProtocol.resolvePath('invalid/path'))
|
||||
.rejects.toThrow('不支持的用户目录类型');
|
||||
});
|
||||
.rejects.toThrow('不支持的用户目录类型')
|
||||
})
|
||||
|
||||
test('应该防止路径穿越攻击', async () => {
|
||||
await expect(userProtocol.resolvePath('documents/../../../etc/passwd'))
|
||||
.rejects.toThrow('安全错误:路径超出用户目录范围');
|
||||
});
|
||||
});
|
||||
.rejects.toThrow('安全错误:路径超出用户目录范围')
|
||||
})
|
||||
})
|
||||
|
||||
describe('用户目录获取', () => {
|
||||
test('应该获取所有支持的用户目录', async () => {
|
||||
const directories = await userProtocol.listUserDirectories();
|
||||
|
||||
expect(directories.home).toBeDefined();
|
||||
expect(directories.documents).toBeDefined();
|
||||
expect(directories.desktop).toBeDefined();
|
||||
expect(directories.downloads).toBeDefined();
|
||||
|
||||
const directories = await userProtocol.listUserDirectories()
|
||||
|
||||
expect(directories.home).toBeDefined()
|
||||
expect(directories.documents).toBeDefined()
|
||||
expect(directories.desktop).toBeDefined()
|
||||
expect(directories.downloads).toBeDefined()
|
||||
|
||||
// 检查路径是否为绝对路径
|
||||
expect(path.isAbsolute(directories.home)).toBe(true);
|
||||
});
|
||||
expect(path.isAbsolute(directories.home)).toBe(true)
|
||||
})
|
||||
|
||||
test('应该缓存目录路径', async () => {
|
||||
// 第一次调用
|
||||
const dir1 = await userProtocol.getUserDirectory('home');
|
||||
expect(userProtocol.dirCache.has('home')).toBe(true);
|
||||
|
||||
const dir1 = await userProtocol.getUserDirectory('home')
|
||||
expect(userProtocol.dirCache.has('home')).toBe(true)
|
||||
|
||||
// 第二次调用应该从缓存获取
|
||||
const dir2 = await userProtocol.getUserDirectory('home');
|
||||
expect(dir1).toBe(dir2);
|
||||
});
|
||||
});
|
||||
const dir2 = await userProtocol.getUserDirectory('home')
|
||||
expect(dir1).toBe(dir2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('内容加载', () => {
|
||||
test('应该加载目录内容', async () => {
|
||||
// 使用home目录进行测试(应该总是存在)
|
||||
const homePath = await userProtocol.resolvePath('home');
|
||||
const content = await userProtocol.loadContent(homePath);
|
||||
|
||||
expect(typeof content).toBe('string');
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
});
|
||||
const homePath = await userProtocol.resolvePath('home')
|
||||
const content = await userProtocol.loadContent(homePath)
|
||||
|
||||
expect(typeof content).toBe('string')
|
||||
expect(content.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('应该支持不同的目录格式化选项', async () => {
|
||||
const homePath = await userProtocol.resolvePath('home');
|
||||
const queryParams = new QueryParams();
|
||||
|
||||
const homePath = await userProtocol.resolvePath('home')
|
||||
const queryParams = new QueryParams()
|
||||
|
||||
// 测试json格式
|
||||
queryParams.set('format', 'json');
|
||||
const jsonContent = await userProtocol.loadContent(homePath, queryParams);
|
||||
expect(() => JSON.parse(jsonContent)).not.toThrow();
|
||||
|
||||
queryParams.set('format', 'json')
|
||||
const jsonContent = await userProtocol.loadContent(homePath, queryParams)
|
||||
expect(() => JSON.parse(jsonContent)).not.toThrow()
|
||||
|
||||
// 测试paths格式
|
||||
queryParams.set('format', 'paths');
|
||||
const pathsContent = await userProtocol.loadContent(homePath, queryParams);
|
||||
expect(typeof pathsContent).toBe('string');
|
||||
});
|
||||
queryParams.set('format', 'paths')
|
||||
const pathsContent = await userProtocol.loadContent(homePath, queryParams)
|
||||
expect(typeof pathsContent).toBe('string')
|
||||
})
|
||||
|
||||
test('应该处理不存在的文件', async () => {
|
||||
const nonExistentPath = await userProtocol.resolvePath('documents/non-existent-file.txt');
|
||||
|
||||
const nonExistentPath = await userProtocol.resolvePath('documents/non-existent-file.txt')
|
||||
|
||||
// 默认情况下应该抛出错误
|
||||
await expect(userProtocol.loadContent(nonExistentPath))
|
||||
.rejects.toThrow('文件或目录不存在');
|
||||
|
||||
.rejects.toThrow('文件或目录不存在')
|
||||
|
||||
// 设置exists=false应该返回空字符串
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('exists', 'false');
|
||||
const content = await userProtocol.loadContent(nonExistentPath, queryParams);
|
||||
expect(content).toBe('');
|
||||
});
|
||||
});
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('exists', 'false')
|
||||
const content = await userProtocol.loadContent(nonExistentPath, queryParams)
|
||||
expect(content).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('查询参数处理', () => {
|
||||
test('应该应用行过滤', () => {
|
||||
const content = 'line1\nline2\nline3\nline4\nline5';
|
||||
|
||||
const content = 'line1\nline2\nline3\nline4\nline5'
|
||||
|
||||
// 测试单行
|
||||
expect(userProtocol.applyLineFilter(content, '2')).toBe('line2');
|
||||
|
||||
expect(userProtocol.applyLineFilter(content, '2')).toBe('line2')
|
||||
|
||||
// 测试范围
|
||||
expect(userProtocol.applyLineFilter(content, '2-4')).toBe('line2\nline3\nline4');
|
||||
|
||||
expect(userProtocol.applyLineFilter(content, '2-4')).toBe('line2\nline3\nline4')
|
||||
|
||||
// 测试边界
|
||||
expect(userProtocol.applyLineFilter(content, '1-2')).toBe('line1\nline2');
|
||||
});
|
||||
expect(userProtocol.applyLineFilter(content, '1-2')).toBe('line1\nline2')
|
||||
})
|
||||
|
||||
test('应该应用格式化', () => {
|
||||
const jsonContent = '{"name": "test", "value": 123}';
|
||||
|
||||
const jsonContent = '{"name": "test", "value": 123}'
|
||||
|
||||
// 测试JSON格式化
|
||||
const formatted = userProtocol.applyFormat(jsonContent, 'json');
|
||||
expect(formatted).toContain('{\n "name"');
|
||||
|
||||
const formatted = userProtocol.applyFormat(jsonContent, 'json')
|
||||
expect(formatted).toContain('{\n "name"')
|
||||
|
||||
// 测试trim格式化
|
||||
const textContent = ' hello world ';
|
||||
expect(userProtocol.applyFormat(textContent, 'trim')).toBe('hello world');
|
||||
});
|
||||
});
|
||||
const textContent = ' hello world '
|
||||
expect(userProtocol.applyFormat(textContent, 'trim')).toBe('hello world')
|
||||
})
|
||||
})
|
||||
|
||||
describe('缓存管理', () => {
|
||||
test('应该启用缓存', () => {
|
||||
expect(userProtocol.enableCache).toBe(true);
|
||||
});
|
||||
expect(userProtocol.enableCache).toBe(true)
|
||||
})
|
||||
|
||||
test('应该提供缓存统计', () => {
|
||||
const stats = userProtocol.getCacheStats();
|
||||
expect(stats.protocol).toBe('user');
|
||||
expect(stats.enabled).toBe(true);
|
||||
expect(typeof stats.size).toBe('number');
|
||||
});
|
||||
const stats = userProtocol.getCacheStats()
|
||||
expect(stats.protocol).toBe('user')
|
||||
expect(stats.enabled).toBe(true)
|
||||
expect(typeof stats.size).toBe('number')
|
||||
})
|
||||
|
||||
test('应该清除缓存', async () => {
|
||||
// 先缓存一些数据
|
||||
await userProtocol.getUserDirectory('home');
|
||||
expect(userProtocol.dirCache.size).toBeGreaterThan(0);
|
||||
|
||||
await userProtocol.getUserDirectory('home')
|
||||
expect(userProtocol.dirCache.size).toBeGreaterThan(0)
|
||||
|
||||
// 清除缓存
|
||||
userProtocol.clearCache();
|
||||
expect(userProtocol.dirCache.size).toBe(0);
|
||||
expect(userProtocol.cache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
userProtocol.clearCache()
|
||||
expect(userProtocol.dirCache.size).toBe(0)
|
||||
expect(userProtocol.cache.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('集成测试', () => {
|
||||
test('应该完整解析用户协议资源', async () => {
|
||||
const queryParams = new QueryParams();
|
||||
queryParams.set('format', 'json');
|
||||
|
||||
const content = await userProtocol.resolve('home', queryParams);
|
||||
|
||||
expect(typeof content).toBe('string');
|
||||
expect(content.length).toBeGreaterThan(0);
|
||||
|
||||
const queryParams = new QueryParams()
|
||||
queryParams.set('format', 'json')
|
||||
|
||||
const content = await userProtocol.resolve('home', queryParams)
|
||||
|
||||
expect(typeof content).toBe('string')
|
||||
expect(content.length).toBeGreaterThan(0)
|
||||
|
||||
// 如果格式是json,应该能解析
|
||||
if (queryParams.get('format') === 'json') {
|
||||
expect(() => JSON.parse(content)).not.toThrow();
|
||||
expect(() => JSON.parse(content)).not.toThrow()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
test('应该处理嵌套路径', async () => {
|
||||
// 假设Documents目录存在
|
||||
try {
|
||||
const content = await userProtocol.resolve('documents');
|
||||
expect(typeof content).toBe('string');
|
||||
const content = await userProtocol.resolve('documents')
|
||||
expect(typeof content).toBe('string')
|
||||
} catch (error) {
|
||||
// 如果Documents目录不存在,这是正常的
|
||||
expect(error.message).toContain('不存在');
|
||||
expect(error.message).toContain('不存在')
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,203 +1,203 @@
|
||||
const ResourceManager = require('../../../lib/core/resource/resourceManager');
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const ResourceManager = require('../../../lib/core/resource/resourceManager')
|
||||
const fs = require('fs').promises
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
describe('ResourceManager - Integration Tests', () => {
|
||||
let manager;
|
||||
let tempDir;
|
||||
let manager
|
||||
let tempDir
|
||||
|
||||
beforeAll(async () => {
|
||||
// 创建临时测试目录
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-'));
|
||||
|
||||
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-'))
|
||||
|
||||
// 创建测试文件
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, 'test.md'),
|
||||
'# 测试文件\n\n这是一个测试文件。\n第三行内容。\n第四行内容。'
|
||||
);
|
||||
|
||||
)
|
||||
|
||||
await fs.writeFile(
|
||||
path.join(tempDir, 'nested.md'),
|
||||
'nested content'
|
||||
);
|
||||
)
|
||||
|
||||
// 创建子目录和更多测试文件
|
||||
const subDir = path.join(tempDir, 'subdir');
|
||||
await fs.mkdir(subDir);
|
||||
const subDir = path.join(tempDir, 'subdir')
|
||||
await fs.mkdir(subDir)
|
||||
await fs.writeFile(
|
||||
path.join(subDir, 'sub-test.md'),
|
||||
'subdirectory content'
|
||||
);
|
||||
});
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
// 清理临时目录
|
||||
await fs.rm(tempDir, { recursive: true });
|
||||
});
|
||||
await fs.rm(tempDir, { recursive: true })
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
manager = new ResourceManager({
|
||||
workingDirectory: tempDir,
|
||||
enableCache: true
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
describe('完整的资源解析流程', () => {
|
||||
test('应该解析并加载本地文件', async () => {
|
||||
const result = await manager.resolve('@file://test.md');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('测试文件');
|
||||
expect(result.metadata.protocol).toBe('file');
|
||||
expect(result.sources).toContain('test.md');
|
||||
});
|
||||
const result = await manager.resolve('@file://test.md')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain('测试文件')
|
||||
expect(result.metadata.protocol).toBe('file')
|
||||
expect(result.sources).toContain('test.md')
|
||||
})
|
||||
|
||||
test('应该处理带查询参数的文件加载', async () => {
|
||||
const result = await manager.resolve('@file://test.md?line=2-3');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).not.toContain('# 测试文件');
|
||||
expect(result.content).toContain('这是一个测试文件');
|
||||
expect(result.content).not.toContain('第三行内容');
|
||||
expect(result.content).not.toContain('第四行内容');
|
||||
});
|
||||
const result = await manager.resolve('@file://test.md?line=2-3')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).not.toContain('# 测试文件')
|
||||
expect(result.content).toContain('这是一个测试文件')
|
||||
expect(result.content).not.toContain('第三行内容')
|
||||
expect(result.content).not.toContain('第四行内容')
|
||||
})
|
||||
|
||||
test('应该处理通配符文件模式', async () => {
|
||||
const result = await manager.resolve('@file://*.md');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('test.md');
|
||||
expect(result.content).toContain('nested.md');
|
||||
});
|
||||
});
|
||||
const result = await manager.resolve('@file://*.md')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain('test.md')
|
||||
expect(result.content).toContain('nested.md')
|
||||
})
|
||||
})
|
||||
|
||||
describe('内置协议集成', () => {
|
||||
test('应该处理prompt协议的注册表解析', async () => {
|
||||
// 模拟prompt协议解析
|
||||
const mockProtocolFile = path.join(tempDir, 'protocols.md');
|
||||
await fs.writeFile(mockProtocolFile, '# PromptX 协议\n\nDPML协议说明');
|
||||
|
||||
const mockProtocolFile = path.join(tempDir, 'protocols.md')
|
||||
await fs.writeFile(mockProtocolFile, '# PromptX 协议\n\nDPML协议说明')
|
||||
|
||||
// 注册测试协议
|
||||
manager.registry.register('test-prompt', {
|
||||
name: 'test-prompt',
|
||||
description: '测试提示词协议',
|
||||
registry: {
|
||||
'protocols': `@file://${mockProtocolFile}`
|
||||
protocols: `@file://${mockProtocolFile}`
|
||||
}
|
||||
});
|
||||
|
||||
const result = await manager.resolve('@test-prompt://protocols');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toContain('PromptX 协议');
|
||||
expect(result.content).toContain('DPML协议说明');
|
||||
});
|
||||
})
|
||||
|
||||
const result = await manager.resolve('@test-prompt://protocols')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toContain('PromptX 协议')
|
||||
expect(result.content).toContain('DPML协议说明')
|
||||
})
|
||||
|
||||
test('应该处理嵌套引用解析', async () => {
|
||||
// 创建指向嵌套文件的引用文件
|
||||
const refFile = path.join(tempDir, 'reference.md');
|
||||
await fs.writeFile(refFile, '@file://nested.md');
|
||||
|
||||
const refFile = path.join(tempDir, 'reference.md')
|
||||
await fs.writeFile(refFile, '@file://nested.md')
|
||||
|
||||
manager.registry.register('test-nested', {
|
||||
registry: {
|
||||
'ref': `@file://${refFile}`
|
||||
ref: `@file://${refFile}`
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const result = await manager.resolve('@test-nested://ref');
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.content).toBe('nested content');
|
||||
});
|
||||
});
|
||||
const result = await manager.resolve('@test-nested://ref')
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.content).toBe('nested content')
|
||||
})
|
||||
})
|
||||
|
||||
describe('缓存机制', () => {
|
||||
test('应该缓存已加载的资源', async () => {
|
||||
const firstResult = await manager.resolve('@file://test.md');
|
||||
const secondResult = await manager.resolve('@file://test.md');
|
||||
|
||||
expect(firstResult.content).toBe(secondResult.content);
|
||||
expect(firstResult.success).toBe(true);
|
||||
expect(secondResult.success).toBe(true);
|
||||
});
|
||||
const firstResult = await manager.resolve('@file://test.md')
|
||||
const secondResult = await manager.resolve('@file://test.md')
|
||||
|
||||
expect(firstResult.content).toBe(secondResult.content)
|
||||
expect(firstResult.success).toBe(true)
|
||||
expect(secondResult.success).toBe(true)
|
||||
})
|
||||
|
||||
test('应该清除缓存', async () => {
|
||||
await manager.resolve('@file://test.md');
|
||||
expect(manager.cache.size).toBeGreaterThan(0);
|
||||
|
||||
manager.clearCache();
|
||||
expect(manager.cache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
await manager.resolve('@file://test.md')
|
||||
expect(manager.cache.size).toBeGreaterThan(0)
|
||||
|
||||
manager.clearCache()
|
||||
expect(manager.cache.size).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('批量资源解析', () => {
|
||||
test('应该批量解析多个资源', async () => {
|
||||
const refs = [
|
||||
'@file://test.md',
|
||||
'@file://nested.md'
|
||||
];
|
||||
|
||||
const results = await manager.resolveMultiple(refs);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0].success).toBe(true);
|
||||
expect(results[1].success).toBe(true);
|
||||
expect(results[0].content).toContain('测试文件');
|
||||
expect(results[1].content).toContain('nested content');
|
||||
});
|
||||
});
|
||||
]
|
||||
|
||||
const results = await manager.resolveMultiple(refs)
|
||||
|
||||
expect(results).toHaveLength(2)
|
||||
expect(results[0].success).toBe(true)
|
||||
expect(results[1].success).toBe(true)
|
||||
expect(results[0].content).toContain('测试文件')
|
||||
expect(results[1].content).toContain('nested content')
|
||||
})
|
||||
})
|
||||
|
||||
describe('错误处理', () => {
|
||||
test('应该处理文件不存在的情况', async () => {
|
||||
const result = await manager.resolve('@file://nonexistent.md');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.error.message).toContain('Failed to read file');
|
||||
});
|
||||
const result = await manager.resolve('@file://nonexistent.md')
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error).toBeDefined()
|
||||
expect(result.error.message).toContain('Failed to read file')
|
||||
})
|
||||
|
||||
test('应该处理无效的协议', async () => {
|
||||
const result = await manager.resolve('@unknown://test');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error.message).toContain('Unknown protocol');
|
||||
});
|
||||
const result = await manager.resolve('@unknown://test')
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error.message).toContain('Unknown protocol')
|
||||
})
|
||||
|
||||
test('应该处理无效的资源引用语法', async () => {
|
||||
const result = await manager.resolve('invalid-reference');
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error.message).toContain('Invalid resource reference syntax');
|
||||
});
|
||||
});
|
||||
const result = await manager.resolve('invalid-reference')
|
||||
|
||||
expect(result.success).toBe(false)
|
||||
expect(result.error.message).toContain('Invalid resource reference syntax')
|
||||
})
|
||||
})
|
||||
|
||||
describe('验证功能', () => {
|
||||
test('应该验证有效的资源引用', () => {
|
||||
expect(manager.isValidReference('@file://test.md')).toBe(true);
|
||||
expect(manager.isValidReference('@prompt://protocols')).toBe(true);
|
||||
});
|
||||
expect(manager.isValidReference('@file://test.md')).toBe(true)
|
||||
expect(manager.isValidReference('@prompt://protocols')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的资源引用', () => {
|
||||
expect(manager.isValidReference('invalid')).toBe(false);
|
||||
expect(manager.isValidReference('@unknown://test')).toBe(false);
|
||||
});
|
||||
});
|
||||
expect(manager.isValidReference('invalid')).toBe(false)
|
||||
expect(manager.isValidReference('@unknown://test')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('工具功能', () => {
|
||||
test('应该列出可用协议', () => {
|
||||
const protocols = manager.listProtocols();
|
||||
|
||||
expect(protocols).toContain('file');
|
||||
expect(protocols).toContain('prompt');
|
||||
expect(protocols).toContain('memory');
|
||||
});
|
||||
const protocols = manager.listProtocols()
|
||||
|
||||
expect(protocols).toContain('file')
|
||||
expect(protocols).toContain('prompt')
|
||||
expect(protocols).toContain('memory')
|
||||
})
|
||||
|
||||
test('应该获取注册表信息', () => {
|
||||
const info = manager.getRegistryInfo('prompt');
|
||||
|
||||
expect(info).toBeDefined();
|
||||
expect(info.name).toBe('prompt');
|
||||
});
|
||||
});
|
||||
});
|
||||
const info = manager.getRegistryInfo('prompt')
|
||||
|
||||
expect(info).toBeDefined()
|
||||
expect(info.name).toBe('prompt')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,133 +1,133 @@
|
||||
const ResourceProtocolParser = require('../../../lib/core/resource/resourceProtocolParser');
|
||||
const {
|
||||
LoadingSemantics,
|
||||
ParsedReference,
|
||||
QueryParams
|
||||
} = require('../../../lib/core/resource/types');
|
||||
const ResourceProtocolParser = require('../../../lib/core/resource/resourceProtocolParser')
|
||||
const {
|
||||
LoadingSemantics,
|
||||
ParsedReference,
|
||||
QueryParams
|
||||
} = require('../../../lib/core/resource/types')
|
||||
|
||||
describe('ResourceProtocolParser - Unit Tests', () => {
|
||||
let parser;
|
||||
let parser
|
||||
|
||||
beforeEach(() => {
|
||||
parser = new ResourceProtocolParser();
|
||||
});
|
||||
parser = new ResourceProtocolParser()
|
||||
})
|
||||
|
||||
describe('基础语法解析', () => {
|
||||
test('应该解析基本的资源引用', () => {
|
||||
const result = parser.parse('@prompt://protocols');
|
||||
|
||||
expect(result.protocol).toBe('prompt');
|
||||
expect(result.path).toBe('protocols');
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.DEFAULT);
|
||||
expect(result.isNested).toBe(false);
|
||||
});
|
||||
const result = parser.parse('@prompt://protocols')
|
||||
|
||||
expect(result.protocol).toBe('prompt')
|
||||
expect(result.path).toBe('protocols')
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.DEFAULT)
|
||||
expect(result.isNested).toBe(false)
|
||||
})
|
||||
|
||||
test('应该解析带查询参数的资源引用', () => {
|
||||
const result = parser.parse('@file://test.md?line=5-10&cache=true');
|
||||
|
||||
expect(result.protocol).toBe('file');
|
||||
expect(result.path).toBe('test.md');
|
||||
expect(result.queryParams.line).toBe('5-10');
|
||||
expect(result.queryParams.cache).toBe(true);
|
||||
});
|
||||
const result = parser.parse('@file://test.md?line=5-10&cache=true')
|
||||
|
||||
expect(result.protocol).toBe('file')
|
||||
expect(result.path).toBe('test.md')
|
||||
expect(result.queryParams.line).toBe('5-10')
|
||||
expect(result.queryParams.cache).toBe(true)
|
||||
})
|
||||
|
||||
test('应该解析热加载语义', () => {
|
||||
const result = parser.parse('@!prompt://core');
|
||||
|
||||
expect(result.protocol).toBe('prompt');
|
||||
expect(result.path).toBe('core');
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.HOT_LOAD);
|
||||
});
|
||||
const result = parser.parse('@!prompt://core')
|
||||
|
||||
expect(result.protocol).toBe('prompt')
|
||||
expect(result.path).toBe('core')
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.HOT_LOAD)
|
||||
})
|
||||
|
||||
test('应该解析懒加载语义', () => {
|
||||
const result = parser.parse('@?file://lazy-resource.md');
|
||||
|
||||
expect(result.protocol).toBe('file');
|
||||
expect(result.path).toBe('lazy-resource.md');
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.LAZY_LOAD);
|
||||
});
|
||||
});
|
||||
const result = parser.parse('@?file://lazy-resource.md')
|
||||
|
||||
expect(result.protocol).toBe('file')
|
||||
expect(result.path).toBe('lazy-resource.md')
|
||||
expect(result.loadingSemantics).toBe(LoadingSemantics.LAZY_LOAD)
|
||||
})
|
||||
})
|
||||
|
||||
describe('嵌套引用解析', () => {
|
||||
test('应该解析简单嵌套引用', () => {
|
||||
const result = parser.parse('@prompt://@file://nested.md');
|
||||
|
||||
expect(result.protocol).toBe('prompt');
|
||||
expect(result.isNested).toBe(true);
|
||||
expect(result.nestedRef.inner.protocol).toBe('file');
|
||||
expect(result.nestedRef.inner.path).toBe('nested.md');
|
||||
});
|
||||
const result = parser.parse('@prompt://@file://nested.md')
|
||||
|
||||
expect(result.protocol).toBe('prompt')
|
||||
expect(result.isNested).toBe(true)
|
||||
expect(result.nestedRef.inner.protocol).toBe('file')
|
||||
expect(result.nestedRef.inner.path).toBe('nested.md')
|
||||
})
|
||||
|
||||
test('应该解析多层嵌套引用', () => {
|
||||
const result = parser.parse('@prompt://@memory://@file://deep.md');
|
||||
|
||||
expect(result.protocol).toBe('prompt');
|
||||
expect(result.isNested).toBe(true);
|
||||
expect(result.nestedRef.inner.protocol).toBe('memory');
|
||||
expect(result.nestedRef.inner.isNested).toBe(true);
|
||||
expect(result.nestedRef.depth).toBe(2);
|
||||
});
|
||||
});
|
||||
const result = parser.parse('@prompt://@memory://@file://deep.md')
|
||||
|
||||
expect(result.protocol).toBe('prompt')
|
||||
expect(result.isNested).toBe(true)
|
||||
expect(result.nestedRef.inner.protocol).toBe('memory')
|
||||
expect(result.nestedRef.inner.isNested).toBe(true)
|
||||
expect(result.nestedRef.depth).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('查询参数解析', () => {
|
||||
test('应该解析多个查询参数', () => {
|
||||
const params = parser.parseQueryParams('line=1-10&format=json&cache=true');
|
||||
|
||||
expect(params.line).toBe('1-10');
|
||||
expect(params.format).toBe('json');
|
||||
expect(params.cache).toBe(true);
|
||||
});
|
||||
const params = parser.parseQueryParams('line=1-10&format=json&cache=true')
|
||||
|
||||
expect(params.line).toBe('1-10')
|
||||
expect(params.format).toBe('json')
|
||||
expect(params.cache).toBe(true)
|
||||
})
|
||||
|
||||
test('应该处理空查询参数', () => {
|
||||
const params = parser.parseQueryParams('');
|
||||
|
||||
expect(params.getAll()).toEqual({});
|
||||
});
|
||||
const params = parser.parseQueryParams('')
|
||||
|
||||
expect(params.getAll()).toEqual({})
|
||||
})
|
||||
|
||||
test('应该处理URL编码的参数', () => {
|
||||
const params = parser.parseQueryParams('query=%E4%B8%AD%E6%96%87');
|
||||
|
||||
expect(params.get('query')).toBe('中文');
|
||||
});
|
||||
});
|
||||
const params = parser.parseQueryParams('query=%E4%B8%AD%E6%96%87')
|
||||
|
||||
expect(params.get('query')).toBe('中文')
|
||||
})
|
||||
})
|
||||
|
||||
describe('语法验证', () => {
|
||||
test('应该验证有效的语法', () => {
|
||||
expect(parser.validateSyntax('@prompt://protocols')).toBe(true);
|
||||
expect(parser.validateSyntax('@!file://test.md')).toBe(true);
|
||||
expect(parser.validateSyntax('@?memory://declarative')).toBe(true);
|
||||
});
|
||||
expect(parser.validateSyntax('@prompt://protocols')).toBe(true)
|
||||
expect(parser.validateSyntax('@!file://test.md')).toBe(true)
|
||||
expect(parser.validateSyntax('@?memory://declarative')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的语法', () => {
|
||||
expect(parser.validateSyntax('prompt://protocols')).toBe(false); // 缺少@
|
||||
expect(parser.validateSyntax('@://test')).toBe(false); // 空协议
|
||||
expect(parser.validateSyntax('@123protocol://test')).toBe(false); // 协议名不能以数字开头
|
||||
expect(parser.validateSyntax('')).toBe(false); // 空字符串
|
||||
});
|
||||
});
|
||||
expect(parser.validateSyntax('prompt://protocols')).toBe(false) // 缺少@
|
||||
expect(parser.validateSyntax('@://test')).toBe(false) // 空协议
|
||||
expect(parser.validateSyntax('@123protocol://test')).toBe(false) // 协议名不能以数字开头
|
||||
expect(parser.validateSyntax('')).toBe(false) // 空字符串
|
||||
})
|
||||
})
|
||||
|
||||
describe('错误处理', () => {
|
||||
test('应该抛出适当的错误信息', () => {
|
||||
expect(() => parser.parse('')).toThrow('Invalid resource reference');
|
||||
expect(() => parser.parse(null)).toThrow('Invalid resource reference');
|
||||
expect(() => parser.parse('invalid')).toThrow('Invalid resource reference syntax');
|
||||
});
|
||||
});
|
||||
expect(() => parser.parse('')).toThrow('Invalid resource reference')
|
||||
expect(() => parser.parse(null)).toThrow('Invalid resource reference')
|
||||
expect(() => parser.parse('invalid')).toThrow('Invalid resource reference syntax')
|
||||
})
|
||||
})
|
||||
|
||||
describe('工具方法', () => {
|
||||
test('应该正确提取协议名', () => {
|
||||
expect(parser.extractProtocol('@prompt://protocols')).toBe('prompt');
|
||||
expect(parser.extractProtocol('@!file://test.md')).toBe('file');
|
||||
});
|
||||
expect(parser.extractProtocol('@prompt://protocols')).toBe('prompt')
|
||||
expect(parser.extractProtocol('@!file://test.md')).toBe('file')
|
||||
})
|
||||
|
||||
test('应该正确提取路径', () => {
|
||||
expect(parser.extractPath('@prompt://protocols?format=json')).toBe('protocols');
|
||||
expect(parser.extractPath('@file://path/to/file.md')).toBe('path/to/file.md');
|
||||
});
|
||||
expect(parser.extractPath('@prompt://protocols?format=json')).toBe('protocols')
|
||||
expect(parser.extractPath('@file://path/to/file.md')).toBe('path/to/file.md')
|
||||
})
|
||||
|
||||
test('应该正确提取查询参数', () => {
|
||||
expect(parser.extractParams('@file://test.md?line=5-10')).toBe('line=5-10');
|
||||
expect(parser.extractParams('@file://test.md')).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
expect(parser.extractParams('@file://test.md?line=5-10')).toBe('line=5-10')
|
||||
expect(parser.extractParams('@file://test.md')).toBe('')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,64 +1,64 @@
|
||||
const ResourceRegistry = require('../../../lib/core/resource/resourceRegistry');
|
||||
const { ProtocolInfo } = require('../../../lib/core/resource/types');
|
||||
const ResourceRegistry = require('../../../lib/core/resource/resourceRegistry')
|
||||
const { ProtocolInfo } = require('../../../lib/core/resource/types')
|
||||
|
||||
describe('ResourceRegistry - Unit Tests', () => {
|
||||
let registry;
|
||||
let registry
|
||||
|
||||
beforeEach(() => {
|
||||
registry = new ResourceRegistry();
|
||||
});
|
||||
registry = new ResourceRegistry()
|
||||
})
|
||||
|
||||
describe('内置协议', () => {
|
||||
test('应该包含内置协议', () => {
|
||||
const protocols = registry.listProtocols();
|
||||
|
||||
expect(protocols).toContain('prompt');
|
||||
expect(protocols).toContain('file');
|
||||
expect(protocols).toContain('memory');
|
||||
});
|
||||
const protocols = registry.listProtocols()
|
||||
|
||||
expect(protocols).toContain('prompt')
|
||||
expect(protocols).toContain('file')
|
||||
expect(protocols).toContain('memory')
|
||||
})
|
||||
|
||||
test('应该正确获取prompt协议信息', () => {
|
||||
const protocolInfo = registry.getProtocolInfo('prompt');
|
||||
|
||||
expect(protocolInfo).toBeDefined();
|
||||
expect(protocolInfo.name).toBe('prompt');
|
||||
expect(protocolInfo.description).toContain('PromptX内置提示词资源协议');
|
||||
expect(protocolInfo.location).toContain('prompt://');
|
||||
});
|
||||
const protocolInfo = registry.getProtocolInfo('prompt')
|
||||
|
||||
expect(protocolInfo).toBeDefined()
|
||||
expect(protocolInfo.name).toBe('prompt')
|
||||
expect(protocolInfo.description).toContain('PromptX内置提示词资源协议')
|
||||
expect(protocolInfo.location).toContain('prompt://')
|
||||
})
|
||||
|
||||
test('应该为协议提供资源注册表', () => {
|
||||
const protocolInfo = registry.getProtocolInfo('memory');
|
||||
|
||||
expect(protocolInfo.registry).toBeDefined();
|
||||
expect(protocolInfo.registry.size).toBeGreaterThan(0);
|
||||
expect(protocolInfo.registry.has('declarative')).toBe(true);
|
||||
expect(protocolInfo.registry.has('procedural')).toBe(true);
|
||||
});
|
||||
});
|
||||
const protocolInfo = registry.getProtocolInfo('memory')
|
||||
|
||||
expect(protocolInfo.registry).toBeDefined()
|
||||
expect(protocolInfo.registry.size).toBeGreaterThan(0)
|
||||
expect(protocolInfo.registry.has('declarative')).toBe(true)
|
||||
expect(protocolInfo.registry.has('procedural')).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('资源解析', () => {
|
||||
test('应该解析prompt协议的资源ID', () => {
|
||||
const resolved = registry.resolve('prompt', 'protocols');
|
||||
|
||||
expect(resolved).toBe('@package://prompt/protocol/**/*.md');
|
||||
});
|
||||
const resolved = registry.resolve('prompt', 'protocols')
|
||||
|
||||
expect(resolved).toBe('@package://prompt/protocol/**/*.md')
|
||||
})
|
||||
|
||||
test('应该解析memory协议的资源ID', () => {
|
||||
const resolved = registry.resolve('memory', 'declarative');
|
||||
|
||||
expect(resolved).toBe('@project://.promptx/memory/declarative.md');
|
||||
});
|
||||
const resolved = registry.resolve('memory', 'declarative')
|
||||
|
||||
expect(resolved).toBe('@project://.promptx/memory/declarative.md')
|
||||
})
|
||||
|
||||
test('应该解析未注册协议的资源路径', () => {
|
||||
const resolved = registry.resolve('file', 'any/path.md');
|
||||
|
||||
expect(resolved).toBe('any/path.md');
|
||||
});
|
||||
const resolved = registry.resolve('file', 'any/path.md')
|
||||
|
||||
expect(resolved).toBe('any/path.md')
|
||||
})
|
||||
|
||||
test('应该在资源ID不存在时抛出错误', () => {
|
||||
expect(() => registry.resolve('prompt', 'nonexistent')).toThrow('Resource ID \'nonexistent\' not found in prompt protocol registry');
|
||||
});
|
||||
});
|
||||
expect(() => registry.resolve('prompt', 'nonexistent')).toThrow('Resource ID \'nonexistent\' not found in prompt protocol registry')
|
||||
})
|
||||
})
|
||||
|
||||
describe('自定义协议注册', () => {
|
||||
test('应该注册新的自定义协议', () => {
|
||||
@ -66,69 +66,69 @@ describe('ResourceRegistry - Unit Tests', () => {
|
||||
description: '测试协议',
|
||||
location: 'test://{resource_id}',
|
||||
registry: {
|
||||
'test1': '@file://test1.md',
|
||||
'test2': '@file://test2.md'
|
||||
test1: '@file://test1.md',
|
||||
test2: '@file://test2.md'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registry.register('test', customProtocol);
|
||||
|
||||
expect(registry.hasProtocol('test')).toBe(true);
|
||||
expect(registry.resolve('test', 'test1')).toBe('@file://test1.md');
|
||||
});
|
||||
registry.register('test', customProtocol)
|
||||
|
||||
expect(registry.hasProtocol('test')).toBe(true)
|
||||
expect(registry.resolve('test', 'test1')).toBe('@file://test1.md')
|
||||
})
|
||||
|
||||
test('应该列出自定义协议的资源', () => {
|
||||
const customProtocol = {
|
||||
registry: {
|
||||
'resource1': '@file://r1.md',
|
||||
'resource2': '@file://r2.md'
|
||||
resource1: '@file://r1.md',
|
||||
resource2: '@file://r2.md'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
registry.register('custom', customProtocol);
|
||||
const resources = registry.listProtocolResources('custom');
|
||||
|
||||
expect(resources).toContain('resource1');
|
||||
expect(resources).toContain('resource2');
|
||||
});
|
||||
});
|
||||
registry.register('custom', customProtocol)
|
||||
const resources = registry.listProtocolResources('custom')
|
||||
|
||||
expect(resources).toContain('resource1')
|
||||
expect(resources).toContain('resource2')
|
||||
})
|
||||
})
|
||||
|
||||
describe('验证功能', () => {
|
||||
test('应该验证有效的协议和资源ID', () => {
|
||||
expect(registry.validateReference('prompt', 'protocols')).toBe(true);
|
||||
expect(registry.validateReference('file', 'any-path.md')).toBe(true);
|
||||
expect(registry.validateReference('memory', 'declarative')).toBe(true);
|
||||
});
|
||||
expect(registry.validateReference('prompt', 'protocols')).toBe(true)
|
||||
expect(registry.validateReference('file', 'any-path.md')).toBe(true)
|
||||
expect(registry.validateReference('memory', 'declarative')).toBe(true)
|
||||
})
|
||||
|
||||
test('应该拒绝无效的协议和资源ID', () => {
|
||||
expect(registry.validateReference('unknown', 'test')).toBe(false);
|
||||
expect(registry.validateReference('prompt', 'nonexistent')).toBe(false);
|
||||
});
|
||||
});
|
||||
expect(registry.validateReference('unknown', 'test')).toBe(false)
|
||||
expect(registry.validateReference('prompt', 'nonexistent')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('注册表信息', () => {
|
||||
test('应该返回完整的注册表信息', () => {
|
||||
const info = registry.getRegistryInfo();
|
||||
|
||||
expect(info.builtin).toHaveProperty('prompt');
|
||||
expect(info.builtin).toHaveProperty('file');
|
||||
expect(info.builtin).toHaveProperty('memory');
|
||||
expect(info.custom).toEqual({});
|
||||
});
|
||||
const info = registry.getRegistryInfo()
|
||||
|
||||
expect(info.builtin).toHaveProperty('prompt')
|
||||
expect(info.builtin).toHaveProperty('file')
|
||||
expect(info.builtin).toHaveProperty('memory')
|
||||
expect(info.custom).toEqual({})
|
||||
})
|
||||
|
||||
test('应该返回协议的资源列表', () => {
|
||||
const resources = registry.listProtocolResources('prompt');
|
||||
|
||||
expect(resources).toContain('protocols');
|
||||
expect(resources).toContain('core');
|
||||
expect(resources).toContain('domain');
|
||||
expect(resources).toContain('bootstrap');
|
||||
});
|
||||
const resources = registry.listProtocolResources('prompt')
|
||||
|
||||
expect(resources).toContain('protocols')
|
||||
expect(resources).toContain('core')
|
||||
expect(resources).toContain('domain')
|
||||
expect(resources).toContain('bootstrap')
|
||||
})
|
||||
|
||||
test('应该为无注册表的协议返回空列表', () => {
|
||||
const resources = registry.listProtocolResources('file');
|
||||
|
||||
expect(resources).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
const resources = registry.listProtocolResources('file')
|
||||
|
||||
expect(resources).toEqual([])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
82
src/tests/fixtures/testResources.js
vendored
82
src/tests/fixtures/testResources.js
vendored
@ -1,38 +1,38 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const os = require('os');
|
||||
const path = require('path')
|
||||
const fs = require('fs').promises
|
||||
const os = require('os')
|
||||
|
||||
/**
|
||||
* 测试资源工厂
|
||||
* 提供测试用的固定数据和辅助函数
|
||||
*/
|
||||
class TestResourceFactory {
|
||||
constructor() {
|
||||
this.tempDirs = new Set();
|
||||
constructor () {
|
||||
this.tempDirs = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时测试目录
|
||||
* @returns {Promise<string>} 临时目录路径
|
||||
*/
|
||||
async createTempDir() {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-'));
|
||||
this.tempDirs.add(tempDir);
|
||||
return tempDir;
|
||||
async createTempDir () {
|
||||
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptx-test-'))
|
||||
this.tempDirs.add(tempDir)
|
||||
return tempDir
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有临时目录
|
||||
*/
|
||||
async cleanup() {
|
||||
async cleanup () {
|
||||
for (const dir of this.tempDirs) {
|
||||
try {
|
||||
await fs.rmdir(dir, { recursive: true });
|
||||
await fs.rmdir(dir, { recursive: true })
|
||||
} catch (error) {
|
||||
console.warn(`Failed to cleanup temp dir ${dir}:`, error.message);
|
||||
console.warn(`Failed to cleanup temp dir ${dir}:`, error.message)
|
||||
}
|
||||
}
|
||||
this.tempDirs.clear();
|
||||
this.tempDirs.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -40,18 +40,18 @@ class TestResourceFactory {
|
||||
* @param {string} baseDir - 基础目录
|
||||
* @returns {Promise<object>} 创建的文件路径映射
|
||||
*/
|
||||
async createPromptXStructure(baseDir) {
|
||||
async createPromptXStructure (baseDir) {
|
||||
const structure = {
|
||||
prompt: path.join(baseDir, 'prompt'),
|
||||
core: path.join(baseDir, 'prompt', 'core'),
|
||||
domain: path.join(baseDir, 'prompt', 'domain'),
|
||||
protocol: path.join(baseDir, 'prompt', 'protocol'),
|
||||
memory: path.join(baseDir, '.memory')
|
||||
};
|
||||
}
|
||||
|
||||
// 创建目录结构
|
||||
for (const dir of Object.values(structure)) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
await fs.mkdir(dir, { recursive: true })
|
||||
}
|
||||
|
||||
// 创建测试文件
|
||||
@ -63,24 +63,24 @@ class TestResourceFactory {
|
||||
protocolDpml: path.join(structure.protocol, 'dpml.md'),
|
||||
memoryDeclarative: path.join(structure.memory, 'declarative.md'),
|
||||
memoryProcedural: path.join(structure.memory, 'procedural.md')
|
||||
};
|
||||
}
|
||||
|
||||
// 创建core子目录
|
||||
await fs.mkdir(path.join(structure.core, 'thought'), { recursive: true });
|
||||
await fs.mkdir(path.join(structure.core, 'execution'), { recursive: true });
|
||||
await fs.mkdir(path.join(structure.domain, 'test'), { recursive: true });
|
||||
await fs.mkdir(path.join(structure.core, 'thought'), { recursive: true })
|
||||
await fs.mkdir(path.join(structure.core, 'execution'), { recursive: true })
|
||||
await fs.mkdir(path.join(structure.domain, 'test'), { recursive: true })
|
||||
|
||||
// 写入测试文件内容
|
||||
await this.writeTestFiles(files);
|
||||
await this.writeTestFiles(files)
|
||||
|
||||
return { structure, files };
|
||||
return { structure, files }
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入测试文件内容
|
||||
* @param {object} files - 文件路径映射
|
||||
*/
|
||||
async writeTestFiles(files) {
|
||||
async writeTestFiles (files) {
|
||||
const contents = {
|
||||
[files.bootstrap]: `# PromptX Bootstrap
|
||||
|
||||
@ -182,17 +182,17 @@ JSON结构化数据`,
|
||||
|
||||
## 存储格式
|
||||
步骤化序列`
|
||||
};
|
||||
}
|
||||
|
||||
for (const [filePath, content] of Object.entries(contents)) {
|
||||
await fs.writeFile(filePath, content, 'utf8');
|
||||
await fs.writeFile(filePath, content, 'utf8')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试用的DPML资源引用
|
||||
*/
|
||||
getTestResourceRefs() {
|
||||
getTestResourceRefs () {
|
||||
return {
|
||||
valid: [
|
||||
'@promptx://bootstrap',
|
||||
@ -224,13 +224,13 @@ JSON结构化数据`,
|
||||
'@file://@memory://declarative',
|
||||
'@promptx://@memory://@file://deep-nested.md'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试用的查询参数
|
||||
*/
|
||||
getTestQueryParams() {
|
||||
getTestQueryParams () {
|
||||
return {
|
||||
line: ['1', '5-10', '1-', '-10'],
|
||||
format: ['json', 'text', 'xml'],
|
||||
@ -238,21 +238,21 @@ JSON结构化数据`,
|
||||
encoding: ['utf8', 'gbk', 'ascii'],
|
||||
timeout: ['5000', '10000'],
|
||||
custom: ['value1', 'value2']
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建模拟的协议注册表
|
||||
*/
|
||||
getMockProtocolRegistry() {
|
||||
getMockProtocolRegistry () {
|
||||
return {
|
||||
'test-protocol': {
|
||||
description: '测试协议',
|
||||
location: 'test://{resource_id}',
|
||||
registry: {
|
||||
'resource1': '@file://test1.md',
|
||||
'resource2': '@file://test2.md',
|
||||
'nested': '@test-protocol://resource1'
|
||||
resource1: '@file://test1.md',
|
||||
resource2: '@file://test2.md',
|
||||
nested: '@test-protocol://resource1'
|
||||
}
|
||||
},
|
||||
'mock-http': {
|
||||
@ -263,13 +263,13 @@ JSON结构化数据`,
|
||||
format: 'string'
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建错误场景测试数据
|
||||
*/
|
||||
getErrorScenarios() {
|
||||
getErrorScenarios () {
|
||||
return {
|
||||
fileNotFound: '@file://nonexistent.md',
|
||||
permissionDenied: '@file://restricted.md',
|
||||
@ -277,16 +277,16 @@ JSON结构化数据`,
|
||||
malformedUrl: '@file://',
|
||||
networkTimeout: '@http://timeout.example.com',
|
||||
parseError: 'invalid-syntax'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
TestResourceFactory,
|
||||
|
||||
|
||||
// 便捷工厂函数
|
||||
createTestFactory: () => new TestResourceFactory(),
|
||||
|
||||
|
||||
// 常用测试数据
|
||||
SAMPLE_DPML_REFS: [
|
||||
'@promptx://protocols',
|
||||
@ -294,8 +294,8 @@ module.exports = {
|
||||
'@!memory://hot-memory',
|
||||
'@?file://lazy-file.md'
|
||||
],
|
||||
|
||||
|
||||
SAMPLE_PROTOCOLS: ['promptx', 'file', 'memory', 'http', 'https'],
|
||||
|
||||
|
||||
SAMPLE_LOADING_SEMANTICS: ['@', '@!', '@?']
|
||||
};
|
||||
}
|
||||
|
||||
@ -3,44 +3,44 @@
|
||||
*/
|
||||
|
||||
// 设置测试超时时间
|
||||
jest.setTimeout(30000);
|
||||
jest.setTimeout(30000)
|
||||
|
||||
// 全局变量设置
|
||||
global.TEST_ENV = 'test';
|
||||
global.TEST_ENV = 'test'
|
||||
|
||||
// 模拟console.log以减少测试输出噪音
|
||||
const originalConsoleLog = console.log;
|
||||
const originalConsoleWarn = console.warn;
|
||||
const originalConsoleError = console.error;
|
||||
const originalConsoleLog = console.log
|
||||
const originalConsoleWarn = console.warn
|
||||
const originalConsoleError = console.error
|
||||
|
||||
// 在测试环境中静默一些不必要的日志
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
console.log = (...args) => {
|
||||
// 只有在明确需要时才输出
|
||||
if (args.some(arg => typeof arg === 'string' && arg.includes('TEST_OUTPUT'))) {
|
||||
originalConsoleLog(...args);
|
||||
originalConsoleLog(...args)
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
console.warn = (...args) => {
|
||||
// 保留警告信息
|
||||
if (args.some(arg => typeof arg === 'string' && arg.includes('TEST_WARN'))) {
|
||||
originalConsoleWarn(...args);
|
||||
originalConsoleWarn(...args)
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
console.error = (...args) => {
|
||||
// 保留错误信息
|
||||
originalConsoleError(...args);
|
||||
};
|
||||
originalConsoleError(...args)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试结束后恢复console
|
||||
afterAll(() => {
|
||||
console.log = originalConsoleLog;
|
||||
console.warn = originalConsoleWarn;
|
||||
console.error = originalConsoleError;
|
||||
});
|
||||
console.log = originalConsoleLog
|
||||
console.warn = originalConsoleWarn
|
||||
console.error = originalConsoleError
|
||||
})
|
||||
|
||||
// 全局测试工具函数
|
||||
global.testUtils = {
|
||||
@ -49,15 +49,15 @@ global.testUtils = {
|
||||
* @param {number} ms - 毫秒数
|
||||
*/
|
||||
sleep: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
|
||||
|
||||
|
||||
/**
|
||||
* 创建延迟Promise
|
||||
* @param {any} value - 返回值
|
||||
* @param {number} delay - 延迟时间
|
||||
*/
|
||||
delayed: (value, delay = 100) =>
|
||||
delayed: (value, delay = 100) =>
|
||||
new Promise(resolve => setTimeout(() => resolve(value), delay)),
|
||||
|
||||
|
||||
/**
|
||||
* 创建拒绝的Promise
|
||||
* @param {any} error - 错误对象
|
||||
@ -65,59 +65,59 @@ global.testUtils = {
|
||||
*/
|
||||
delayedReject: (error, delay = 100) =>
|
||||
new Promise((_, reject) => setTimeout(() => reject(error), delay))
|
||||
};
|
||||
}
|
||||
|
||||
// 全局断言扩展
|
||||
expect.extend({
|
||||
/**
|
||||
* 检查是否为有效的DPML资源引用
|
||||
*/
|
||||
toBeValidDpmlReference(received) {
|
||||
const dpmlPattern = /^@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.+/;
|
||||
const pass = typeof received === 'string' && dpmlPattern.test(received);
|
||||
|
||||
toBeValidDpmlReference (received) {
|
||||
const dpmlPattern = /^@[!?]?[a-zA-Z][a-zA-Z0-9_-]*:\/\/.+/
|
||||
const pass = typeof received === 'string' && dpmlPattern.test(received)
|
||||
|
||||
if (pass) {
|
||||
return {
|
||||
message: () => `expected ${received} not to be a valid DPML reference`,
|
||||
pass: true
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () => `expected ${received} to be a valid DPML reference`,
|
||||
pass: false
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 检查对象是否包含必需的属性
|
||||
*/
|
||||
toHaveRequiredProperties(received, properties) {
|
||||
const missingProps = properties.filter(prop => !(prop in received));
|
||||
const pass = missingProps.length === 0;
|
||||
|
||||
toHaveRequiredProperties (received, properties) {
|
||||
const missingProps = properties.filter(prop => !(prop in received))
|
||||
const pass = missingProps.length === 0
|
||||
|
||||
if (pass) {
|
||||
return {
|
||||
message: () => `expected object not to have properties ${properties.join(', ')}`,
|
||||
pass: true
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
message: () => `expected object to have properties ${missingProps.join(', ')}`,
|
||||
pass: false
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 处理未捕获的Promise拒绝
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||
});
|
||||
console.error('Unhandled Rejection at:', promise, 'reason:', reason)
|
||||
})
|
||||
|
||||
// 处理未捕获的异常
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught Exception:', error);
|
||||
});
|
||||
console.error('Uncaught Exception:', error)
|
||||
})
|
||||
|
||||
console.log('🧪 Jest测试环境已初始化');
|
||||
console.log('🧪 Jest测试环境已初始化')
|
||||
|
||||
Reference in New Issue
Block a user