Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
275
npm/packages/agentic-synth/tests/unit/config/config.test.js
Normal file
275
npm/packages/agentic-synth/tests/unit/config/config.test.js
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Unit tests for Config
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { Config } from '../../../src/config/config.js';
|
||||
import { writeFileSync, unlinkSync, existsSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { tmpdir } from 'os';
|
||||
|
||||
describe('Config', () => {
|
||||
let testConfigPath;
|
||||
let originalEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = { ...process.env };
|
||||
testConfigPath = join(tmpdir(), `test-config-${Date.now()}.json`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
if (existsSync(testConfigPath)) {
|
||||
unlinkSync(testConfigPath);
|
||||
}
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create config with defaults', () => {
|
||||
const config = new Config({ loadEnv: false });
|
||||
expect(config.values).toBeDefined();
|
||||
expect(config.get('api.baseUrl')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should accept custom options', () => {
|
||||
const config = new Config({
|
||||
loadEnv: false,
|
||||
api: { baseUrl: 'https://custom.com' }
|
||||
});
|
||||
expect(config.get('api.baseUrl')).toBe('https://custom.com');
|
||||
});
|
||||
|
||||
it('should load from file if provided', () => {
|
||||
writeFileSync(testConfigPath, JSON.stringify({
|
||||
custom: { value: 'test' }
|
||||
}));
|
||||
|
||||
const config = new Config({
|
||||
loadEnv: false,
|
||||
configPath: testConfigPath
|
||||
});
|
||||
|
||||
expect(config.get('custom.value')).toBe('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config({
|
||||
loadEnv: false,
|
||||
api: {
|
||||
baseUrl: 'https://test.com',
|
||||
timeout: 5000
|
||||
},
|
||||
nested: {
|
||||
deep: {
|
||||
value: 'found'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should get top-level value', () => {
|
||||
expect(config.get('api')).toEqual({
|
||||
baseUrl: 'https://test.com',
|
||||
timeout: 5000
|
||||
});
|
||||
});
|
||||
|
||||
it('should get nested value with dot notation', () => {
|
||||
expect(config.get('api.baseUrl')).toBe('https://test.com');
|
||||
expect(config.get('nested.deep.value')).toBe('found');
|
||||
});
|
||||
|
||||
it('should return default for non-existent key', () => {
|
||||
expect(config.get('nonexistent', 'default')).toBe('default');
|
||||
});
|
||||
|
||||
it('should return undefined for non-existent key without default', () => {
|
||||
expect(config.get('nonexistent')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should read from environment variables', () => {
|
||||
process.env.AGENTIC_SYNTH_API_KEY = 'env-key-123';
|
||||
const config = new Config({ loadEnv: false });
|
||||
|
||||
expect(config.get('api.key')).toBe('env-key-123');
|
||||
});
|
||||
|
||||
it('should prioritize environment over config file', () => {
|
||||
process.env.AGENTIC_SYNTH_CUSTOM_VALUE = 'from-env';
|
||||
|
||||
const config = new Config({
|
||||
loadEnv: false,
|
||||
custom: { value: 'from-config' }
|
||||
});
|
||||
|
||||
expect(config.get('custom.value')).toBe('from-env');
|
||||
});
|
||||
});
|
||||
|
||||
describe('set', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config({ loadEnv: false });
|
||||
});
|
||||
|
||||
it('should set top-level value', () => {
|
||||
config.set('newKey', 'newValue');
|
||||
expect(config.get('newKey')).toBe('newValue');
|
||||
});
|
||||
|
||||
it('should set nested value with dot notation', () => {
|
||||
config.set('nested.deep.value', 'test');
|
||||
expect(config.get('nested.deep.value')).toBe('test');
|
||||
});
|
||||
|
||||
it('should create nested structure if not exists', () => {
|
||||
config.set('a.b.c.d', 'deep');
|
||||
expect(config.get('a.b.c.d')).toBe('deep');
|
||||
});
|
||||
|
||||
it('should update existing value', () => {
|
||||
config.set('api.baseUrl', 'https://new.com');
|
||||
expect(config.get('api.baseUrl')).toBe('https://new.com');
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadFromFile', () => {
|
||||
it('should load JSON config', () => {
|
||||
const configData = {
|
||||
api: { baseUrl: 'https://json.com' }
|
||||
};
|
||||
writeFileSync(testConfigPath, JSON.stringify(configData));
|
||||
|
||||
const config = new Config({ loadEnv: false });
|
||||
config.loadFromFile(testConfigPath);
|
||||
|
||||
expect(config.get('api.baseUrl')).toBe('https://json.com');
|
||||
});
|
||||
|
||||
it('should load YAML config', () => {
|
||||
const yamlPath = testConfigPath.replace('.json', '.yaml');
|
||||
writeFileSync(yamlPath, 'api:\n baseUrl: https://yaml.com');
|
||||
|
||||
const config = new Config({ loadEnv: false });
|
||||
config.loadFromFile(yamlPath);
|
||||
|
||||
expect(config.get('api.baseUrl')).toBe('https://yaml.com');
|
||||
|
||||
unlinkSync(yamlPath);
|
||||
});
|
||||
|
||||
it('should throw error for invalid JSON', () => {
|
||||
writeFileSync(testConfigPath, 'invalid json');
|
||||
|
||||
const config = new Config({ loadEnv: false });
|
||||
expect(() => config.loadFromFile(testConfigPath)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for unsupported format', () => {
|
||||
const txtPath = testConfigPath.replace('.json', '.txt');
|
||||
writeFileSync(txtPath, 'text');
|
||||
|
||||
const config = new Config({ loadEnv: false });
|
||||
expect(() => config.loadFromFile(txtPath)).toThrow('Unsupported config file format');
|
||||
|
||||
unlinkSync(txtPath);
|
||||
});
|
||||
|
||||
it('should throw error for non-existent file', () => {
|
||||
const config = new Config({ loadEnv: false });
|
||||
expect(() => config.loadFromFile('/nonexistent/file.json')).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config({
|
||||
loadEnv: false,
|
||||
api: { baseUrl: 'https://test.com' },
|
||||
cache: { maxSize: 100 }
|
||||
});
|
||||
});
|
||||
|
||||
it('should pass validation for existing keys', () => {
|
||||
expect(() => config.validate(['api.baseUrl', 'cache.maxSize'])).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error for missing required keys', () => {
|
||||
expect(() => config.validate(['nonexistent'])).toThrow('Missing required configuration: nonexistent');
|
||||
});
|
||||
|
||||
it('should list all missing keys', () => {
|
||||
expect(() => config.validate(['missing1', 'missing2'])).toThrow('missing1, missing2');
|
||||
});
|
||||
|
||||
it('should return true on successful validation', () => {
|
||||
expect(config.validate(['api.baseUrl'])).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should return all configuration', () => {
|
||||
const config = new Config({
|
||||
loadEnv: false,
|
||||
custom: { value: 'test' }
|
||||
});
|
||||
|
||||
const all = config.getAll();
|
||||
expect(all).toHaveProperty('custom');
|
||||
expect(all.custom.value).toBe('test');
|
||||
});
|
||||
|
||||
it('should return copy not reference', () => {
|
||||
const config = new Config({ loadEnv: false });
|
||||
const all = config.getAll();
|
||||
|
||||
all.modified = true;
|
||||
expect(config.get('modified')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('_parseValue', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = new Config({ loadEnv: false });
|
||||
});
|
||||
|
||||
it('should parse JSON strings', () => {
|
||||
expect(config._parseValue('{"key":"value"}')).toEqual({ key: 'value' });
|
||||
expect(config._parseValue('[1,2,3]')).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('should parse booleans', () => {
|
||||
expect(config._parseValue('true')).toBe(true);
|
||||
expect(config._parseValue('false')).toBe(false);
|
||||
});
|
||||
|
||||
it('should parse numbers', () => {
|
||||
expect(config._parseValue('123')).toBe(123);
|
||||
expect(config._parseValue('45.67')).toBe(45.67);
|
||||
});
|
||||
|
||||
it('should return string for unparseable values', () => {
|
||||
expect(config._parseValue('plain text')).toBe('plain text');
|
||||
});
|
||||
});
|
||||
|
||||
describe('default configuration', () => {
|
||||
it('should have sensible defaults', () => {
|
||||
const config = new Config({ loadEnv: false });
|
||||
|
||||
expect(config.get('api.timeout')).toBe(5000);
|
||||
expect(config.get('cache.maxSize')).toBe(100);
|
||||
expect(config.get('cache.ttl')).toBe(3600000);
|
||||
expect(config.get('router.strategy')).toBe('round-robin');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user