import { describe, expect, it } from 'bun:test'; import { z } from 'zod'; import { baseConfigSchema, baseProviderConfigSchema, browserConfigSchema, databaseConfigSchema, dragonflyConfigSchema, environmentSchema, eodProviderConfigSchema, httpConfigSchema, ibProviderConfigSchema, loggingConfigSchema, mongodbConfigSchema, postgresConfigSchema, providerConfigSchema, proxyConfigSchema, qmProviderConfigSchema, questdbConfigSchema, queueConfigSchema, serviceConfigSchema, webshareConfigSchema, webshareProviderConfigSchema, yahooProviderConfigSchema, } from '../src/schemas'; describe('Config Schemas', () => { describe('environmentSchema', () => { it('should accept valid environments', () => { expect(environmentSchema.parse('development')).toBe('development'); expect(environmentSchema.parse('test')).toBe('test'); expect(environmentSchema.parse('production')).toBe('production'); }); it('should reject invalid environments', () => { expect(() => environmentSchema.parse('staging')).toThrow(); expect(() => environmentSchema.parse('dev')).toThrow(); expect(() => environmentSchema.parse('')).toThrow(); }); }); describe('baseConfigSchema', () => { it('should accept minimal valid config', () => { const config = baseConfigSchema.parse({}); expect(config).toEqual({ debug: false, }); }); it('should accept full valid config', () => { const input = { environment: 'production', name: 'test-app', version: '1.0.0', debug: true, }; const config = baseConfigSchema.parse(input); expect(config).toEqual(input); }); it('should apply default values', () => { const config = baseConfigSchema.parse({ name: 'app' }); expect(config.debug).toBe(false); }); it('should reject invalid environment in base config', () => { expect(() => baseConfigSchema.parse({ environment: 'invalid' })).toThrow(); }); }); describe('serviceConfigSchema', () => { it('should require name and port', () => { expect(() => serviceConfigSchema.parse({})).toThrow(); expect(() => serviceConfigSchema.parse({ name: 'test' })).toThrow(); expect(() => serviceConfigSchema.parse({ port: 3000 })).toThrow(); }); it('should accept minimal valid config', () => { const config = serviceConfigSchema.parse({ name: 'test-service', port: 3000, }); expect(config).toEqual({ name: 'test-service', port: 3000, host: '0.0.0.0', healthCheckPath: '/health', metricsPath: '/metrics', shutdownTimeout: 30000, cors: { enabled: true, origin: '*', credentials: true, }, }); }); it('should accept full config', () => { const input = { name: 'test-service', serviceName: 'test-service', port: 8080, host: 'localhost', healthCheckPath: '/api/health', metricsPath: '/api/metrics', shutdownTimeout: 60000, cors: { enabled: false, origin: ['http://localhost:3000', 'https://example.com'], credentials: false, }, }; const config = serviceConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate port range', () => { expect(() => serviceConfigSchema.parse({ name: 'test', port: 0 })).toThrow(); expect(() => serviceConfigSchema.parse({ name: 'test', port: 65536 })).toThrow(); expect(() => serviceConfigSchema.parse({ name: 'test', port: -1 })).toThrow(); // Valid ports expect(serviceConfigSchema.parse({ name: 'test', port: 1 }).port).toBe(1); expect(serviceConfigSchema.parse({ name: 'test', port: 65535 }).port).toBe(65535); }); it('should handle CORS origin as string or array', () => { const stringOrigin = serviceConfigSchema.parse({ name: 'test', port: 3000, cors: { origin: 'http://localhost:3000' }, }); expect(stringOrigin.cors.origin).toBe('http://localhost:3000'); const arrayOrigin = serviceConfigSchema.parse({ name: 'test', port: 3000, cors: { origin: ['http://localhost:3000', 'https://example.com'] }, }); expect(arrayOrigin.cors.origin).toEqual(['http://localhost:3000', 'https://example.com']); }); }); describe('loggingConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = loggingConfigSchema.parse({}); expect(config).toEqual({ level: 'info', format: 'json', hideObject: false, }); }); it('should accept all log levels', () => { const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal']; for (const level of levels) { const config = loggingConfigSchema.parse({ level }); expect(config.level).toBe(level); } }); it('should reject invalid log levels', () => { expect(() => loggingConfigSchema.parse({ level: 'verbose' })).toThrow(); expect(() => loggingConfigSchema.parse({ level: 'warning' })).toThrow(); }); it('should accept loki configuration', () => { const config = loggingConfigSchema.parse({ loki: { enabled: true, host: 'loki.example.com', port: 3100, labels: { app: 'test', env: 'prod' }, }, }); expect(config.loki).toEqual({ enabled: true, host: 'loki.example.com', port: 3100, labels: { app: 'test', env: 'prod' }, }); }); it('should apply loki defaults', () => { const config = loggingConfigSchema.parse({ loki: { enabled: true }, }); expect(config.loki).toEqual({ enabled: true, host: 'localhost', port: 3100, labels: {}, }); }); }); describe('queueConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = queueConfigSchema.parse({ redis: {}, // redis is required, but its properties have defaults }); expect(config).toEqual({ enabled: true, redis: { host: 'localhost', port: 6379, db: 1, }, workers: 1, concurrency: 1, enableScheduledJobs: true, defaultJobOptions: { attempts: 3, backoff: { type: 'exponential', delay: 1000, }, removeOnComplete: 100, removeOnFail: 100, }, }); }); it('should accept full config', () => { const input = { enabled: false, redis: { host: 'redis.example.com', port: 6380, password: 'secret', db: 2, }, workers: 4, concurrency: 10, enableScheduledJobs: false, defaultJobOptions: { attempts: 5, backoff: { type: 'fixed' as const, delay: 2000, }, removeOnComplete: 50, removeOnFail: 200, timeout: 60000, }, }; const config = queueConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate backoff type', () => { const exponential = queueConfigSchema.parse({ redis: {}, defaultJobOptions: { backoff: { type: 'exponential' } }, }); expect(exponential.defaultJobOptions.backoff.type).toBe('exponential'); const fixed = queueConfigSchema.parse({ redis: {}, defaultJobOptions: { backoff: { type: 'fixed' } }, }); expect(fixed.defaultJobOptions.backoff.type).toBe('fixed'); expect(() => queueConfigSchema.parse({ redis: {}, defaultJobOptions: { backoff: { type: 'linear' } }, }) ).toThrow(); }); }); describe('httpConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = httpConfigSchema.parse({}); expect(config).toEqual({ timeout: 30000, retries: 3, retryDelay: 1000, }); }); it('should accept full config', () => { const input = { timeout: 60000, retries: 5, retryDelay: 2000, userAgent: 'MyApp/1.0', proxy: { enabled: true, url: 'http://proxy.example.com:8080', auth: { username: 'user', password: 'pass', }, }, }; const config = httpConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate proxy URL', () => { expect(() => httpConfigSchema.parse({ proxy: { url: 'not-a-url' }, }) ).toThrow(); const validProxy = httpConfigSchema.parse({ proxy: { url: 'http://proxy.example.com' }, }); expect(validProxy.proxy?.url).toBe('http://proxy.example.com'); }); }); describe('webshareConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = webshareConfigSchema.parse({}); expect(config).toEqual({ apiUrl: 'https://proxy.webshare.io/api/v2/', enabled: true, }); }); it('should accept full config', () => { const input = { apiKey: 'test-api-key', apiUrl: 'https://custom.webshare.io/api/v3/', enabled: false, }; const config = webshareConfigSchema.parse(input); expect(config).toEqual(input); }); }); describe('browserConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = browserConfigSchema.parse({}); expect(config).toEqual({ headless: true, timeout: 30000, }); }); it('should accept custom values', () => { const config = browserConfigSchema.parse({ headless: false, timeout: 60000, }); expect(config).toEqual({ headless: false, timeout: 60000, }); }); }); describe('proxyConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = proxyConfigSchema.parse({}); expect(config).toEqual({ enabled: false, cachePrefix: 'proxy:', ttl: 3600, }); }); it('should accept full config', () => { const input = { enabled: true, cachePrefix: 'custom:proxy:', ttl: 7200, webshare: { apiKey: 'test-key', apiUrl: 'https://api.webshare.io/v2/', }, }; const config = proxyConfigSchema.parse(input); expect(config).toEqual(input); }); }); describe('Schema Composition', () => { it('should be able to compose schemas', () => { const appConfigSchema = z.object({ base: baseConfigSchema, service: serviceConfigSchema, logging: loggingConfigSchema, }); const config = appConfigSchema.parse({ base: { name: 'test-app', version: '1.0.0', }, service: { name: 'test-service', port: 3000, }, logging: { level: 'debug', }, }); expect(config.base.debug).toBe(false); expect(config.service.host).toBe('0.0.0.0'); expect(config.logging.format).toBe('json'); }); }); describe('Edge Cases', () => { it('should handle empty strings appropriately', () => { // Empty strings are allowed by z.string() unless .min(1) is specified const serviceConfig = serviceConfigSchema.parse({ name: '', port: 3000 }); expect(serviceConfig.name).toBe(''); const baseConfig = baseConfigSchema.parse({ name: '' }); expect(baseConfig.name).toBe(''); }); it('should handle null values', () => { expect(() => serviceConfigSchema.parse({ name: null, port: 3000 })).toThrow(); expect(() => queueConfigSchema.parse({ redis: {}, workers: null })).toThrow(); }); it('should handle undefined values for optional fields', () => { const config = serviceConfigSchema.parse({ name: 'test', port: 3000, serviceName: undefined, }); expect(config.serviceName).toBeUndefined(); }); it('should handle numeric strings for number fields', () => { expect(() => serviceConfigSchema.parse({ name: 'test', port: '3000' })).toThrow(); expect(() => queueConfigSchema.parse({ redis: {}, workers: '4' })).toThrow(); }); it('should strip unknown properties', () => { const config = baseConfigSchema.parse({ name: 'test', unknownProp: 'should be removed', }); expect('unknownProp' in config).toBe(false); }); }); describe('postgresConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = postgresConfigSchema.parse({ database: 'testdb', user: 'testuser', password: 'testpass', }); expect(config).toEqual({ enabled: true, host: 'localhost', port: 5432, database: 'testdb', user: 'testuser', password: 'testpass', ssl: false, poolSize: 10, connectionTimeout: 30000, idleTimeout: 10000, }); }); it('should accept full config', () => { const input = { enabled: false, host: 'db.example.com', port: 5433, database: 'proddb', user: 'admin', password: 'secret', ssl: true, poolSize: 20, connectionTimeout: 60000, idleTimeout: 30000, }; const config = postgresConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate poolSize range', () => { expect(() => postgresConfigSchema.parse({ database: 'testdb', user: 'testuser', password: 'testpass', poolSize: 0, }) ).toThrow(); expect(() => postgresConfigSchema.parse({ database: 'testdb', user: 'testuser', password: 'testpass', poolSize: 101, }) ).toThrow(); }); }); describe('questdbConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = questdbConfigSchema.parse({}); expect(config).toEqual({ enabled: true, host: 'localhost', ilpPort: 9009, httpPort: 9000, pgPort: 8812, database: 'questdb', bufferSize: 65536, flushInterval: 1000, }); }); it('should accept full config', () => { const input = { enabled: false, host: 'questdb.example.com', ilpPort: 9010, httpPort: 9001, pgPort: 8813, database: 'metrics', user: 'admin', password: 'secret', bufferSize: 131072, flushInterval: 2000, }; const config = questdbConfigSchema.parse(input); expect(config).toEqual(input); }); }); describe('mongodbConfigSchema', () => { it('should accept minimal config', () => { const config = mongodbConfigSchema.parse({ uri: 'mongodb://localhost:27017', database: 'testdb', }); expect(config).toEqual({ enabled: true, uri: 'mongodb://localhost:27017', database: 'testdb', poolSize: 10, }); }); it('should accept full config', () => { const input = { enabled: false, uri: 'mongodb://user:pass@cluster.mongodb.net', database: 'proddb', poolSize: 50, host: 'cluster.mongodb.net', port: 27017, user: 'admin', password: 'secret', authSource: 'admin', replicaSet: 'rs0', }; const config = mongodbConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate URI format', () => { expect(() => mongodbConfigSchema.parse({ uri: 'invalid-uri', database: 'testdb', }) ).toThrow(); }); it('should validate poolSize range', () => { expect(() => mongodbConfigSchema.parse({ uri: 'mongodb://localhost', database: 'testdb', poolSize: 0, }) ).toThrow(); expect(() => mongodbConfigSchema.parse({ uri: 'mongodb://localhost', database: 'testdb', poolSize: 101, }) ).toThrow(); }); }); describe('dragonflyConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = dragonflyConfigSchema.parse({}); expect(config).toEqual({ enabled: true, host: 'localhost', port: 6379, db: 0, maxRetries: 3, retryDelay: 100, }); }); it('should accept full config', () => { const input = { enabled: false, host: 'cache.example.com', port: 6380, password: 'secret', db: 5, keyPrefix: 'app:', ttl: 3600, maxRetries: 5, retryDelay: 200, }; const config = dragonflyConfigSchema.parse(input); expect(config).toEqual(input); }); it('should validate db range', () => { expect(() => dragonflyConfigSchema.parse({ db: -1 })).toThrow(); expect(() => dragonflyConfigSchema.parse({ db: 16 })).toThrow(); }); }); describe('databaseConfigSchema', () => { it('should accept complete database configuration', () => { const config = databaseConfigSchema.parse({ postgres: { database: 'testdb', user: 'testuser', password: 'testpass', }, questdb: {}, mongodb: { uri: 'mongodb://localhost', database: 'testdb', }, dragonfly: {}, }); expect(config.postgres.host).toBe('localhost'); expect(config.questdb.enabled).toBe(true); expect(config.mongodb.poolSize).toBe(10); expect(config.dragonfly.port).toBe(6379); }); }); describe('baseProviderConfigSchema', () => { it('should accept minimal config with defaults', () => { const config = baseProviderConfigSchema.parse({ name: 'test-provider', }); expect(config).toEqual({ name: 'test-provider', enabled: true, priority: 0, timeout: 30000, retries: 3, }); }); it('should accept full config', () => { const input = { name: 'test-provider', enabled: false, priority: 10, rateLimit: { maxRequests: 50, windowMs: 30000, }, timeout: 60000, retries: 5, }; const config = baseProviderConfigSchema.parse(input); expect(config).toEqual(input); }); }); describe('eodProviderConfigSchema', () => { it('should accept minimal config', () => { const config = eodProviderConfigSchema.parse({ name: 'eod', apiKey: 'test-key', }); expect(config).toEqual({ name: 'eod', apiKey: 'test-key', enabled: true, priority: 0, timeout: 30000, retries: 3, baseUrl: 'https://eodhistoricaldata.com/api', tier: 'free', }); }); it('should validate tier values', () => { expect(() => eodProviderConfigSchema.parse({ name: 'eod', apiKey: 'test-key', tier: 'premium', }) ).toThrow(); const validTiers = ['free', 'fundamentals', 'all-in-one']; for (const tier of validTiers) { const config = eodProviderConfigSchema.parse({ name: 'eod', apiKey: 'test-key', tier, }); expect(config.tier).toBe(tier); } }); }); describe('ibProviderConfigSchema', () => { it('should accept minimal config', () => { const config = ibProviderConfigSchema.parse({ name: 'ib', }); expect(config).toEqual({ name: 'ib', enabled: true, priority: 0, timeout: 30000, retries: 3, gateway: { host: 'localhost', port: 5000, clientId: 1, }, marketDataType: 'delayed', }); }); it('should accept full config', () => { const input = { name: 'ib', enabled: false, priority: 5, gateway: { host: 'gateway.example.com', port: 7497, clientId: 99, }, account: 'DU123456', marketDataType: 'live' as const, }; const config = ibProviderConfigSchema.parse(input); expect(config).toEqual(expect.objectContaining(input)); }); it('should validate marketDataType', () => { expect(() => ibProviderConfigSchema.parse({ name: 'ib', marketDataType: 'realtime', }) ).toThrow(); const validTypes = ['live', 'delayed', 'frozen']; for (const type of validTypes) { const config = ibProviderConfigSchema.parse({ name: 'ib', marketDataType: type, }); expect(config.marketDataType).toBe(type); } }); }); describe('qmProviderConfigSchema', () => { it('should require all credentials', () => { expect(() => qmProviderConfigSchema.parse({ name: 'qm', }) ).toThrow(); const config = qmProviderConfigSchema.parse({ name: 'qm', username: 'testuser', password: 'testpass', webmasterId: '12345', }); expect(config.baseUrl).toBe('https://app.quotemedia.com/quotetools'); }); }); describe('yahooProviderConfigSchema', () => { it('should accept minimal config', () => { const config = yahooProviderConfigSchema.parse({ name: 'yahoo', }); expect(config).toEqual({ name: 'yahoo', enabled: true, priority: 0, timeout: 30000, retries: 3, baseUrl: 'https://query1.finance.yahoo.com', cookieJar: true, }); }); it('should accept crumb parameter', () => { const config = yahooProviderConfigSchema.parse({ name: 'yahoo', crumb: 'abc123xyz', }); expect(config.crumb).toBe('abc123xyz'); }); }); describe('webshareProviderConfigSchema', () => { it('should not require name like other providers', () => { const config = webshareProviderConfigSchema.parse({}); expect(config).toEqual({ apiUrl: 'https://proxy.webshare.io/api/v2/', enabled: true, }); }); it('should accept apiKey', () => { const config = webshareProviderConfigSchema.parse({ apiKey: 'test-key', enabled: false, }); expect(config.apiKey).toBe('test-key'); expect(config.enabled).toBe(false); }); }); describe('providerConfigSchema', () => { it('should accept empty config', () => { const config = providerConfigSchema.parse({}); expect(config).toEqual({}); }); it('should accept partial provider config', () => { const config = providerConfigSchema.parse({ eod: { name: 'eod', apiKey: 'test-key', }, yahoo: { name: 'yahoo', }, }); expect(config.eod?.apiKey).toBe('test-key'); expect(config.yahoo?.baseUrl).toBe('https://query1.finance.yahoo.com'); expect(config.ib).toBeUndefined(); }); it('should accept full provider config', () => { const config = providerConfigSchema.parse({ eod: { name: 'eod', apiKey: 'eod-key', tier: 'all-in-one', }, ib: { name: 'ib', gateway: { host: 'gateway.ib.com', port: 7497, clientId: 2, }, }, qm: { name: 'qm', username: 'user', password: 'pass', webmasterId: '123', }, yahoo: { name: 'yahoo', crumb: 'xyz', }, webshare: { apiKey: 'ws-key', }, }); expect(config.eod?.tier).toBe('all-in-one'); expect(config.ib?.gateway.port).toBe(7497); expect(config.qm?.username).toBe('user'); expect(config.yahoo?.crumb).toBe('xyz'); expect(config.webshare?.apiKey).toBe('ws-key'); }); }); });