config-validation.test.ts•8.18 kB
import { 
  validateEnvironment, 
  validateConfig, 
  ConfigValidationError,
  parseConfigSafely,
  EnvironmentSchema,
  ConfigSchema
} from '../config/config-schema';
describe('Configuration Validation', () => {
  describe('Environment Validation', () => {
    it('should validate valid environment variables', () => {
      const validEnv = {
        GOOGLE_CLIENT_ID: 'test-client-id',
        GOOGLE_CLIENT_SECRET: 'test-client-secret',
        NODE_ENV: 'test' as const
      };
      expect(() => validateEnvironment(validEnv)).not.toThrow();
      const result = validateEnvironment(validEnv);
      expect(result.GOOGLE_CLIENT_ID).toBe('test-client-id');
      expect(result.GOOGLE_CLIENT_SECRET).toBe('test-client-secret');
    });
    it('should reject missing required environment variables', () => {
      const invalidEnv = {
        GOOGLE_CLIENT_SECRET: 'test-client-secret'
        // Missing GOOGLE_CLIENT_ID
      };
      expect(() => validateEnvironment(invalidEnv)).toThrow(ConfigValidationError);
    });
    it('should validate optional environment variables with defaults', () => {
      const envWithOptionals = {
        GOOGLE_CLIENT_ID: 'test-client-id',
        GOOGLE_CLIENT_SECRET: 'test-client-secret',
        PORT: '8080',
        LOG_LEVEL: 'debug' as const
      };
      const result = validateEnvironment(envWithOptionals);
      expect(result.PORT).toBe('8080');
      expect(result.LOG_LEVEL).toBe('debug');
    });
    it('should reject invalid port numbers', () => {
      const invalidEnv = {
        GOOGLE_CLIENT_ID: 'test-client-id',
        GOOGLE_CLIENT_SECRET: 'test-client-secret',
        PORT: 'not-a-number'
      };
      expect(() => validateEnvironment(invalidEnv)).toThrow(ConfigValidationError);
    });
    it('should reject invalid log levels', () => {
      const invalidEnv = {
        GOOGLE_CLIENT_ID: 'test-client-id',
        GOOGLE_CLIENT_SECRET: 'test-client-secret',
        LOG_LEVEL: 'invalid-level'
      };
      expect(() => validateEnvironment(invalidEnv)).toThrow(ConfigValidationError);
    });
  });
  describe('Configuration Validation', () => {
    const validConfig = {
      google: {
        clientId: 'test-client-id',
        clientSecret: 'test-client-secret',
        redirectUri: 'http://localhost:4153/oauth2callback',
        scopes: ['https://www.googleapis.com/auth/calendar.events']
      },
      server: {
        port: 3000,
        host: 'localhost'
      },
      auth: {
        port: 4153,
        host: 'localhost',
        useManualAuth: false
      },
      security: {
        enableDetailedErrors: true,
        sanitizeLogs: true,
        logLevel: 'debug' as const,
        redactSensitiveData: true
      }
    };
    it('should validate complete valid configuration', () => {
      expect(() => validateConfig(validConfig)).not.toThrow();
      const result = validateConfig(validConfig);
      expect(result.google.clientId).toBe('test-client-id');
      expect(result.server.port).toBe(3000);
    });
    it('should reject dummy client ID in production context', () => {
      const configWithDummy = {
        ...validConfig,
        google: {
          ...validConfig.google,
          clientId: 'dummy-client-id'
        }
      };
      expect(() => validateConfig(configWithDummy)).toThrow(ConfigValidationError);
    });
    it('should reject invalid redirect URI', () => {
      const configWithInvalidUri = {
        ...validConfig,
        google: {
          ...validConfig.google,
          redirectUri: 'not-a-valid-url'
        }
      };
      expect(() => validateConfig(configWithInvalidUri)).toThrow(ConfigValidationError);
    });
    it('should reject invalid port numbers', () => {
      const configWithInvalidPort = {
        ...validConfig,
        server: {
          ...validConfig.server,
          port: 70000 // Too high
        }
      };
      expect(() => validateConfig(configWithInvalidPort)).toThrow(ConfigValidationError);
    });
    it('should reject redirect URI without oauth2callback', () => {
      const configWithoutCallback = {
        ...validConfig,
        google: {
          ...validConfig.google,
          redirectUri: 'http://localhost:4153/wrong-endpoint'
        }
      };
      expect(() => validateConfig(configWithoutCallback)).toThrow(ConfigValidationError);
    });
    it('should require at least one scope', () => {
      const configWithoutScopes = {
        ...validConfig,
        google: {
          ...validConfig.google,
          scopes: []
        }
      };
      expect(() => validateConfig(configWithoutScopes)).toThrow(ConfigValidationError);
    });
  });
  describe('Safe Configuration Parsing', () => {
    const validConfig = {
      google: {
        clientId: 'test-client-id',
        clientSecret: 'test-client-secret',
        redirectUri: 'http://localhost:4153/oauth2callback',
        scopes: ['https://www.googleapis.com/auth/calendar.events']
      },
      server: {
        port: 3000,
        host: 'localhost'
      },
      auth: {
        port: 4153,
        host: 'localhost',
        useManualAuth: false
      },
      security: {
        enableDetailedErrors: true,
        sanitizeLogs: true,
        logLevel: 'debug' as const,
        redactSensitiveData: true
      }
    };
    it('should return success for valid configuration', () => {
      const result = parseConfigSafely(validConfig);
      expect(result.success).toBe(true);
      if (result.success) {
        expect(result.data.google.clientId).toBe('test-client-id');
      }
    });
    it('should return error for invalid configuration', () => {
      const invalidConfig = {
        ...validConfig,
        server: {
          ...validConfig.server,
          port: 'invalid-port'
        }
      };
      const result = parseConfigSafely(invalidConfig);
      expect(result.success).toBe(false);
      if (!result.success) {
        expect(result.error).toBeInstanceOf(ConfigValidationError);
        expect(result.error.getFormattedErrors().length).toBeGreaterThan(0);
      }
    });
  });
  describe('ConfigValidationError', () => {
    it('should format errors correctly', () => {
      const invalidConfig = {
        google: {
          clientId: '', // Invalid: empty string
          scopes: [] // Invalid: empty array
        }
      };
      try {
        validateConfig(invalidConfig);
        fail('Should have thrown ConfigValidationError');
      } catch (error) {
        expect(error).toBeInstanceOf(ConfigValidationError);
        if (error instanceof ConfigValidationError) {
          const formattedErrors = error.getFormattedErrors();
          expect(formattedErrors.length).toBeGreaterThan(0);
          expect(error.getSummary()).toContain('Configuration validation failed');
        }
      }
    });
  });
  describe('Schema Types', () => {
    it('should provide correct TypeScript types', () => {
      // This test ensures that our schema types work correctly at compile time
      const validEnvResult = EnvironmentSchema.safeParse({
        GOOGLE_CLIENT_ID: 'test',
        GOOGLE_CLIENT_SECRET: 'test'
      });
      expect(validEnvResult.success).toBe(true);
      if (validEnvResult.success) {
        // TypeScript should infer these types correctly
        const clientId: string = validEnvResult.data.GOOGLE_CLIENT_ID;
        const clientSecret: string = validEnvResult.data.GOOGLE_CLIENT_SECRET;
        expect(clientId).toBe('test');
        expect(clientSecret).toBe('test');
      }
      const validConfigResult = ConfigSchema.safeParse({
        google: {
          clientId: 'test',
          clientSecret: 'test',
          redirectUri: 'http://localhost:4153/oauth2callback',
          scopes: ['https://www.googleapis.com/auth/calendar.events']
        },
        server: { port: 3000, host: 'localhost' },
        auth: { port: 4153, host: 'localhost', useManualAuth: false },
        security: {
          enableDetailedErrors: true,
          sanitizeLogs: true,
          logLevel: 'debug',
          redactSensitiveData: true
        }
      });
      expect(validConfigResult.success).toBe(true);
    });
  });
});