import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; import { createContainer, ContainerManager } from '../../apps/website/lib/di/container'; import { SESSION_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN, LEAGUE_SERVICE_TOKEN, AUTH_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN, TEAM_SERVICE_TOKEN, RACE_SERVICE_TOKEN, DASHBOARD_SERVICE_TOKEN, LOGGER_TOKEN, CONFIG_TOKEN, LEAGUE_API_CLIENT_TOKEN, AUTH_API_CLIENT_TOKEN, DRIVER_API_CLIENT_TOKEN, TEAM_API_CLIENT_TOKEN, RACE_API_CLIENT_TOKEN, } from '../../apps/website/lib/di/tokens'; // Define minimal service interfaces for testing interface MockSessionService { getSession: () => Promise; } interface MockAuthService { login: (email: string, password: string) => Promise; } interface MockLeagueService { getAllLeagues: () => Promise; } interface MockLeagueMembershipService { getLeagueMemberships: (userId: string) => Promise; } interface ConfigFunction { (): string; } /** * Integration test for website DI container */ describe('Website DI Container Integration', () => { let originalEnv: NodeJS.ProcessEnv; beforeAll(() => { originalEnv = { ...process.env }; process.env.NODE_ENV = 'test'; }); afterAll(() => { process.env = originalEnv; ContainerManager.getInstance().dispose(); }); beforeEach(() => { // Clean up all API URL env vars before each test delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; ContainerManager.getInstance().dispose(); }); afterEach(() => { // Clean up after each test delete process.env.API_BASE_URL; delete process.env.NEXT_PUBLIC_API_BASE_URL; ContainerManager.getInstance().dispose(); }); describe('Basic Container Functionality', () => { it('creates container successfully', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); expect(container).toBeDefined(); expect(container).not.toBeNull(); }); it('resolves core services without errors', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); expect(() => container.get(LOGGER_TOKEN)).not.toThrow(); expect(() => container.get(CONFIG_TOKEN)).not.toThrow(); const logger = container.get(LOGGER_TOKEN); const config = container.get(CONFIG_TOKEN); expect(logger).toBeDefined(); expect(config).toBeDefined(); }); it('resolves API clients without errors', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); const apiClients = [ LEAGUE_API_CLIENT_TOKEN, AUTH_API_CLIENT_TOKEN, DRIVER_API_CLIENT_TOKEN, TEAM_API_CLIENT_TOKEN, RACE_API_CLIENT_TOKEN, ]; for (const token of apiClients) { expect(() => container.get(token)).not.toThrow(); const client = container.get(token); expect(client).toBeDefined(); } }); it('resolves auth services including SessionService', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); expect(() => container.get(SESSION_SERVICE_TOKEN)).not.toThrow(); expect(() => container.get(AUTH_SERVICE_TOKEN)).not.toThrow(); const sessionService = container.get(SESSION_SERVICE_TOKEN); const authService = container.get(AUTH_SERVICE_TOKEN); expect(sessionService).toBeDefined(); expect(authService).toBeDefined(); expect(typeof sessionService.getSession).toBe('function'); expect(typeof authService.login).toBe('function'); }); it('resolves league services including LeagueMembershipService', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); expect(() => container.get(LEAGUE_SERVICE_TOKEN)).not.toThrow(); expect(() => container.get(LEAGUE_MEMBERSHIP_SERVICE_TOKEN)).not.toThrow(); const leagueService = container.get(LEAGUE_SERVICE_TOKEN); const membershipService = container.get(LEAGUE_MEMBERSHIP_SERVICE_TOKEN); expect(leagueService).toBeDefined(); expect(membershipService).toBeDefined(); expect(typeof leagueService.getAllLeagues).toBe('function'); expect(typeof membershipService.getLeagueMemberships).toBe('function'); }); it('resolves domain services without errors', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); expect(() => container.get(DRIVER_SERVICE_TOKEN)).not.toThrow(); expect(() => container.get(TEAM_SERVICE_TOKEN)).not.toThrow(); expect(() => container.get(RACE_SERVICE_TOKEN)).not.toThrow(); expect(() => container.get(DASHBOARD_SERVICE_TOKEN)).not.toThrow(); const driverService = container.get(DRIVER_SERVICE_TOKEN); const teamService = container.get(TEAM_SERVICE_TOKEN); const raceService = container.get(RACE_SERVICE_TOKEN); const dashboardService = container.get(DASHBOARD_SERVICE_TOKEN); expect(driverService).toBeDefined(); expect(teamService).toBeDefined(); expect(raceService).toBeDefined(); expect(dashboardService).toBeDefined(); }); it('resolves all services in a single operation', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); const tokens = [ SESSION_SERVICE_TOKEN, LEAGUE_MEMBERSHIP_SERVICE_TOKEN, LEAGUE_SERVICE_TOKEN, AUTH_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN, TEAM_SERVICE_TOKEN, RACE_SERVICE_TOKEN, DASHBOARD_SERVICE_TOKEN, LOGGER_TOKEN, CONFIG_TOKEN, ]; const services = tokens.map(token => { try { return container.get(token); } catch (error) { throw new Error(`Failed to resolve token ${token.toString()}: ${error.message}`); } }); expect(services.length).toBe(tokens.length); services.forEach((service) => { expect(service).toBeDefined(); expect(service).not.toBeNull(); }); }); it('throws clear error for non-existent bindings', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); const fakeToken = Symbol.for('Non.Existent.Service'); expect(() => container.get(fakeToken)).toThrow(); }); }); describe('SSR Dynamic Environment Variables', () => { it('config binding is a function (not a string)', () => { process.env.API_BASE_URL = 'http://test:3001'; const container = createContainer(); const config = container.get(CONFIG_TOKEN); // Config should be a function that can be called expect(typeof config).toBe('function'); // Should be callable const configFn = config as ConfigFunction; expect(() => configFn()).not.toThrow(); }); it('config function returns current environment value', () => { process.env.API_BASE_URL = 'http://test:3001'; const container = createContainer(); const getConfig = container.get(CONFIG_TOKEN) as ConfigFunction; const configValue = getConfig(); // Should return some value (could be fallback in test env) expect(typeof configValue).toBe('string'); expect(configValue.length).toBeGreaterThan(0); }); it('multiple containers share the same config behavior', () => { process.env.API_BASE_URL = 'http://test:3001'; const container1 = createContainer(); const container2 = createContainer(); const config1 = container1.get(CONFIG_TOKEN) as ConfigFunction; const config2 = container2.get(CONFIG_TOKEN) as ConfigFunction; // Both should be functions expect(typeof config1).toBe('function'); expect(typeof config2).toBe('function'); // Both should return the same type of value expect(typeof config1()).toBe(typeof config2()); }); it('ContainerManager singleton behavior', () => { process.env.API_BASE_URL = 'http://test:3001'; const manager = ContainerManager.getInstance(); const container1 = manager.getContainer(); const container2 = manager.getContainer(); // Should be same instance expect(container1).toBe(container2); const config1 = container1.get(CONFIG_TOKEN) as ConfigFunction; const config2 = container2.get(CONFIG_TOKEN) as ConfigFunction; // Both should be functions expect(typeof config1).toBe('function'); expect(typeof config2).toBe('function'); }); it('API clients work with dynamic config', () => { process.env.API_BASE_URL = 'http://api-test:3001'; const container = createContainer(); const leagueClient = container.get(LEAGUE_API_CLIENT_TOKEN); expect(leagueClient).toBeDefined(); expect(leagueClient).not.toBeNull(); expect(typeof leagueClient).toBe('object'); }); it('dispose clears singleton instance', () => { process.env.API_BASE_URL = 'http://test:3001'; const manager = ContainerManager.getInstance(); const container1 = manager.getContainer(); manager.dispose(); const container2 = manager.getContainer(); // Should be different instances after dispose expect(container1).not.toBe(container2); }); it('services work after container recreation', () => { process.env.API_BASE_URL = 'http://test:3001'; const container1 = createContainer(); const config1 = container1.get(CONFIG_TOKEN) as ConfigFunction; ContainerManager.getInstance().dispose(); const container2 = createContainer(); const config2 = container2.get(CONFIG_TOKEN) as ConfigFunction; // Both should be functions expect(typeof config1).toBe('function'); expect(typeof config2).toBe('function'); }); }); describe('SSR Boot Safety', () => { it('resolves all tokens required for SSR entry rendering', () => { process.env.API_BASE_URL = 'http://localhost:3001'; const container = createContainer(); // Tokens typically used in SSR (middleware, layouts, initial page loads) const ssrTokens = [ LOGGER_TOKEN, CONFIG_TOKEN, SESSION_SERVICE_TOKEN, AUTH_SERVICE_TOKEN, LEAGUE_SERVICE_TOKEN, DRIVER_SERVICE_TOKEN, DASHBOARD_SERVICE_TOKEN, // API clients are often resolved by services AUTH_API_CLIENT_TOKEN, LEAGUE_API_CLIENT_TOKEN, ]; for (const token of ssrTokens) { try { const service = container.get(token); expect(service, `Failed to resolve ${token.toString()}`).toBeDefined(); } catch (error) { throw new Error(`SSR Boot Safety Failure: Could not resolve ${token.toString()}. Error: ${error.message}`); } } }); }); });