Skip to main content
Glama
environment-validator.test.ts9.9 kB
import { describe, it, expect } from 'vitest'; import { EnvironmentValidator } from './environment-validator.js'; import type { AppConfig } from './types.js'; describe('EnvironmentValidator', () => { const validator = new EnvironmentValidator(); describe('必須設定項目の検証', () => { it('LYCHEE_REDMINE_URLが存在しない場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: '', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('missing_required'); expect(result.error.field).toBe('lycheeRedmine.url'); } }); it('LYCHEE_REDMINE_API_KEYが存在しない場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: '', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('missing_required'); expect(result.error.field).toBe('lycheeRedmine.apiKey'); } }); it('lycheeRedmineセクション全体が存在しない場合はエラーを返す', () => { const config: Partial<AppConfig> = { server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('missing_required'); } }); }); describe('URLフォーマットの検証', () => { it('HTTPSでないURLの場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'http://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('invalid_format'); expect(result.error.field).toBe('lycheeRedmine.url'); expect(result.error.message).toContain('HTTPS'); } }); it('不正なURL形式の場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'not-a-valid-url', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('invalid_format'); expect(result.error.field).toBe('lycheeRedmine.url'); } }); it('HTTPSのURLの場合は成功する', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(true); }); it('HTTPSのURLで末尾スラッシュがある場合も成功する', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com/', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(true); }); }); describe('APIキーの検証', () => { it('APIキーが空文字列の場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: '', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('missing_required'); expect(result.error.field).toBe('lycheeRedmine.apiKey'); } }); it('APIキーが空白のみの場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: ' ', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('missing_required'); expect(result.error.field).toBe('lycheeRedmine.apiKey'); } }); it('APIキーが有効な文字列の場合は成功する', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'a1b2c3d4e5f6', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(true); }); }); describe('オプション設定項目の検証', () => { it('serverセクションが存在しない場合でも、必須項目が揃っていれば成功する', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, }; const result = validator.validate(config); expect(result.ok).toBe(true); if (result.ok) { // デフォルト値が適用されることを確認 expect(result.value.server.logLevel).toBe('INFO'); expect(result.value.server.timeout).toBe(30000); expect(result.value.server.retryMaxAttempts).toBe(3); } }); it('部分的なserver設定でも、デフォルト値で補完される', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'DEBUG', timeout: undefined as unknown as number, retryMaxAttempts: undefined as unknown as number, }, }; const result = validator.validate(config); expect(result.ok).toBe(true); if (result.ok) { expect(result.value.server.logLevel).toBe('DEBUG'); expect(result.value.server.timeout).toBe(30000); expect(result.value.server.retryMaxAttempts).toBe(3); } }); it('不正なlogLevelの場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'INVALID' as 'INFO', timeout: 30000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('invalid_format'); expect(result.error.field).toBe('server.logLevel'); } }); it('timeoutが負の値の場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: -1000, retryMaxAttempts: 3, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('invalid_format'); expect(result.error.field).toBe('server.timeout'); } }); it('retryMaxAttemptsが負の値の場合はエラーを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://example.com', apiKey: 'valid-api-key', }, server: { logLevel: 'INFO', timeout: 30000, retryMaxAttempts: -1, }, }; const result = validator.validate(config); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe('invalid_format'); expect(result.error.field).toBe('server.retryMaxAttempts'); } }); }); describe('完全な設定の検証', () => { it('すべての設定が正しい場合は、完全なAppConfigを返す', () => { const config: Partial<AppConfig> = { lycheeRedmine: { url: 'https://redmine.example.com', apiKey: 'my-secret-api-key-123', }, server: { logLevel: 'WARN', timeout: 60000, retryMaxAttempts: 5, }, }; const result = validator.validate(config); expect(result.ok).toBe(true); if (result.ok) { expect(result.value).toEqual({ lycheeRedmine: { url: 'https://redmine.example.com', apiKey: 'my-secret-api-key-123', }, server: { logLevel: 'WARN', timeout: 60000, retryMaxAttempts: 5, }, }); } }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ssoma-dev/mcp-server-lychee-redmine'

If you have feedback or need assistance with the MCP directory API, please join our Discord server