import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { getWebsiteApiBaseUrl } from './apiBaseUrl'; describe('getWebsiteApiBaseUrl', () => { const originalEnv = process.env; beforeEach(() => { vi.resetModules(); process.env = { ...originalEnv }; // Clear window mock if (typeof window !== 'undefined') { delete (window as any).__NEXT_PUBLIC_API_BASE_URL__; } }); afterEach(() => { process.env = originalEnv; }); describe('with environment variables', () => { it('should return configured NEXT_PUBLIC_API_BASE_URL in browser', () => { // Mock browser environment vi.stubGlobal('window', { location: {} } as any); process.env.NEXT_PUBLIC_API_BASE_URL = 'https://api.example.com'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api.example.com'); }); it('should return configured API_BASE_URL in Node.js', () => { // Ensure we're not in browser vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = 'https://api.example.com'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api.example.com'); }); it('should prefer API_BASE_URL over NEXT_PUBLIC_API_BASE_URL in Node.js', () => { vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = 'https://api-server.com'; process.env.NEXT_PUBLIC_API_BASE_URL = 'https://api-client.com'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api-server.com'); }); it('should fallback to NEXT_PUBLIC_API_BASE_URL if API_BASE_URL is not set', () => { vi.stubGlobal('window', undefined as any); delete process.env.API_BASE_URL; process.env.NEXT_PUBLIC_API_BASE_URL = 'https://api-fallback.com'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api-fallback.com'); }); }); describe('normalization', () => { it('should trim whitespace from URL', () => { vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = ' https://api.example.com '; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api.example.com'); }); it('should remove trailing slash', () => { vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = 'https://api.example.com/'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api.example.com'); }); it('should handle multiple trailing slashes', () => { vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = 'https://api.example.com///'; const result = getWebsiteApiBaseUrl(); // normalizeBaseUrl only removes one trailing slash expect(result).toBe('https://api.example.com//'); }); it('should handle URL with path and trailing slash', () => { vi.stubGlobal('window', undefined as any); process.env.API_BASE_URL = 'https://api.example.com/v1/'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://api.example.com/v1'); }); }); describe('fallback behavior', () => { it('should fallback to localhost in development when no env vars set', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'development'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; delete process.env.CI; delete process.env.DOCKER; const result = getWebsiteApiBaseUrl(); expect(result).toBe('http://localhost:3001'); }); it('should fallback to api:3000 in production when no env vars set', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'production'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; delete process.env.CI; delete process.env.DOCKER; const result = getWebsiteApiBaseUrl(); expect(result).toBe('http://api:3000'); }); }); describe('test-like environment', () => { it('should throw error in test environment when no URL configured', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'test'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; expect(() => getWebsiteApiBaseUrl()).toThrow( 'Missing API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.' ); }); it('should throw error in CI environment when no URL configured', () => { vi.stubGlobal('window', undefined as any); process.env.CI = 'true'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; expect(() => getWebsiteApiBaseUrl()).toThrow( 'Missing API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.' ); }); it('should throw error in Docker environment when no URL configured', () => { vi.stubGlobal('window', undefined as any); process.env.DOCKER = 'true'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; expect(() => getWebsiteApiBaseUrl()).toThrow( 'Missing API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.' ); }); it('should throw browser-specific error in test environment when in browser', () => { vi.stubGlobal('window', { location: {} } as any); process.env.NODE_ENV = 'test'; delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; expect(() => getWebsiteApiBaseUrl()).toThrow( 'Missing NEXT_PUBLIC_API_BASE_URL. In Docker/CI/test we do not allow falling back to localhost.' ); }); it('should work in test environment when URL is configured', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'test'; process.env.API_BASE_URL = 'https://test-api.example.com'; const result = getWebsiteApiBaseUrl(); expect(result).toBe('https://test-api.example.com'); }); }); describe('empty string handling', () => { it('should treat empty string as not configured', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'development'; process.env.API_BASE_URL = ''; delete process.env.NEXT_PUBLIC_API_BASE_URL; const result = getWebsiteApiBaseUrl(); expect(result).toBe('http://localhost:3001'); }); it('should treat whitespace-only string as not configured', () => { vi.stubGlobal('window', undefined as any); process.env.NODE_ENV = 'development'; process.env.API_BASE_URL = ' '; delete process.env.NEXT_PUBLIC_API_BASE_URL; const result = getWebsiteApiBaseUrl(); expect(result).toBe('http://localhost:3001'); }); }); });