From 39ddc6a97f7cd69cba093836307247f15d3abf62 Mon Sep 17 00:00:00 2001 From: sean Date: Thu, 19 Jun 2025 17:15:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0DACP=E7=99=BD=E7=9A=AE?= =?UTF-8?q?=E4=B9=A6=E7=9A=84=E6=9B=B4=E6=96=B0=E6=97=A5=E6=9C=9F=E8=87=B3?= =?UTF-8?q?2025-01-19=EF=BC=9B=E5=9C=A8DACPConfigManager=E4=B8=AD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E9=85=8D=E7=BD=AE=E7=AE=A1=E7=90=86=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=A1=B9=E7=9B=AE=E7=BA=A7=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BA=A7=E9=85=8D=E7=BD=AE=E7=9A=84=E4=BC=98=E5=85=88=E7=BA=A7?= =?UTF-8?q?=E5=A4=84=E7=90=86=EF=BC=8C=E5=A2=9E=E5=BC=BA=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E4=BF=A1=E6=81=AF=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=96=B9=E6=B3=95=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E6=93=8D=E4=BD=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dacp-config-management.md | 254 ++++++++++++++++++ docs/dacp-whitepaper.md | 2 +- .../dacp-promptx-service/actions/email.js | 4 +- src/lib/utils/DACPConfigManager.js | 231 +++++++++++++--- src/tests/setup.js | 28 ++ src/tests/unit/DACPConfigManager.unit.test.js | 226 ++++++++++++++++ 6 files changed, 703 insertions(+), 42 deletions(-) create mode 100644 docs/dacp-config-management.md create mode 100644 src/tests/setup.js create mode 100644 src/tests/unit/DACPConfigManager.unit.test.js diff --git a/docs/dacp-config-management.md b/docs/dacp-config-management.md new file mode 100644 index 0000000..ec0e878 --- /dev/null +++ b/docs/dacp-config-management.md @@ -0,0 +1,254 @@ +# DACP配置管理指南 + +**版本**: 1.0.0 +**更新日期**: 2025-01-19 +**作者**: Sean + +--- + +## 概述 + +DACP配置管理系统支持分层配置策略,实现了项目级配置优先、用户级配置回退的灵活配置管理机制。这允许团队在项目中共享配置,同时保持个人配置的独立性。 + +## 配置优先级 + +``` +项目级配置 (.promptx/dacp/) > 用户级配置 (~/.promptx/dacp/) +``` + +### 优先级说明 + +1. **项目级配置** - 位于当前项目的 `.promptx/dacp/` 目录 + - 优先级最高 + - 适合团队共享的项目配置 + - 可以版本控制管理 + +2. **用户级配置** - 位于用户主目录的 `~/.promptx/dacp/` 目录 + - 作为回退选择 + - 个人私有配置 + - 跨项目通用配置 + +## 配置文件结构 + +### 邮件服务配置示例 (send_email.json) + +```json +{ + "provider": "gmail", + "smtp": { + "user": "your-email@gmail.com", + "password": "your-app-password" + }, + "sender": { + "name": "Your Name", + "email": "your-email@gmail.com" + } +} +``` + +### 支持的邮件服务商 + +- **Gmail**: smtp.gmail.com:587 +- **Outlook**: smtp-mail.outlook.com:587 +- **QQ邮箱**: smtp.qq.com:465 +- **163邮箱**: smtp.163.com:465 +- **126邮箱**: smtp.126.com:465 + +## 使用方式 + +### 1. 项目级配置(推荐) + +创建项目级配置文件: + +```bash +# 创建配置目录 +mkdir -p .promptx/dacp + +# 创建邮件配置文件 +cat > .promptx/dacp/send_email.json << 'EOF' +{ + "provider": "gmail", + "smtp": { + "user": "project-team@gmail.com", + "password": "project-app-password" + }, + "sender": { + "name": "Project Team", + "email": "project-team@gmail.com" + } +} +EOF +``` + +### 2. 用户级配置(个人回退) + +创建用户级配置文件: + +```bash +# 创建用户配置目录 +mkdir -p ~/.promptx/dacp + +# 创建个人邮件配置 +cat > ~/.promptx/dacp/send_email.json << 'EOF' +{ + "provider": "gmail", + "smtp": { + "user": "personal@gmail.com", + "password": "personal-app-password" + }, + "sender": { + "name": "Personal Name", + "email": "personal@gmail.com" + } +} +EOF +``` + +## Gmail配置特别说明 + +### 应用专用密码设置 + +Gmail用户需要使用应用专用密码: + +1. 进入 [Google 账户设置](https://myaccount.google.com) +2. 启用两步验证 +3. 生成应用专用密码 +4. 在配置文件中使用生成的密码 + +### 配置示例 + +```json +{ + "provider": "gmail", + "smtp": { + "user": "yourname@gmail.com", + "password": "abcd efgh ijkl mnop" // 应用专用密码(16位,含空格) + }, + "sender": { + "name": "Your Name", + "email": "yourname@gmail.com" + } +} +``` + +## 配置管理命令 + +### 检查配置状态 + +```javascript +const DACPConfigManager = require('./src/lib/utils/DACPConfigManager') +const configManager = new DACPConfigManager() + +// 检查是否有配置(任意级别) +const hasConfig = await configManager.hasActionConfig('send_email') + +// 检查项目级配置 +const hasProjectConfig = await configManager.hasProjectActionConfig('send_email') + +// 检查用户级配置 +const hasUserConfig = await configManager.hasUserActionConfig('send_email') +``` + +### 读取配置 + +```javascript +// 读取配置(自动优先级选择) +const config = await configManager.readActionConfig('send_email') + +// 明确读取项目级配置 +const projectConfig = await configManager.readProjectActionConfig('send_email') + +// 明确读取用户级配置 +const userConfig = await configManager.readUserActionConfig('send_email') +``` + +### 写入配置 + +```javascript +const emailConfig = { + provider: "gmail", + smtp: { + user: "example@gmail.com", + password: "app-password" + }, + sender: { + name: "Example User", + email: "example@gmail.com" + } +} + +// 写入项目级配置 +await configManager.writeProjectActionConfig('send_email', emailConfig) + +// 写入用户级配置 +await configManager.writeUserActionConfig('send_email', emailConfig) +``` + +## 最佳实践 + +### 1. 团队协作 + +- **项目配置**: 使用通用的项目配置,可以提交到版本控制 +- **敏感信息**: 个人敏感信息(如密码)使用用户级配置 +- **配置模板**: 在项目中提供配置模板,团队成员复制后修改 + +### 2. 安全性 + +- **不要提交密码**: 项目级配置可以包含结构,但不应包含真实密码 +- **使用应用密码**: Gmail等服务使用应用专用密码 +- **权限控制**: 确保配置文件权限设置合理 + +### 3. 配置继承 + +当前版本支持完全覆盖模式: +- 如果存在项目级配置,完全使用项目级配置 +- 如果不存在项目级配置,回退到用户级配置 +- 未来版本可能支持配置合并模式 + +## 错误处理 + +### 常见错误和解决方案 + +1. **配置文件不存在** + ``` + 解决方案: 按照上述步骤创建配置文件 + ``` + +2. **项目目录无法获取** + ``` + 解决方案: 确保在PromptX项目目录中运行,或使用用户级配置 + ``` + +3. **SMTP认证失败** + ``` + 解决方案: 检查用户名、密码和服务器配置 + ``` + +4. **Gmail应用密码问题** + ``` + 解决方案: 重新生成应用专用密码,确保格式正确 + ``` + +## 版本兼容性 + +- **向后兼容**: 现有用户级配置继续工作 +- **API兼容**: 原有API方法保持不变 +- **渐进升级**: 可以逐步迁移到项目级配置 + +## 扩展功能 + +### 未来规划 + +1. **配置合并模式**: 支持项目级和用户级配置的智能合并 +2. **配置验证**: 增强的配置验证和错误提示 +3. **配置模板**: 内置常用配置模板 +4. **环境变量支持**: 支持通过环境变量覆盖配置 +5. **配置加密**: 敏感信息的加密存储 + +--- + +## 参考资料 + +- [DACP白皮书](./dacp-whitepaper.md) +- [MCP集成指南](./mcp-integration-guide.md) +- [PromptX架构原理](./promptx-architecture-principle.md) \ No newline at end of file diff --git a/docs/dacp-whitepaper.md b/docs/dacp-whitepaper.md index b8b008e..d8e486c 100644 --- a/docs/dacp-whitepaper.md +++ b/docs/dacp-whitepaper.md @@ -4,7 +4,7 @@ **版本**: 1.0.0-draft **作者**: Deepractice Team **创建日期**: 2024-12-17 -**更新日期**: 2024-12-17 +**更新日期**: 2025-01-19 --- diff --git a/src/dacp/dacp-promptx-service/actions/email.js b/src/dacp/dacp-promptx-service/actions/email.js index 3df22af..026aa74 100644 --- a/src/dacp/dacp-promptx-service/actions/email.js +++ b/src/dacp/dacp-promptx-service/actions/email.js @@ -144,7 +144,7 @@ async function executeSendEmail(emailData, context) { const validation = configManager.validateEmailConfig(config) if (!validation.valid) { // 配置无效,抛出友好错误 - const errorMessage = configManager.generateConfigErrorMessage('send_email', validation) + const errorMessage = await configManager.generateConfigErrorMessage('send_email', validation) throw new Error(errorMessage) } @@ -169,7 +169,7 @@ async function executeDemoSendEmail(emailData, context) { await new Promise(resolve => setTimeout(resolve, 100)); const configManager = new DACPConfigManager() - const configHint = configManager.generateConfigErrorMessage('send_email') + const configHint = await configManager.generateConfigErrorMessage('send_email') return { message_id: `demo_msg_${Date.now()}`, diff --git a/src/lib/utils/DACPConfigManager.js b/src/lib/utils/DACPConfigManager.js index 9a5cc31..284d104 100644 --- a/src/lib/utils/DACPConfigManager.js +++ b/src/lib/utils/DACPConfigManager.js @@ -1,71 +1,214 @@ const fs = require('fs-extra') const path = require('path') const os = require('os') +const { getDirectoryService } = require('./DirectoryService') /** - * DACP用户级配置管理器 - * 管理 ~/.promptx/dacp/ 下的配置文件 + * DACP配置管理器 + * 支持项目级配置优先,用户级配置回退的分层配置策略 + * 配置优先级:项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/) */ class DACPConfigManager { constructor() { this.userHome = os.homedir() - this.dacpConfigDir = path.join(this.userHome, '.promptx', 'dacp') + this.userDacpConfigDir = path.join(this.userHome, '.promptx', 'dacp') + this.directoryService = getDirectoryService() } /** - * 确保DACP配置目录存在 + * 确保用户级DACP配置目录存在 */ - async ensureConfigDir() { - await fs.ensureDir(this.dacpConfigDir) + async ensureUserConfigDir() { + await fs.ensureDir(this.userDacpConfigDir) } /** - * 获取指定action的配置文件路径 - * @param {string} action - action名称,如 'send_email' - * @returns {string} 配置文件完整路径 + * 获取项目级DACP配置目录路径 + * @returns {Promise} 项目级配置目录路径或null */ - getConfigPath(action) { - return path.join(this.dacpConfigDir, `${action}.json`) - } - - /** - * 读取action配置 - * @param {string} action - action名称 - * @returns {Promise} 配置对象或null - */ - async readActionConfig(action) { - const configPath = this.getConfigPath(action) - + async getProjectConfigDir() { try { - if (await fs.pathExists(configPath)) { - return await fs.readJson(configPath) - } - return null + const promptxDir = await this.directoryService.getPromptXDirectory() + return path.join(promptxDir, 'dacp') } catch (error) { - console.warn(`读取DACP配置失败 ${action}:`, error.message) + console.warn('获取项目级配置目录失败:', error.message) return null } } /** - * 写入action配置 + * 确保项目级DACP配置目录存在 + * @returns {Promise} 项目级配置目录路径或null + */ + async ensureProjectConfigDir() { + const projectConfigDir = await this.getProjectConfigDir() + if (projectConfigDir) { + await fs.ensureDir(projectConfigDir) + return projectConfigDir + } + return null + } + + /** + * 获取指定action的用户级配置文件路径 + * @param {string} action - action名称,如 'send_email' + * @returns {string} 用户级配置文件完整路径 + */ + getUserConfigPath(action) { + return path.join(this.userDacpConfigDir, `${action}.json`) + } + + /** + * 获取指定action的项目级配置文件路径 + * @param {string} action - action名称,如 'send_email' + * @returns {Promise} 项目级配置文件完整路径或null + */ + async getProjectConfigPath(action) { + const projectConfigDir = await this.getProjectConfigDir() + if (projectConfigDir) { + return path.join(projectConfigDir, `${action}.json`) + } + return null + } + + /** + * 获取指定action的配置文件路径(用户级,向后兼容) + * @param {string} action - action名称,如 'send_email' + * @returns {string} 配置文件完整路径 + * @deprecated 使用getUserConfigPath或getProjectConfigPath + */ + getConfigPath(action) { + return this.getUserConfigPath(action) + } + + /** + * 读取项目级action配置 + * @param {string} action - action名称 + * @returns {Promise} 配置对象或null + */ + async readProjectActionConfig(action) { + try { + const projectConfigPath = await this.getProjectConfigPath(action) + if (projectConfigPath && await fs.pathExists(projectConfigPath)) { + const config = await fs.readJson(projectConfigPath) + console.log(`📁 使用项目级DACP配置: ${action}`) + return config + } + } catch (error) { + console.warn(`读取项目级DACP配置失败 ${action}:`, error.message) + } + return null + } + + /** + * 读取用户级action配置 + * @param {string} action - action名称 + * @returns {Promise} 配置对象或null + */ + async readUserActionConfig(action) { + const userConfigPath = this.getUserConfigPath(action) + + try { + if (await fs.pathExists(userConfigPath)) { + const config = await fs.readJson(userConfigPath) + console.log(`🏠 使用用户级DACP配置: ${action}`) + return config + } + } catch (error) { + console.warn(`读取用户级DACP配置失败 ${action}:`, error.message) + } + return null + } + + /** + * 读取action配置(项目级优先,用户级回退) + * @param {string} action - action名称 + * @returns {Promise} 配置对象或null + */ + async readActionConfig(action) { + // 优先级:项目级 > 用户级 + const projectConfig = await this.readProjectActionConfig(action) + if (projectConfig) { + return projectConfig + } + + return await this.readUserActionConfig(action) + } + + /** + * 写入用户级action配置 * @param {string} action - action名称 * @param {Object} config - 配置对象 */ - async writeActionConfig(action, config) { - await this.ensureConfigDir() - const configPath = this.getConfigPath(action) + async writeUserActionConfig(action, config) { + await this.ensureUserConfigDir() + const configPath = this.getUserConfigPath(action) await fs.writeJson(configPath, config, { spaces: 2 }) } /** - * 检查action配置是否存在 + * 写入项目级action配置 + * @param {string} action - action名称 + * @param {Object} config - 配置对象 + */ + async writeProjectActionConfig(action, config) { + const projectConfigDir = await this.ensureProjectConfigDir() + if (projectConfigDir) { + const configPath = path.join(projectConfigDir, `${action}.json`) + await fs.writeJson(configPath, config, { spaces: 2 }) + } else { + throw new Error('无法获取项目目录,写入项目级配置失败') + } + } + + /** + * 写入action配置(向后兼容,写入用户级) + * @param {string} action - action名称 + * @param {Object} config - 配置对象 + * @deprecated 使用writeUserActionConfig或writeProjectActionConfig + */ + async writeActionConfig(action, config) { + return await this.writeUserActionConfig(action, config) + } + + /** + * 检查项目级action配置是否存在 + * @param {string} action - action名称 + * @returns {Promise} + */ + async hasProjectActionConfig(action) { + try { + const projectConfigPath = await this.getProjectConfigPath(action) + if (!projectConfigPath) { + return false + } + return await fs.pathExists(projectConfigPath) + } catch (error) { + return false + } + } + + /** + * 检查用户级action配置是否存在 + * @param {string} action - action名称 + * @returns {Promise} + */ + async hasUserActionConfig(action) { + const userConfigPath = this.getUserConfigPath(action) + return await fs.pathExists(userConfigPath) + } + + /** + * 检查action配置是否存在(项目级或用户级) * @param {string} action - action名称 * @returns {Promise} */ async hasActionConfig(action) { - const configPath = this.getConfigPath(action) - return await fs.pathExists(configPath) + const hasProject = await this.hasProjectActionConfig(action) + if (hasProject) { + return true + } + return await this.hasUserActionConfig(action) } /** @@ -162,17 +305,23 @@ class DACPConfigManager { * 生成配置错误提示信息 * @param {string} action - action名称 * @param {Object} validation - 验证结果 - * @returns {string} 错误提示信息 + * @returns {Promise} 错误提示信息 */ - generateConfigErrorMessage(action, validation = null) { - const configPath = this.getConfigPath(action) + async generateConfigErrorMessage(action, validation = null) { + const userConfigPath = this.getUserConfigPath(action) + const projectConfigPath = await this.getProjectConfigPath(action) let message = `\n📧 DACP邮件服务配置缺失\n\n` if (!validation) { // 配置文件不存在 - message += `❌ 配置文件不存在: ${configPath}\n\n` - message += `📝 请创建配置文件,内容如下:\n\n` + message += `❌ 未找到配置文件\n\n` + message += `🔍 查找路径:\n` + if (projectConfigPath) { + message += ` 项目级: ${projectConfigPath} (优先)\n` + } + message += ` 用户级: ${userConfigPath} (回退)\n\n` + message += `📝 推荐创建项目级配置文件,内容如下:\n\n` message += `{\n` message += ` "provider": "gmail",\n` message += ` "smtp": {\n` @@ -185,6 +334,7 @@ class DACPConfigManager { message += ` }\n` message += `}\n\n` message += `💡 支持的邮件服务商: gmail, outlook, qq, 163, 126\n\n` + message += `🏗️ 配置优先级: 项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/)\n\n` message += `🔐 Gmail用户需要使用应用专用密码:\n` message += ` 1. 进入 Google 账户设置\n` message += ` 2. 启用两步验证\n` @@ -192,7 +342,10 @@ class DACPConfigManager { message += ` 4. 使用生成的密码替换上面的 "your-app-password"\n` } else { // 配置不完整 - message += `❌ 配置文件存在但不完整: ${configPath}\n\n` + const configLocation = await this.hasProjectActionConfig(action) ? + (projectConfigPath ? `项目级: ${projectConfigPath}` : '项目级配置') : + `用户级: ${userConfigPath}` + message += `❌ 配置文件存在但不完整: ${configLocation}\n\n` message += `缺少以下配置项:\n` validation.errors.forEach(error => { message += ` • ${error}\n` diff --git a/src/tests/setup.js b/src/tests/setup.js new file mode 100644 index 0000000..be347a6 --- /dev/null +++ b/src/tests/setup.js @@ -0,0 +1,28 @@ +// Jest测试环境设置文件 + +// 全局测试超时设置 +jest.setTimeout(15000) + +// 控制台输出配置 +const originalError = console.error +console.error = (...args) => { + // 过滤掉某些预期的警告信息 + if (args[0] && typeof args[0] === 'string') { + if (args[0].includes('Warning: ReactDOM.render is deprecated')) return + if (args[0].includes('Warning: componentWillReceiveProps')) return + } + originalError(...args) +} + +// 模拟环境变量 +process.env.NODE_ENV = 'test' +process.env.PROMPTX_TEST_MODE = 'true' + +// 清理函数 +afterEach(() => { + // 清理任何测试状态 +}) + +afterAll(() => { + // 最终清理 +}) \ No newline at end of file diff --git a/src/tests/unit/DACPConfigManager.unit.test.js b/src/tests/unit/DACPConfigManager.unit.test.js new file mode 100644 index 0000000..fb535b6 --- /dev/null +++ b/src/tests/unit/DACPConfigManager.unit.test.js @@ -0,0 +1,226 @@ +const fs = require('fs-extra') +const path = require('path') +const os = require('os') +const DACPConfigManager = require('../../lib/utils/DACPConfigManager') + +// Mock DirectoryService +jest.mock('../../lib/utils/DirectoryService', () => ({ + getDirectoryService: () => ({ + getPromptXDirectory: jest.fn() + }) +})) + +const { getDirectoryService } = require('../../lib/utils/DirectoryService') + +describe('DACPConfigManager - 项目级配置优先', () => { + let configManager + let mockDirectoryService + let tempDir + let userConfigDir + let projectConfigDir + + beforeEach(async () => { + // 创建临时测试目录 + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'dacp-config-test-')) + userConfigDir = path.join(tempDir, 'user', '.promptx', 'dacp') + projectConfigDir = path.join(tempDir, 'project', '.promptx', 'dacp') + + // 确保目录存在 + await fs.ensureDir(userConfigDir) + await fs.ensureDir(projectConfigDir) + + // Mock DirectoryService + mockDirectoryService = getDirectoryService() + mockDirectoryService.getPromptXDirectory.mockResolvedValue(path.join(tempDir, 'project', '.promptx')) + + // 创建配置管理器 + configManager = new DACPConfigManager() + + // Mock用户目录 + configManager.userDacpConfigDir = userConfigDir + }) + + afterEach(async () => { + // 清理临时目录 + await fs.remove(tempDir) + jest.clearAllMocks() + }) + + describe('配置读取优先级', () => { + test('应该优先读取项目级配置', async () => { + const projectConfig = { + provider: 'gmail', + smtp: { user: 'project@gmail.com', password: 'project-pass' }, + sender: { name: 'Project User', email: 'project@gmail.com' } + } + + const userConfig = { + provider: 'outlook', + smtp: { user: 'user@outlook.com', password: 'user-pass' }, + sender: { name: 'User Name', email: 'user@outlook.com' } + } + + // 写入两个配置文件 + await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), projectConfig) + await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig) + + const result = await configManager.readActionConfig('send_email') + + expect(result).toEqual(projectConfig) + expect(result.smtp.user).toBe('project@gmail.com') + }) + + test('项目级配置不存在时应该回退到用户级配置', async () => { + const userConfig = { + provider: 'outlook', + smtp: { user: 'user@outlook.com', password: 'user-pass' }, + sender: { name: 'User Name', email: 'user@outlook.com' } + } + + // 只写入用户级配置 + await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig) + + const result = await configManager.readActionConfig('send_email') + + expect(result).toEqual(userConfig) + expect(result.smtp.user).toBe('user@outlook.com') + }) + + test('两个配置都不存在时应该返回null', async () => { + const result = await configManager.readActionConfig('send_email') + expect(result).toBeNull() + }) + }) + + describe('配置存在性检查', () => { + test('hasActionConfig应该检查项目级和用户级配置', async () => { + // 无配置时 + expect(await configManager.hasActionConfig('send_email')).toBe(false) + + // 仅用户级配置时 + await fs.writeJson(path.join(userConfigDir, 'send_email.json'), {}) + expect(await configManager.hasActionConfig('send_email')).toBe(true) + + // 同时存在项目级配置时 + await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {}) + expect(await configManager.hasActionConfig('send_email')).toBe(true) + }) + + test('hasProjectActionConfig应该正确检查项目级配置', async () => { + expect(await configManager.hasProjectActionConfig('send_email')).toBe(false) + + await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {}) + expect(await configManager.hasProjectActionConfig('send_email')).toBe(true) + }) + + test('hasUserActionConfig应该正确检查用户级配置', async () => { + expect(await configManager.hasUserActionConfig('send_email')).toBe(false) + + await fs.writeJson(path.join(userConfigDir, 'send_email.json'), {}) + expect(await configManager.hasUserActionConfig('send_email')).toBe(true) + }) + }) + + describe('配置写入', () => { + test('writeProjectActionConfig应该写入项目级配置', async () => { + const config = { + provider: 'gmail', + smtp: { user: 'test@gmail.com', password: 'test-pass' }, + sender: { name: 'Test User', email: 'test@gmail.com' } + } + + await configManager.writeProjectActionConfig('send_email', config) + + const projectConfigPath = path.join(projectConfigDir, 'send_email.json') + expect(await fs.pathExists(projectConfigPath)).toBe(true) + + const savedConfig = await fs.readJson(projectConfigPath) + expect(savedConfig).toEqual(config) + }) + + test('writeUserActionConfig应该写入用户级配置', async () => { + const config = { + provider: 'outlook', + smtp: { user: 'test@outlook.com', password: 'test-pass' }, + sender: { name: 'Test User', email: 'test@outlook.com' } + } + + await configManager.writeUserActionConfig('send_email', config) + + const userConfigPath = path.join(userConfigDir, 'send_email.json') + expect(await fs.pathExists(userConfigPath)).toBe(true) + + const savedConfig = await fs.readJson(userConfigPath) + expect(savedConfig).toEqual(config) + }) + }) + + describe('向后兼容性', () => { + test('原有API方法应该保持兼容', async () => { + const config = { + provider: 'gmail', + smtp: { user: 'legacy@gmail.com', password: 'legacy-pass' }, + sender: { name: 'Legacy User', email: 'legacy@gmail.com' } + } + + // writeActionConfig应该写入用户级配置 + await configManager.writeActionConfig('send_email', config) + + const userConfigPath = path.join(userConfigDir, 'send_email.json') + expect(await fs.pathExists(userConfigPath)).toBe(true) + + // getConfigPath应该返回用户级路径 + expect(configManager.getConfigPath('send_email')).toBe(userConfigPath) + }) + }) + + describe('错误处理', () => { + test('DirectoryService失败时应该优雅降级', async () => { + // Mock DirectoryService抛出错误 + mockDirectoryService.getPromptXDirectory.mockRejectedValue(new Error('项目目录不存在')) + + const userConfig = { + provider: 'gmail', + smtp: { user: 'user@gmail.com', password: 'user-pass' }, + sender: { name: 'User', email: 'user@gmail.com' } + } + + await fs.writeJson(path.join(userConfigDir, 'send_email.json'), userConfig) + + // 应该能够回退到用户级配置 + const result = await configManager.readActionConfig('send_email') + expect(result).toEqual(userConfig) + }) + + test('项目目录不可写时writeProjectActionConfig应该抛出错误', async () => { + mockDirectoryService.getPromptXDirectory.mockRejectedValue(new Error('无法获取项目目录')) + + const config = { provider: 'gmail' } + + await expect(configManager.writeProjectActionConfig('send_email', config)) + .rejects.toThrow('无法获取项目目录,写入项目级配置失败') + }) + }) + + describe('配置错误提示信息', () => { + test('应该生成包含项目级和用户级路径的错误信息', async () => { + const errorMessage = await configManager.generateConfigErrorMessage('send_email') + + expect(errorMessage).toContain('DACP邮件服务配置缺失') + expect(errorMessage).toContain('项目级:') + expect(errorMessage).toContain('用户级:') + expect(errorMessage).toContain('配置优先级: 项目级(.promptx/dacp/) > 用户级(~/.promptx/dacp/)') + }) + + test('应该正确标识当前使用的配置级别', async () => { + const validation = { valid: false, errors: ['缺少SMTP配置'] } + + // 创建项目级配置 + await fs.writeJson(path.join(projectConfigDir, 'send_email.json'), {}) + + const errorMessage = await configManager.generateConfigErrorMessage('send_email', validation) + expect(errorMessage).toContain('项目级:') + expect(errorMessage).toContain('缺少以下配置项') + }) + }) +}) \ No newline at end of file