feat: 完成DPML协议体系0~1阶段开发 - 三层协议架构100%实现,智能路径检测系统,@package://与package.json完美集成,用户项目集成方案,CLI框架完整实现,132/137核心测试通过(96.3%通过率)

This commit is contained in:
sean
2025-05-31 13:03:26 +08:00
parent 3338b7c21f
commit be285f55b8
89 changed files with 6071 additions and 467 deletions

View File

@ -0,0 +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;
describe('PackageProtocol', () => {
let packageProtocol;
const originalEnv = process.env;
const projectRoot = process.cwd(); // PromptX项目根目录
beforeEach(() => {
packageProtocol = new PackageProtocol();
// 重置环境变量
process.env = { ...originalEnv };
});
afterEach(() => {
packageProtocol.clearCache();
process.env = originalEnv;
});
describe('基础功能', () => {
test('应该正确初始化协议', () => {
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');
});
test('应该支持缓存', () => {
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');
});
test('应该检测npx执行模式', () => {
// 模拟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);
});
test('检测结果应该是有效的安装模式', () => {
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);
});
test('应该通过npm_config_cache检测npx', () => {
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);
});
});
describe('全局安装检测', () => {
test('应该检测常见的全局路径', () => {
// 这个测试在实际环境中可能会失败因为我们无法轻易改变__dirname
const result = packageProtocol._isGlobalInstall();
expect(typeof result).toBe('boolean');
});
});
describe('开发模式检测', () => {
test('应该通过NODE_ENV检测开发模式', () => {
process.env.NODE_ENV = 'development';
expect(packageProtocol._isDevelopmentMode()).toBe(true);
});
test('应该检测非node_modules目录', () => {
// 当前测试环境应该不在node_modules中
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$/);
});
test('应该能找到根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();
});
});
describe('包根目录获取', () => {
test('应该能获取包根目录', async () => {
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);
});
});
describe('路径解析', () => {
test('应该解析package.json路径', async () => {
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$/);
});
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$/);
});
test('空路径应该返回包根目录', async () => {
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();
});
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);
});
});
describe('路径安全检查', () => {
test('应该阻止目录遍历攻击', async () => {
await expect(
packageProtocol.resolvePath('../../../etc/passwd')
).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$/);
});
});
describe('资源存在性检查', () => {
test('存在的文件应该返回true', async () => {
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);
});
});
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');
// 验证内容是有效的JSON
expect(() => JSON.parse(result.content)).not.toThrow();
});
test('加载不存在的文件应该抛出错误', async () => {
await expect(
packageProtocol.loadContent('nonexistent.txt')
).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');
});
});
describe('查询参数支持', () => {
test('应该支持查询参数', async () => {
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);
});
});
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');
});
});
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);
});
});
describe('错误处理', () => {
test('文件系统错误应该被正确处理', async () => {
// 尝试访问一个权限不足的路径(如果存在的话)
const result = await packageProtocol.exists('../../../root/.ssh/id_rsa');
expect(typeof result).toBe('boolean');
});
test('路径解析错误应该包含有用信息', async () => {
try {
await packageProtocol.resolvePath('../../../etc/passwd');
} catch (error) {
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$/);
});
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$/);
});
test('带有空格的路径应该被正确处理', async () => {
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$/);
});
});
});

View File

@ -0,0 +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;
describe('ProjectProtocol', () => {
let projectProtocol;
const projectRoot = process.cwd(); // PromptX项目根目录
const promptxPath = path.join(projectRoot, '.promptx');
beforeEach(() => {
projectProtocol = new ProjectProtocol();
});
afterEach(() => {
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');
});
test('应该提供协议信息', () => {
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('行范围');
});
});
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);
});
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);
});
test('应该验证项目目录类型', () => {
const supportedDirs = Object.keys(projectProtocol.projectDirs);
supportedDirs.forEach(dir => {
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);
});
test('应该从子目录找到项目根目录', async () => {
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);
});
test('应该处理未找到项目根目录的情况', async () => {
// 使用系统临时目录测试
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'));
});
test('应该解析lib目录路径', async () => {
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'));
});
test('应该解析目录路径(无文件名)', async () => {
const resolvedPath = await projectProtocol.resolvePath('src');
expect(resolvedPath).toBe(path.join(projectRoot, 'src'));
});
test('应该拒绝不支持的目录类型', async () => {
await expect(projectProtocol.resolvePath('invalid/path')).rejects.toThrow('不支持的项目目录类型');
});
test('应该处理安全路径检查', async () => {
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'));
});
});
describe('内容加载', () => {
test('应该加载存在的文件内容', async () => {
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]');
});
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);
});
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]');
});
test('应该处理不存在的文件', async () => {
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('');
});
});
describe('完整协议解析', () => {
test('应该完整解析project://协议', async () => {
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);
});
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);
});
});
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);
});
test('应该标识不存在的目录', async () => {
const info = await projectProtocol.getProjectInfo();
// 有些目录可能不存在,应该正确标识
Object.values(info.directories).forEach(dir => {
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');
});
test('应该能清除缓存', async () => {
await projectProtocol.findProjectRoot(); // 填充缓存
expect(projectProtocol.projectRootCache.size).toBeGreaterThan(0);
projectProtocol.clearCache();
expect(projectProtocol.projectRootCache.size).toBe(0);
});
});
});

View File

@ -0,0 +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');
describe('UserProtocol', () => {
let userProtocol;
beforeEach(() => {
userProtocol = new UserProtocol();
});
afterEach(() => {
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');
});
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');
});
test('应该提供支持的参数列表', () => {
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);
});
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);
});
});
describe('路径解析', () => {
test('应该解析home目录', async () => {
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);
});
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);
});
test('应该拒绝不支持的目录类型', async () => {
await expect(userProtocol.resolvePath('invalid/path'))
.rejects.toThrow('不支持的用户目录类型');
});
test('应该防止路径穿越攻击', async () => {
await expect(userProtocol.resolvePath('documents/../../../etc/passwd'))
.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();
// 检查路径是否为绝对路径
expect(path.isAbsolute(directories.home)).toBe(true);
});
test('应该缓存目录路径', async () => {
// 第一次调用
const dir1 = await userProtocol.getUserDirectory('home');
expect(userProtocol.dirCache.has('home')).toBe(true);
// 第二次调用应该从缓存获取
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);
});
test('应该支持不同的目录格式化选项', async () => {
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();
// 测试paths格式
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');
// 默认情况下应该抛出错误
await expect(userProtocol.loadContent(nonExistentPath))
.rejects.toThrow('文件或目录不存在');
// 设置exists=false应该返回空字符串
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';
// 测试单行
expect(userProtocol.applyLineFilter(content, '2')).toBe('line2');
// 测试范围
expect(userProtocol.applyLineFilter(content, '2-4')).toBe('line2\nline3\nline4');
// 测试边界
expect(userProtocol.applyLineFilter(content, '1-2')).toBe('line1\nline2');
});
test('应该应用格式化', () => {
const jsonContent = '{"name": "test", "value": 123}';
// 测试JSON格式化
const formatted = userProtocol.applyFormat(jsonContent, 'json');
expect(formatted).toContain('{\n "name"');
// 测试trim格式化
const textContent = ' hello world ';
expect(userProtocol.applyFormat(textContent, 'trim')).toBe('hello world');
});
});
describe('缓存管理', () => {
test('应该启用缓存', () => {
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');
});
test('应该清除缓存', async () => {
// 先缓存一些数据
await userProtocol.getUserDirectory('home');
expect(userProtocol.dirCache.size).toBeGreaterThan(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);
// 如果格式是json应该能解析
if (queryParams.get('format') === 'json') {
expect(() => JSON.parse(content)).not.toThrow();
}
});
test('应该处理嵌套路径', async () => {
// 假设Documents目录存在
try {
const content = await userProtocol.resolve('documents');
expect(typeof content).toBe('string');
} catch (error) {
// 如果Documents目录不存在这是正常的
expect(error.message).toContain('不存在');
}
});
});
});

View File

@ -0,0 +1,203 @@
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;
beforeAll(async () => {
// 创建临时测试目录
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);
await fs.writeFile(
path.join(subDir, 'sub-test.md'),
'subdirectory content'
);
});
afterAll(async () => {
// 清理临时目录
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');
});
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('第四行内容');
});
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');
});
});
describe('内置协议集成', () => {
test('应该处理prompt协议的注册表解析', async () => {
// 模拟prompt协议解析
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}`
}
});
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');
manager.registry.register('test-nested', {
registry: {
'ref': `@file://${refFile}`
}
});
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);
});
test('应该清除缓存', async () => {
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');
});
});
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');
});
test('应该处理无效的协议', async () => {
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');
});
});
describe('验证功能', () => {
test('应该验证有效的资源引用', () => {
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);
});
});
describe('工具功能', () => {
test('应该列出可用协议', () => {
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');
});
});
});

View File

@ -0,0 +1,133 @@
const ResourceProtocolParser = require('../../../lib/core/resource/resourceProtocolParser');
const {
LoadingSemantics,
ParsedReference,
QueryParams
} = require('../../../lib/core/resource/types');
describe('ResourceProtocolParser - Unit Tests', () => {
let parser;
beforeEach(() => {
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);
});
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);
});
test('应该解析热加载语义', () => {
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);
});
});
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');
});
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);
});
});
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);
});
test('应该处理空查询参数', () => {
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('中文');
});
});
describe('语法验证', () => {
test('应该验证有效的语法', () => {
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); // 空字符串
});
});
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');
});
});
describe('工具方法', () => {
test('应该正确提取协议名', () => {
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');
});
test('应该正确提取查询参数', () => {
expect(parser.extractParams('@file://test.md?line=5-10')).toBe('line=5-10');
expect(parser.extractParams('@file://test.md')).toBe('');
});
});
});

View File

@ -0,0 +1,134 @@
const ResourceRegistry = require('../../../lib/core/resource/resourceRegistry');
const { ProtocolInfo } = require('../../../lib/core/resource/types');
describe('ResourceRegistry - Unit Tests', () => {
let registry;
beforeEach(() => {
registry = new ResourceRegistry();
});
describe('内置协议', () => {
test('应该包含内置协议', () => {
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://');
});
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);
});
});
describe('资源解析', () => {
test('应该解析prompt协议的资源ID', () => {
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');
});
test('应该解析未注册协议的资源路径', () => {
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');
});
});
describe('自定义协议注册', () => {
test('应该注册新的自定义协议', () => {
const customProtocol = {
description: '测试协议',
location: 'test://{resource_id}',
registry: {
'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');
});
test('应该列出自定义协议的资源', () => {
const customProtocol = {
registry: {
'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');
});
});
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);
});
test('应该拒绝无效的协议和资源ID', () => {
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({});
});
test('应该返回协议的资源列表', () => {
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([]);
});
});
});