Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
184
vendor/ruvector/npm/packages/agentic-synth/tests/unit/api/client.test.js
vendored
Normal file
184
vendor/ruvector/npm/packages/agentic-synth/tests/unit/api/client.test.js
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Unit tests for APIClient
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { APIClient } from '../../../src/api/client.js';
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = vi.fn();
|
||||
|
||||
describe('APIClient', () => {
|
||||
let client;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new APIClient({
|
||||
baseUrl: 'https://api.test.com',
|
||||
apiKey: 'test-key-123',
|
||||
timeout: 5000,
|
||||
retries: 3
|
||||
});
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create client with default options', () => {
|
||||
const defaultClient = new APIClient();
|
||||
expect(defaultClient.baseUrl).toBe('https://api.example.com');
|
||||
expect(defaultClient.timeout).toBe(5000);
|
||||
expect(defaultClient.retries).toBe(3);
|
||||
});
|
||||
|
||||
it('should accept custom options', () => {
|
||||
expect(client.baseUrl).toBe('https://api.test.com');
|
||||
expect(client.apiKey).toBe('test-key-123');
|
||||
expect(client.timeout).toBe(5000);
|
||||
expect(client.retries).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('request', () => {
|
||||
it('should make successful request', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => mockResponse
|
||||
});
|
||||
|
||||
const result = await client.request('/test');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(1);
|
||||
expect(result).toEqual(mockResponse);
|
||||
});
|
||||
|
||||
it('should include authorization header', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({})
|
||||
});
|
||||
|
||||
await client.request('/test');
|
||||
|
||||
const callArgs = global.fetch.mock.calls[0];
|
||||
expect(callArgs[1].headers.Authorization).toBe('Bearer test-key-123');
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
// Mock must return error for all retry attempts
|
||||
global.fetch.mockResolvedValue({
|
||||
ok: false,
|
||||
status: 404,
|
||||
statusText: 'Not Found'
|
||||
});
|
||||
|
||||
await expect(client.request('/test')).rejects.toThrow('API error: 404 Not Found');
|
||||
});
|
||||
|
||||
it('should retry on failure', async () => {
|
||||
global.fetch
|
||||
.mockRejectedValueOnce(new Error('Network error'))
|
||||
.mockRejectedValueOnce(new Error('Network error'))
|
||||
.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ success: true })
|
||||
});
|
||||
|
||||
const result = await client.request('/test');
|
||||
|
||||
expect(global.fetch).toHaveBeenCalledTimes(3);
|
||||
expect(result).toEqual({ success: true });
|
||||
});
|
||||
|
||||
it('should fail after max retries', async () => {
|
||||
global.fetch.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
await expect(client.request('/test')).rejects.toThrow('Network error');
|
||||
expect(global.fetch).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should respect timeout', async () => {
|
||||
const shortTimeoutClient = new APIClient({ timeout: 100 });
|
||||
|
||||
global.fetch.mockImplementationOnce(() =>
|
||||
new Promise(resolve => setTimeout(resolve, 200))
|
||||
);
|
||||
|
||||
// Note: This test depends on AbortController implementation
|
||||
// May need adjustment based on test environment
|
||||
});
|
||||
});
|
||||
|
||||
describe('get', () => {
|
||||
it('should make GET request', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ result: 'success' })
|
||||
});
|
||||
|
||||
const result = await client.get('/users');
|
||||
|
||||
expect(result).toEqual({ result: 'success' });
|
||||
expect(global.fetch.mock.calls[0][1].method).toBe('GET');
|
||||
});
|
||||
|
||||
it('should append query parameters', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({})
|
||||
});
|
||||
|
||||
await client.get('/users', { page: 1, limit: 10 });
|
||||
|
||||
const url = global.fetch.mock.calls[0][0];
|
||||
expect(url).toContain('?page=1&limit=10');
|
||||
});
|
||||
});
|
||||
|
||||
describe('post', () => {
|
||||
it('should make POST request', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({ id: 123 })
|
||||
});
|
||||
|
||||
const data = { name: 'Test User' };
|
||||
const result = await client.post('/users', data);
|
||||
|
||||
expect(result).toEqual({ id: 123 });
|
||||
|
||||
const callArgs = global.fetch.mock.calls[0];
|
||||
expect(callArgs[1].method).toBe('POST');
|
||||
expect(callArgs[1].body).toBe(JSON.stringify(data));
|
||||
});
|
||||
|
||||
it('should include content-type header', async () => {
|
||||
global.fetch.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
json: async () => ({})
|
||||
});
|
||||
|
||||
await client.post('/test', {});
|
||||
|
||||
const headers = global.fetch.mock.calls[0][1].headers;
|
||||
expect(headers['Content-Type']).toBe('application/json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle network errors', async () => {
|
||||
global.fetch.mockRejectedValue(new Error('Failed to fetch'));
|
||||
|
||||
await expect(client.get('/test')).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should handle timeout errors', async () => {
|
||||
global.fetch.mockImplementationOnce(() =>
|
||||
new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Timeout')), 100);
|
||||
})
|
||||
);
|
||||
|
||||
await expect(client.request('/test')).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
256
vendor/ruvector/npm/packages/agentic-synth/tests/unit/cache/context-cache.test.js
vendored
Normal file
256
vendor/ruvector/npm/packages/agentic-synth/tests/unit/cache/context-cache.test.js
vendored
Normal file
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* Unit tests for ContextCache
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { ContextCache } from '../../../src/cache/context-cache.js';
|
||||
|
||||
describe('ContextCache', () => {
|
||||
let cache;
|
||||
|
||||
beforeEach(() => {
|
||||
cache = new ContextCache({
|
||||
maxSize: 5,
|
||||
ttl: 1000 // 1 second for testing
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create cache with default options', () => {
|
||||
const defaultCache = new ContextCache();
|
||||
expect(defaultCache.maxSize).toBe(100);
|
||||
expect(defaultCache.ttl).toBe(3600000);
|
||||
});
|
||||
|
||||
it('should accept custom options', () => {
|
||||
expect(cache.maxSize).toBe(5);
|
||||
expect(cache.ttl).toBe(1000);
|
||||
});
|
||||
|
||||
it('should initialize empty cache', () => {
|
||||
expect(cache.cache.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should initialize stats', () => {
|
||||
const stats = cache.getStats();
|
||||
expect(stats.hits).toBe(0);
|
||||
expect(stats.misses).toBe(0);
|
||||
expect(stats.evictions).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('set and get', () => {
|
||||
it('should store and retrieve value', () => {
|
||||
cache.set('key1', 'value1');
|
||||
const result = cache.get('key1');
|
||||
expect(result).toBe('value1');
|
||||
});
|
||||
|
||||
it('should return null for non-existent key', () => {
|
||||
const result = cache.get('nonexistent');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should update existing key', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.set('key1', 'value2');
|
||||
expect(cache.get('key1')).toBe('value2');
|
||||
expect(cache.cache.size).toBe(1);
|
||||
});
|
||||
|
||||
it('should store complex objects', () => {
|
||||
const obj = { nested: { data: [1, 2, 3] } };
|
||||
cache.set('complex', obj);
|
||||
expect(cache.get('complex')).toEqual(obj);
|
||||
});
|
||||
});
|
||||
|
||||
describe('TTL (Time To Live)', () => {
|
||||
it('should return null for expired entries', async () => {
|
||||
cache.set('key1', 'value1');
|
||||
|
||||
// Wait for TTL to expire
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
|
||||
const result = cache.get('key1');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should not return expired entries in has()', async () => {
|
||||
cache.set('key1', 'value1');
|
||||
expect(cache.has('key1')).toBe(true);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
|
||||
expect(cache.has('key1')).toBe(false);
|
||||
});
|
||||
|
||||
it('should delete expired entries', async () => {
|
||||
cache.set('key1', 'value1');
|
||||
expect(cache.cache.size).toBe(1);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1100));
|
||||
cache.get('key1'); // Triggers cleanup
|
||||
|
||||
expect(cache.cache.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('eviction', () => {
|
||||
it('should evict LRU entry when at capacity', () => {
|
||||
// Fill cache to capacity
|
||||
for (let i = 0; i < 5; i++) {
|
||||
cache.set(`key${i}`, `value${i}`);
|
||||
}
|
||||
|
||||
expect(cache.cache.size).toBe(5);
|
||||
|
||||
// Access key1 to make key0 the LRU
|
||||
cache.get('key1');
|
||||
|
||||
// Add new entry, should evict key0
|
||||
cache.set('key5', 'value5');
|
||||
|
||||
expect(cache.cache.size).toBe(5);
|
||||
expect(cache.get('key0')).toBeNull();
|
||||
expect(cache.get('key5')).toBe('value5');
|
||||
});
|
||||
|
||||
it('should track eviction stats', () => {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
cache.set(`key${i}`, `value${i}`);
|
||||
}
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.evictions).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('has', () => {
|
||||
it('should return true for existing key', () => {
|
||||
cache.set('key1', 'value1');
|
||||
expect(cache.has('key1')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-existent key', () => {
|
||||
expect(cache.has('nonexistent')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('clear', () => {
|
||||
it('should remove all entries', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.set('key2', 'value2');
|
||||
|
||||
cache.clear();
|
||||
|
||||
expect(cache.cache.size).toBe(0);
|
||||
expect(cache.get('key1')).toBeNull();
|
||||
});
|
||||
|
||||
it('should reset statistics', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.get('key1');
|
||||
cache.get('nonexistent');
|
||||
|
||||
cache.clear();
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.hits).toBe(0);
|
||||
expect(stats.misses).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats', () => {
|
||||
it('should track cache hits', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.get('key1');
|
||||
cache.get('key1');
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.hits).toBe(2);
|
||||
});
|
||||
|
||||
it('should track cache misses', () => {
|
||||
cache.get('nonexistent1');
|
||||
cache.get('nonexistent2');
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.misses).toBe(2);
|
||||
});
|
||||
|
||||
it('should calculate hit rate', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.get('key1'); // hit
|
||||
cache.get('key1'); // hit
|
||||
cache.get('nonexistent'); // miss
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.hitRate).toBeCloseTo(0.666, 2);
|
||||
});
|
||||
|
||||
it('should include cache size', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.set('key2', 'value2');
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.size).toBe(2);
|
||||
});
|
||||
|
||||
it('should handle zero hit rate', () => {
|
||||
cache.get('nonexistent');
|
||||
|
||||
const stats = cache.getStats();
|
||||
expect(stats.hitRate).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('access tracking', () => {
|
||||
it('should update access count', () => {
|
||||
cache.set('key1', 'value1');
|
||||
cache.get('key1');
|
||||
cache.get('key1');
|
||||
|
||||
const entry = cache.cache.get('key1');
|
||||
expect(entry.accessCount).toBe(2);
|
||||
});
|
||||
|
||||
it('should update last access time', () => {
|
||||
cache.set('key1', 'value1');
|
||||
const initialAccess = cache.cache.get('key1').lastAccess;
|
||||
|
||||
// Small delay
|
||||
setTimeout(() => {
|
||||
cache.get('key1');
|
||||
const laterAccess = cache.cache.get('key1').lastAccess;
|
||||
expect(laterAccess).toBeGreaterThan(initialAccess);
|
||||
}, 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance', () => {
|
||||
it('should handle 1000 operations quickly', () => {
|
||||
const start = Date.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
cache.set(`key${i}`, `value${i}`);
|
||||
cache.get(`key${i}`);
|
||||
}
|
||||
|
||||
const duration = Date.now() - start;
|
||||
expect(duration).toBeLessThan(100); // Less than 100ms
|
||||
});
|
||||
|
||||
it('should maintain performance with large values', () => {
|
||||
const largeValue = { data: new Array(1000).fill('x'.repeat(100)) };
|
||||
|
||||
const start = Date.now();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
cache.set(`key${i}`, largeValue);
|
||||
}
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(duration).toBeLessThan(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
275
vendor/ruvector/npm/packages/agentic-synth/tests/unit/config/config.test.js
vendored
Normal file
275
vendor/ruvector/npm/packages/agentic-synth/tests/unit/config/config.test.js
vendored
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');
|
||||
});
|
||||
});
|
||||
});
|
||||
161
vendor/ruvector/npm/packages/agentic-synth/tests/unit/generators/data-generator.test.js
vendored
Normal file
161
vendor/ruvector/npm/packages/agentic-synth/tests/unit/generators/data-generator.test.js
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* Unit tests for DataGenerator
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { DataGenerator } from '../../../src/generators/data-generator.js';
|
||||
|
||||
describe('DataGenerator', () => {
|
||||
let generator;
|
||||
|
||||
beforeEach(() => {
|
||||
generator = new DataGenerator({
|
||||
seed: 12345,
|
||||
schema: {
|
||||
name: { type: 'string', length: 10 },
|
||||
age: { type: 'number', min: 18, max: 65 },
|
||||
active: { type: 'boolean' },
|
||||
tags: { type: 'array', items: 5 },
|
||||
embedding: { type: 'vector', dimensions: 128 }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create generator with default options', () => {
|
||||
const gen = new DataGenerator();
|
||||
expect(gen).toBeDefined();
|
||||
expect(gen.format).toBe('json');
|
||||
});
|
||||
|
||||
it('should accept custom options', () => {
|
||||
const gen = new DataGenerator({
|
||||
seed: 99999,
|
||||
format: 'csv',
|
||||
schema: { test: { type: 'string' } }
|
||||
});
|
||||
expect(gen.seed).toBe(99999);
|
||||
expect(gen.format).toBe('csv');
|
||||
expect(gen.schema).toHaveProperty('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generate', () => {
|
||||
it('should generate specified number of records', () => {
|
||||
const data = generator.generate(5);
|
||||
expect(data).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should generate single record by default', () => {
|
||||
const data = generator.generate();
|
||||
expect(data).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should throw error for invalid count', () => {
|
||||
expect(() => generator.generate(0)).toThrow('Count must be at least 1');
|
||||
expect(() => generator.generate(-5)).toThrow('Count must be at least 1');
|
||||
});
|
||||
|
||||
it('should generate records with correct schema fields', () => {
|
||||
const data = generator.generate(1);
|
||||
const record = data[0];
|
||||
|
||||
expect(record).toHaveProperty('id');
|
||||
expect(record).toHaveProperty('name');
|
||||
expect(record).toHaveProperty('age');
|
||||
expect(record).toHaveProperty('active');
|
||||
expect(record).toHaveProperty('tags');
|
||||
expect(record).toHaveProperty('embedding');
|
||||
});
|
||||
|
||||
it('should generate unique IDs', () => {
|
||||
const data = generator.generate(10);
|
||||
const ids = data.map(r => r.id);
|
||||
const uniqueIds = new Set(ids);
|
||||
expect(uniqueIds.size).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('field generation', () => {
|
||||
it('should generate strings of correct length', () => {
|
||||
const data = generator.generate(1);
|
||||
expect(data[0].name).toHaveLength(10);
|
||||
expect(typeof data[0].name).toBe('string');
|
||||
});
|
||||
|
||||
it('should generate numbers within range', () => {
|
||||
const data = generator.generate(100);
|
||||
data.forEach(record => {
|
||||
expect(record.age).toBeGreaterThanOrEqual(18);
|
||||
expect(record.age).toBeLessThanOrEqual(65);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate boolean values', () => {
|
||||
const data = generator.generate(1);
|
||||
expect(typeof data[0].active).toBe('boolean');
|
||||
});
|
||||
|
||||
it('should generate arrays of correct length', () => {
|
||||
const data = generator.generate(1);
|
||||
expect(Array.isArray(data[0].tags)).toBe(true);
|
||||
expect(data[0].tags).toHaveLength(5);
|
||||
});
|
||||
|
||||
it('should generate vectors with correct dimensions', () => {
|
||||
const data = generator.generate(1);
|
||||
expect(Array.isArray(data[0].embedding)).toBe(true);
|
||||
expect(data[0].embedding).toHaveLength(128);
|
||||
|
||||
// Check all values are numbers between 0 and 1
|
||||
data[0].embedding.forEach(val => {
|
||||
expect(typeof val).toBe('number');
|
||||
expect(val).toBeGreaterThanOrEqual(0);
|
||||
expect(val).toBeLessThanOrEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setSeed', () => {
|
||||
it('should allow updating seed', () => {
|
||||
generator.setSeed(54321);
|
||||
expect(generator.seed).toBe(54321);
|
||||
});
|
||||
|
||||
it('should produce same results with same seed', () => {
|
||||
const gen1 = new DataGenerator({ seed: 12345, schema: { val: { type: 'number' } } });
|
||||
const gen2 = new DataGenerator({ seed: 12345, schema: { val: { type: 'number' } } });
|
||||
|
||||
// Note: This test may be flaky due to random number generation
|
||||
// In real implementation, you'd want seeded random number generation
|
||||
expect(gen1.seed).toBe(gen2.seed);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance', () => {
|
||||
it('should generate 1000 records quickly', () => {
|
||||
const start = Date.now();
|
||||
const data = generator.generate(1000);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(data).toHaveLength(1000);
|
||||
expect(duration).toBeLessThan(1000); // Less than 1 second
|
||||
});
|
||||
|
||||
it('should handle large vector dimensions efficiently', () => {
|
||||
const largeVectorGen = new DataGenerator({
|
||||
schema: {
|
||||
embedding: { type: 'vector', dimensions: 4096 }
|
||||
}
|
||||
});
|
||||
|
||||
const start = Date.now();
|
||||
const data = largeVectorGen.generate(100);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(data).toHaveLength(100);
|
||||
expect(data[0].embedding).toHaveLength(4096);
|
||||
expect(duration).toBeLessThan(2000); // Less than 2 seconds
|
||||
});
|
||||
});
|
||||
});
|
||||
257
vendor/ruvector/npm/packages/agentic-synth/tests/unit/routing/model-router.test.js
vendored
Normal file
257
vendor/ruvector/npm/packages/agentic-synth/tests/unit/routing/model-router.test.js
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Unit tests for ModelRouter
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { ModelRouter } from '../../../src/routing/model-router.js';
|
||||
|
||||
describe('ModelRouter', () => {
|
||||
let router;
|
||||
let models;
|
||||
|
||||
beforeEach(() => {
|
||||
models = [
|
||||
{ id: 'model-1', endpoint: 'http://api1.com', capabilities: ['general', 'code'] },
|
||||
{ id: 'model-2', endpoint: 'http://api2.com', capabilities: ['general'] },
|
||||
{ id: 'model-3', endpoint: 'http://api3.com', capabilities: ['math', 'reasoning'] }
|
||||
];
|
||||
|
||||
router = new ModelRouter({
|
||||
models,
|
||||
strategy: 'round-robin'
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
it('should create router with default options', () => {
|
||||
const defaultRouter = new ModelRouter();
|
||||
expect(defaultRouter.models).toEqual([]);
|
||||
expect(defaultRouter.strategy).toBe('round-robin');
|
||||
});
|
||||
|
||||
it('should accept custom options', () => {
|
||||
expect(router.models).toEqual(models);
|
||||
expect(router.strategy).toBe('round-robin');
|
||||
});
|
||||
|
||||
it('should initialize model stats', () => {
|
||||
models.forEach(model => {
|
||||
const stats = router.getStats(model.id);
|
||||
expect(stats).toBeDefined();
|
||||
expect(stats.requests).toBe(0);
|
||||
expect(stats.errors).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerModel', () => {
|
||||
it('should register new model', () => {
|
||||
const newModel = { id: 'model-4', endpoint: 'http://api4.com' };
|
||||
router.registerModel(newModel);
|
||||
|
||||
expect(router.models).toContain(newModel);
|
||||
expect(router.getStats('model-4')).toBeDefined();
|
||||
});
|
||||
|
||||
it('should throw error for invalid model', () => {
|
||||
expect(() => router.registerModel({})).toThrow('Model must have id and endpoint');
|
||||
expect(() => router.registerModel({ id: 'test' })).toThrow('Model must have id and endpoint');
|
||||
});
|
||||
|
||||
it('should initialize stats for new model', () => {
|
||||
const newModel = { id: 'model-4', endpoint: 'http://api4.com' };
|
||||
router.registerModel(newModel);
|
||||
|
||||
const stats = router.getStats('model-4');
|
||||
expect(stats.requests).toBe(0);
|
||||
expect(stats.errors).toBe(0);
|
||||
expect(stats.avgLatency).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('route - round-robin', () => {
|
||||
it('should distribute requests evenly', () => {
|
||||
const results = [];
|
||||
for (let i = 0; i < 6; i++) {
|
||||
results.push(router.route({}));
|
||||
}
|
||||
|
||||
expect(results[0]).toBe('model-1');
|
||||
expect(results[1]).toBe('model-2');
|
||||
expect(results[2]).toBe('model-3');
|
||||
expect(results[3]).toBe('model-1');
|
||||
expect(results[4]).toBe('model-2');
|
||||
expect(results[5]).toBe('model-3');
|
||||
});
|
||||
|
||||
it('should wrap around after reaching end', () => {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
router.route({});
|
||||
}
|
||||
|
||||
expect(router.route({})).toBe('model-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('route - least-latency', () => {
|
||||
beforeEach(() => {
|
||||
router.strategy = 'least-latency';
|
||||
|
||||
// Record some metrics
|
||||
router.recordMetrics('model-1', 100);
|
||||
router.recordMetrics('model-2', 50);
|
||||
router.recordMetrics('model-3', 150);
|
||||
});
|
||||
|
||||
it('should route to model with lowest latency', () => {
|
||||
const modelId = router.route({});
|
||||
expect(modelId).toBe('model-2');
|
||||
});
|
||||
|
||||
it('should update as latencies change', () => {
|
||||
router.recordMetrics('model-1', 20);
|
||||
router.recordMetrics('model-1', 20);
|
||||
|
||||
const modelId = router.route({});
|
||||
expect(modelId).toBe('model-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('route - cost-optimized', () => {
|
||||
beforeEach(() => {
|
||||
router.strategy = 'cost-optimized';
|
||||
});
|
||||
|
||||
it('should route small requests to first model', () => {
|
||||
const smallRequest = { data: 'test' };
|
||||
const modelId = router.route(smallRequest);
|
||||
expect(modelId).toBe('model-1');
|
||||
});
|
||||
|
||||
it('should route large requests to last model', () => {
|
||||
const largeRequest = { data: 'x'.repeat(2000) };
|
||||
const modelId = router.route(largeRequest);
|
||||
expect(modelId).toBe('model-3');
|
||||
});
|
||||
});
|
||||
|
||||
describe('route - capability-based', () => {
|
||||
beforeEach(() => {
|
||||
router.strategy = 'capability-based';
|
||||
});
|
||||
|
||||
it('should route to model with required capability', () => {
|
||||
const request = { capability: 'code' };
|
||||
const modelId = router.route(request);
|
||||
expect(modelId).toBe('model-1');
|
||||
});
|
||||
|
||||
it('should route math requests to capable model', () => {
|
||||
const request = { capability: 'math' };
|
||||
const modelId = router.route(request);
|
||||
expect(modelId).toBe('model-3');
|
||||
});
|
||||
|
||||
it('should fallback to first model if no match', () => {
|
||||
const request = { capability: 'unsupported' };
|
||||
const modelId = router.route(request);
|
||||
expect(modelId).toBe('model-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('route - error handling', () => {
|
||||
it('should throw error when no models available', () => {
|
||||
const emptyRouter = new ModelRouter();
|
||||
expect(() => emptyRouter.route({})).toThrow('No models available for routing');
|
||||
});
|
||||
});
|
||||
|
||||
describe('recordMetrics', () => {
|
||||
it('should record successful requests', () => {
|
||||
router.recordMetrics('model-1', 100, true);
|
||||
|
||||
const stats = router.getStats('model-1');
|
||||
expect(stats.requests).toBe(1);
|
||||
expect(stats.errors).toBe(0);
|
||||
expect(stats.avgLatency).toBe(100);
|
||||
});
|
||||
|
||||
it('should record failed requests', () => {
|
||||
router.recordMetrics('model-1', 100, false);
|
||||
|
||||
const stats = router.getStats('model-1');
|
||||
expect(stats.requests).toBe(1);
|
||||
expect(stats.errors).toBe(1);
|
||||
});
|
||||
|
||||
it('should calculate average latency', () => {
|
||||
router.recordMetrics('model-1', 100);
|
||||
router.recordMetrics('model-1', 200);
|
||||
router.recordMetrics('model-1', 300);
|
||||
|
||||
const stats = router.getStats('model-1');
|
||||
expect(stats.avgLatency).toBe(200);
|
||||
});
|
||||
|
||||
it('should handle non-existent model gracefully', () => {
|
||||
router.recordMetrics('nonexistent', 100);
|
||||
expect(router.getStats('nonexistent')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getStats', () => {
|
||||
it('should return stats for specific model', () => {
|
||||
router.recordMetrics('model-1', 100);
|
||||
|
||||
const stats = router.getStats('model-1');
|
||||
expect(stats).toHaveProperty('requests');
|
||||
expect(stats).toHaveProperty('errors');
|
||||
expect(stats).toHaveProperty('avgLatency');
|
||||
});
|
||||
|
||||
it('should return all stats when no model specified', () => {
|
||||
const allStats = router.getStats();
|
||||
expect(allStats).toHaveProperty('model-1');
|
||||
expect(allStats).toHaveProperty('model-2');
|
||||
expect(allStats).toHaveProperty('model-3');
|
||||
});
|
||||
|
||||
it('should track multiple models independently', () => {
|
||||
router.recordMetrics('model-1', 100);
|
||||
router.recordMetrics('model-2', 200);
|
||||
|
||||
expect(router.getStats('model-1').avgLatency).toBe(100);
|
||||
expect(router.getStats('model-2').avgLatency).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('performance', () => {
|
||||
it('should handle 1000 routing decisions quickly', () => {
|
||||
const start = Date.now();
|
||||
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
router.route({});
|
||||
}
|
||||
|
||||
const duration = Date.now() - start;
|
||||
expect(duration).toBeLessThan(100); // Less than 100ms
|
||||
});
|
||||
|
||||
it('should efficiently handle many models', () => {
|
||||
const manyModels = Array.from({ length: 100 }, (_, i) => ({
|
||||
id: `model-${i}`,
|
||||
endpoint: `http://api${i}.com`
|
||||
}));
|
||||
|
||||
const largeRouter = new ModelRouter({ models: manyModels });
|
||||
|
||||
const start = Date.now();
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
largeRouter.route({});
|
||||
}
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(duration).toBeLessThan(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user