diff --git a/PREVENT_CONFLICTS.md b/PREVENT_CONFLICTS.md index 5ee72f6..95e8799 100644 --- a/PREVENT_CONFLICTS.md +++ b/PREVENT_CONFLICTS.md @@ -47,7 +47,7 @@ This document outlines strategies to minimize merge conflicts and improve produc ### Long-term Strategy 1. **Adopt "Open for Extension, Closed for Modification"**: Design core classes to accept extensions (plugins, commands, routes) without requiring modification to the class source code. 2. **Automated Conflict Detection**: Implement pre-commit hooks or CI checks that flag potential conflict areas (e.g., large files, modification of frozen core files). -3. **Enhanced Testing**: Add unit tests specifically for the plugin registration and configuration loading logic to ensure refactoring doesn't introduce regressions. +3. [x] **Enhanced Testing**: Add unit tests specifically for the plugin registration and configuration loading logic to ensure refactoring doesn't introduce regressions. (Completed 2026-02-22) ## Conclusion By moving towards dynamic registration, stricter typing, and smaller, single-responsibility modules, the `dataprompt` library can significantly reduce the overhead of merge conflicts and enable seamless parallel development. diff --git a/tests/config.manager.test.ts b/tests/unit/config.manager.test.ts similarity index 71% rename from tests/config.manager.test.ts rename to tests/unit/config.manager.test.ts index cad9bdf..e0bddf0 100644 --- a/tests/config.manager.test.ts +++ b/tests/unit/config.manager.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; -import { ConfigManager } from '..//src/core/config.manager.js'; +import { ConfigManager } from '../../src/core/config.manager.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import { fileURLToPath } from 'url'; @@ -15,7 +15,7 @@ describe('ConfigManager with a config file', () => { // THIS IS THE KEY FIX: Add a package.json to the temp directory to properly // define it as the root for the test, preventing findUp from traversing higher. - await fs.writeFile(path.join(tempDir, 'package.json'), '{}'); + await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify({ type: 'module' })); const dpConfigContent = ` export default { @@ -58,13 +58,52 @@ describe('ConfigManager with a config file', () => { }); }); +describe('ConfigManager Validation', () => { + let tempDir: string; + + beforeAll(async () => { + tempDir = path.resolve(__dirname, 'temp_validation'); + await fs.mkdir(tempDir, { recursive: true }); + await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify({ type: 'module' })); + }); + + afterAll(async () => { + await fs.rm(tempDir, { recursive: true, force: true }); + }); + + it('should validate secrets against plugin schema', async () => { + const dpConfigContent = ` + import { z } from 'genkit'; + const secretPlugin = { + name: 'secret-plugin', + provideSecrets: () => ({ + secrets: {}, + schema: z.object({ + REQUIRED_SECRET: z.string().min(1) + }) + }) + }; + export default { + plugins: [secretPlugin], + secrets: {} + }; + `; + + await fs.writeFile(path.join(tempDir, 'dataprompt.config.js'), dpConfigContent); + + const configManager = new ConfigManager({ projectRoot: tempDir }); + + await expect(configManager.load()).rejects.toThrow(/REQUIRED_SECRET/); + }); +}); + describe('ConfigManager without a config file', () => { let tempDir: string; beforeAll(async () => { tempDir = path.resolve(__dirname, 'temp_without_config'); await fs.mkdir(tempDir, { recursive: true }); - await fs.writeFile(path.join(tempDir, 'package.json'), '{}'); + await fs.writeFile(path.join(tempDir, 'package.json'), JSON.stringify({ type: 'module' })); }); afterAll(async () => { diff --git a/tests/unit/plugin.manager.test.ts b/tests/unit/plugin.manager.test.ts index af2821e..130e45e 100644 --- a/tests/unit/plugin.manager.test.ts +++ b/tests/unit/plugin.manager.test.ts @@ -47,4 +47,28 @@ describe('PluginManager', () => { // Ensure defaults are still present expect(dataSources.map(ds => ds.name)).toContain('fetch'); }); + + it('should register and retrieve trigger providers', () => { + const triggerPlugin: DatapromptPlugin = { + name: 'trigger-plugin', + createTrigger: () => ({ + name: 'custom-trigger', + createTrigger: () => ({ create: () => ({}) as any }) + }) + }; + + const mockConfig: DatapromptConfig = { + rootDir: '/mock', + promptsDir: '/mock/prompts', + schemaFile: '/mock/schema.ts', + secrets: {}, + plugins: [triggerPlugin] + }; + + const manager = new PluginManager(mockConfig); + const trigger = manager.getTrigger('custom-trigger'); + + expect(trigger).toBeDefined(); + expect(trigger.name).toBe('custom-trigger'); + }); });