Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion PREVENT_CONFLICTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
45 changes: 42 additions & 3 deletions tests/config.manager.test.ts → tests/unit/config.manager.test.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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 () => {
Expand Down
24 changes: 24 additions & 0 deletions tests/unit/plugin.manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});