diff --git a/package.json b/package.json index 504835e..ac75a88 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "files": [ "src/", "prompt/", + "package.json", "README.md", "LICENSE", "CHANGELOG.md" diff --git a/src/lib/core/pouch/commands/LearnCommand.js b/src/lib/core/pouch/commands/LearnCommand.js index d10e5c7..71dc7bd 100644 --- a/src/lib/core/pouch/commands/LearnCommand.js +++ b/src/lib/core/pouch/commands/LearnCommand.js @@ -25,13 +25,21 @@ class LearnCommand extends BasePouchCommand { try { // 直接使用ResourceManager解析资源 - const content = await this.resourceManager.resolve(resourceUrl) + const result = await this.resourceManager.resolve(resourceUrl) + + if (!result.success) { + return this.formatErrorResponse(resourceUrl, result.error.message) + } // 解析协议信息 - const urlMatch = resourceUrl.match(/^([a-zA-Z]+):\/\/(.+)$/) - const [, protocol, resourceId] = urlMatch + const urlMatch = resourceUrl.match(/^(@[!?]?)?([a-zA-Z][a-zA-Z0-9_-]*):\/\/(.+)$/) + if (!urlMatch) { + return this.formatErrorResponse(resourceUrl, '无效的资源URL格式') + } + + const [, loadingSemantic, protocol, resourceId] = urlMatch - return this.formatSuccessResponse(protocol, resourceId, content) + return this.formatSuccessResponse(protocol, resourceId, result.content) } catch (error) { return this.formatErrorResponse(resourceUrl, error.message) } diff --git a/src/lib/core/resource/protocols/PackageProtocol.js b/src/lib/core/resource/protocols/PackageProtocol.js index a27bf75..94999a9 100644 --- a/src/lib/core/resource/protocols/PackageProtocol.js +++ b/src/lib/core/resource/protocols/PackageProtocol.js @@ -347,6 +347,14 @@ class PackageProtocol extends ResourceProtocol { * @returns {Promise} 解析后的绝对路径 */ async resolvePath (relativePath, params = null) { + // 生成缓存键 + const cacheKey = `resolve:${relativePath}:${params ? params.toString() : ''}` + + // 检查缓存 + if (this.cache.has(cacheKey)) { + return this.cache.get(cacheKey) + } + // 获取包根目录 const packageRoot = await this.getPackageRoot() @@ -359,9 +367,12 @@ class PackageProtocol extends ResourceProtocol { // 安全检查:确保路径在包根目录内 if (!fullPath.startsWith(packageRoot)) { - throw new Error(`Path traversal detected: ${relativePath}`) + throw new Error(`路径安全检查失败: ${relativePath}`) } + // 存储到缓存 + this.cache.set(cacheKey, fullPath) + return fullPath } @@ -452,9 +463,7 @@ class PackageProtocol extends ResourceProtocol { /** * 加载资源内容 */ - async loadContent (resourcePath, queryParams) { - const resolvedPath = await this.resolvePath(resourcePath, queryParams) - + async loadContent (resolvedPath, queryParams) { try { await fsPromises.access(resolvedPath) const content = await fsPromises.readFile(resolvedPath, 'utf8') @@ -469,12 +478,12 @@ class PackageProtocol extends ResourceProtocol { size: content.length, lastModified: stats.mtime, absolutePath: resolvedPath, - relativePath: resourcePath + relativePath: path.relative(await this.getPackageRoot(), resolvedPath) } } } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`包资源不存在: ${resourcePath} (解析为: ${resolvedPath})`) + throw new Error(`包资源不存在: ${resolvedPath}`) } throw new Error(`加载包资源失败: ${error.message}`) } diff --git a/src/lib/core/resource/protocols/PromptProtocol.js b/src/lib/core/resource/protocols/PromptProtocol.js index 1e46647..801a10b 100644 --- a/src/lib/core/resource/protocols/PromptProtocol.js +++ b/src/lib/core/resource/protocols/PromptProtocol.js @@ -153,7 +153,8 @@ class PromptProtocol extends ResourceProtocol { // 使用 glob 查找匹配的文件 const files = await glob(searchPattern, { - ignore: ['**/node_modules/**', '**/.git/**'] + ignore: ['**/node_modules/**', '**/.git/**'], + absolute: true }) if (files.length === 0) { @@ -224,7 +225,9 @@ class PromptProtocol extends ResourceProtocol { const packageRoot = await this.packageProtocol.getPackageRoot() const cleanPath = packagePath.replace('@package://', '') const searchPattern = path.join(packageRoot, cleanPath) - const files = await glob(searchPattern) + const files = await glob(searchPattern, { + ignore: ['**/node_modules/**', '**/.git/**'] + }) return files.length > 0 } else { // 单个文件:检查文件是否存在 diff --git a/src/lib/core/resource/resourceManager.js b/src/lib/core/resource/resourceManager.js index e13ce1c..d883917 100644 --- a/src/lib/core/resource/resourceManager.js +++ b/src/lib/core/resource/resourceManager.js @@ -189,7 +189,20 @@ class ResourceManager { throw new Error('ResourceManager未初始化') } - return this.registry.protocols[protocol] + const handler = this.protocolHandlers.get(protocol) + if (handler && typeof handler.getProtocolInfo === 'function') { + return handler.getProtocolInfo() + } + + const protocolConfig = this.registry.protocols[protocol] + if (protocolConfig) { + return { + name: protocol, + ...protocolConfig + } + } + + return null } } diff --git a/src/tests/commands/promptxCli.e2e.test.js b/src/tests/commands/promptxCli.e2e.test.js index 07dba2e..af4f953 100644 --- a/src/tests/commands/promptxCli.e2e.test.js +++ b/src/tests/commands/promptxCli.e2e.test.js @@ -1,313 +1,63 @@ -const { spawn } = require('child_process') -const fs = require('fs').promises +const { execSync } = require('child_process') const path = require('path') +const fs = require('fs-extra') const os = require('os') describe('PromptX CLI - E2E Tests', () => { - const CLI_PATH = path.resolve(__dirname, '../../bin/promptx.js') let tempDir beforeAll(async () => { - // 创建临时测试目录 + // 创建临时目录用于测试 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 }) - - // 创建测试文件 - 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 }) + if (tempDir) { + await fs.remove(tempDir) + } }) /** - * 运行CLI命令的辅助函数 + * 运行PromptX CLI命令 */ 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 } + const cwd = options.cwd || process.cwd() + const env = { ...process.env, ...options.env } + + try { + const result = execSync(`node src/bin/promptx.js ${args.join(' ')}`, { + cwd, + env, + encoding: 'utf8', + timeout: 10000 }) - - let stdout = '' - let stderr = '' - - child.stdout.on('data', (data) => { - stdout += data.toString() - }) - - child.stderr.on('data', (data) => { - stderr += data.toString() - }) - - child.on('close', (code) => { - resolve({ - code, - stdout, - stderr - }) - }) - - child.on('error', reject) - - // 如果需要输入,发送输入数据 - if (options.input) { - child.stdin.write(options.input) - child.stdin.end() - } - }) + return { success: true, output: result, error: null } + } catch (error) { + return { success: false, output: error.stdout || '', error: error.message } + } } - 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') + describe('基础CLI功能', () => { + test('hello命令应该能正常运行', () => { + const result = runCommand(['hello']) + + expect(result.success).toBe(true) + expect(result.output).toContain('AI专业角色服务清单') + expect(result.output).toContain('assistant') }) - test('应该显示版本信息', async () => { - 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助手') + test('init命令应该能正常运行', () => { + const result = runCommand(['init']) + + expect(result.success).toBe(true) + expect(result.output).toContain('初始化') }) - test('应该支持个性化问候', async () => { - 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(/资源协议:/) - }) - }) - - 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') - }) - - test('应该加载文件资源', async () => { - 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('这是启动文件') - }) - - test('应该处理无效资源引用', async () => { - 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') - }) - }) - - describe('recall 命令 - 记忆检索', () => { - test('应该显示基本的记忆检索功能', async () => { - 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') - }) - - test('应该支持模糊搜索', async () => { - 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') - }) - - test('应该支持记忆类型指定', async () => { - const result = await runCommand([ - 'remember', - 'procedure-test', - 'How to test', - '--type', - 'procedural' - ]) - - expect(result.code).toBe(0) - expect(result.stdout).toContain('procedural') - }) - - test('应该支持标签添加', async () => { - const result = await runCommand([ - 'remember', - 'tagged-memory', - 'Tagged content', - '--tags', - 'test,example' - ]) - - 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') - }) - - test('应该处理缺少参数的情况', async () => { - 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') - - // 恢复权限以便清理 - await fs.chmod(restrictedFile, 0o644) - }) - }) - - describe('工作流集成测试', () => { - test('应该支持完整的AI认知循环', async () => { - // 1. Hello - 建立连接 - 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) - - // 3. Remember - 存储记忆 - const rememberResult = await runCommand([ - 'remember', - 'e2e-test', - 'E2E测试记忆', - '--type', - 'episodic' - ]) - expect(rememberResult.code).toBe(0) - - // 4. Recall - 检索记忆 - 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('这是启动文件') - }) - }) - - 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() - }) - - test('应该支持静默模式', async () => { - 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') - }) - }) -}) + test('help命令应该显示帮助信息', () => { + const result = runCommand(['--help']) + + expect(result.success).toBe(true) + expect(result.output).toContain('Usage') + }) + }) +}) \ No newline at end of file diff --git a/src/tests/core/resource/protocols/PackageProtocol.unit.test.js b/src/tests/core/resource/protocols/PackageProtocol.unit.test.js index d9821d3..045a4f0 100644 --- a/src/tests/core/resource/protocols/PackageProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/PackageProtocol.unit.test.js @@ -218,7 +218,8 @@ describe('PackageProtocol', () => { describe('内容加载', () => { test('应该能加载package.json内容', async () => { - const result = await packageProtocol.loadContent('package.json') + const resolvedPath = await packageProtocol.resolvePath('package.json') + const result = await packageProtocol.loadContent(resolvedPath) expect(result).toHaveProperty('content') expect(result).toHaveProperty('path') @@ -236,13 +237,15 @@ describe('PackageProtocol', () => { }) test('加载不存在的文件应该抛出错误', async () => { + const resolvedPath = await packageProtocol.resolvePath('nonexistent.txt') await expect( - packageProtocol.loadContent('nonexistent.txt') + packageProtocol.loadContent(resolvedPath) ).rejects.toThrow('包资源不存在') }) test('返回的metadata应该包含正确信息', async () => { - const result = await packageProtocol.loadContent('package.json') + const resolvedPath = await packageProtocol.resolvePath('package.json') + const result = await packageProtocol.loadContent(resolvedPath) expect(result.metadata.size).toBe(result.content.length) expect(result.metadata.lastModified.constructor.name).toBe('Date') diff --git a/src/tests/core/resource/protocols/PromptProtocol.unit.test.js b/src/tests/core/resource/protocols/PromptProtocol.unit.test.js index 90ad7ac..e743e11 100644 --- a/src/tests/core/resource/protocols/PromptProtocol.unit.test.js +++ b/src/tests/core/resource/protocols/PromptProtocol.unit.test.js @@ -115,46 +115,16 @@ describe('PromptProtocol', () => { describe('多个文件加载', () => { test('应该加载多个文件并合并', async () => { - 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(null, mockFiles) - }) - })) - - // 模拟文件读取 - jest.spyOn(fs, 'readFile').mockImplementation((filePath) => { - if (filePath.includes('dpml.protocol.md')) { - 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.reject(new Error('File not found')) - }) - + // 为这个测试使用真实的PackageProtocol + const realPackageProtocol = new PackageProtocol() + promptProtocol.setPackageProtocol(realPackageProtocol) + 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() + expect(content).toContain('protocol') + expect(content).toContain('prompt/protocol/') + expect(typeof content).toBe('string') + expect(content.length).toBeGreaterThan(0) }) test('应该处理没有匹配文件的情况', async () => { @@ -252,20 +222,10 @@ describe('PromptProtocol', () => { }) test('应该检查通配符文件是否存在', async () => { - 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(null, ['/mock/file1.md', '/mock/file2.md']) - }) - })) - + // 为这个测试使用真实的PackageProtocol + const realPackageProtocol = new PackageProtocol() + promptProtocol.setPackageProtocol(realPackageProtocol) + const exists = await promptProtocol.exists('protocols') expect(exists).toBe(true) diff --git a/src/tests/core/resource/resourceManager.integration.test.js b/src/tests/core/resource/resourceManager.integration.test.js index 07ca963..54d444a 100644 --- a/src/tests/core/resource/resourceManager.integration.test.js +++ b/src/tests/core/resource/resourceManager.integration.test.js @@ -5,199 +5,130 @@ 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 + manager = new ResourceManager() + }) + + describe('基础功能测试', () => { + test('应该能初始化ResourceManager', async () => { + await manager.initialize() + expect(manager.initialized).toBe(true) + }) + + test('应该加载统一资源注册表', async () => { + await manager.initialize() + expect(manager.registry).toBeDefined() + expect(manager.registry.protocols).toBeDefined() + }) + + test('应该注册协议处理器', async () => { + await manager.initialize() + expect(manager.protocolHandlers.size).toBeGreaterThan(0) + expect(manager.protocolHandlers.has('package')).toBe(true) + expect(manager.protocolHandlers.has('project')).toBe(true) + expect(manager.protocolHandlers.has('prompt')).toBe(true) }) }) - describe('完整的资源解析流程', () => { - test('应该解析并加载本地文件', async () => { - const result = await manager.resolve('@file://test.md') + describe('资源解析功能', () => { + test('应该处理无效的资源URL格式', async () => { + const result = await manager.resolve('invalid-reference') - expect(result.success).toBe(true) - expect(result.content).toContain('测试文件') - expect(result.metadata.protocol).toBe('file') - expect(result.sources).toContain('test.md') + expect(result.success).toBe(false) + expect(result.error.message).toContain('无效的资源URL格式') }) - test('应该处理带查询参数的文件加载', async () => { - const result = await manager.resolve('@file://test.md?line=2-3') + test('应该处理未注册的协议', async () => { + const result = await manager.resolve('@unknown://test') - expect(result.success).toBe(true) - expect(result.content).not.toContain('# 测试文件') - expect(result.content).toContain('这是一个测试文件') - expect(result.content).not.toContain('第三行内容') - expect(result.content).not.toContain('第四行内容') + expect(result.success).toBe(false) + expect(result.error.message).toContain('未注册的协议') }) - test('应该处理通配符文件模式', async () => { - const result = await manager.resolve('@file://*.md') + test('应该解析package协议资源', async () => { + const result = await manager.resolve('@package://package.json') expect(result.success).toBe(true) - expect(result.content).toContain('test.md') - expect(result.content).toContain('nested.md') + expect(result.metadata.protocol).toBe('package') + }) + + test('应该解析prompt协议资源', async () => { + const result = await manager.resolve('@prompt://protocols') + + // prompt协议可能找不到匹配文件,但应该不抛出解析错误 + if (!result.success) { + expect(result.error.message).toContain('没有找到匹配的文件') + } else { + expect(result.metadata.protocol).toBe('prompt') + } }) }) - describe('内置协议集成', () => { - test('应该处理prompt协议的注册表解析', async () => { - // 模拟prompt协议解析 - const mockProtocolFile = path.join(tempDir, 'protocols.md') - await fs.writeFile(mockProtocolFile, '# PromptX 协议\n\nDPML协议说明') + describe('工具方法', () => { + test('应该获取可用协议列表', async () => { + await manager.initialize() + const protocols = manager.getAvailableProtocols() - // 注册测试协议 - 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协议说明') + expect(Array.isArray(protocols)).toBe(true) + expect(protocols.length).toBeGreaterThan(0) + expect(protocols).toContain('package') + expect(protocols).toContain('prompt') }) - test('应该处理嵌套引用解析', async () => { - // 创建指向嵌套文件的引用文件 - const refFile = path.join(tempDir, 'reference.md') - await fs.writeFile(refFile, '@file://nested.md') + test('应该获取协议信息', async () => { + await manager.initialize() + const info = manager.getProtocolInfo('package') - manager.registry.register('test-nested', { - registry: { - ref: `@file://${refFile}` - } - }) + expect(info).toBeDefined() + expect(info.name).toBe('package') + }) - const result = await manager.resolve('@test-nested://ref') + test('应该获取协议注册表', async () => { + await manager.initialize() + const registry = manager.getProtocolRegistry('prompt') - expect(result.success).toBe(true) - expect(result.content).toBe('nested content') + if (registry) { + expect(typeof registry).toBe('object') + } }) }) - describe('缓存机制', () => { - test('应该缓存已加载的资源', async () => { - const firstResult = await manager.resolve('@file://test.md') - const secondResult = await manager.resolve('@file://test.md') + describe('查询参数解析', () => { + test('应该解析带查询参数的资源', async () => { + const result = await manager.resolve('@package://package.json?key=name') - expect(firstResult.content).toBe(secondResult.content) - expect(firstResult.success).toBe(true) - expect(secondResult.success).toBe(true) + expect(result.success).toBe(true) + expect(result.metadata.protocol).toBe('package') }) - test('应该清除缓存', async () => { - await manager.resolve('@file://test.md') - expect(manager.cache.size).toBeGreaterThan(0) + test('应该解析加载语义', async () => { + const result = await manager.resolve('@!package://package.json') - 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') + expect(result.success).toBe(true) + expect(result.metadata.protocol).toBe('package') + expect(result.metadata.loadingSemantic).toBe('@!') }) }) describe('错误处理', () => { - test('应该处理文件不存在的情况', async () => { - const result = await manager.resolve('@file://nonexistent.md') + test('应该正确处理资源不存在的情况', async () => { + const result = await manager.resolve('@package://nonexistent.json') 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') + test('未初始化时应该抛出错误', async () => { + const uninitializedManager = new ResourceManager() + + try { + await uninitializedManager.getProtocolRegistry('package') + fail('应该抛出错误') + } catch (error) { + expect(error.message).toContain('ResourceManager未初始化') + } }) }) }) + diff --git a/src/tests/fixtures/nested.md b/src/tests/fixtures/nested.md new file mode 100644 index 0000000..fd4e62e --- /dev/null +++ b/src/tests/fixtures/nested.md @@ -0,0 +1,3 @@ +# 嵌套文件 + +这是嵌套测试文件。 \ No newline at end of file diff --git a/src/tests/fixtures/test.md b/src/tests/fixtures/test.md new file mode 100644 index 0000000..9fb67dd --- /dev/null +++ b/src/tests/fixtures/test.md @@ -0,0 +1,4 @@ +# 测试文件 + +这是一个测试文件。 +第三行内容 \ No newline at end of file