340 lines
11 KiB
TypeScript
340 lines
11 KiB
TypeScript
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<unknown>;
|
|
}
|
|
|
|
interface MockAuthService {
|
|
login: (email: string, password: string) => Promise<unknown>;
|
|
}
|
|
|
|
interface MockLeagueService {
|
|
getAllLeagues: () => Promise<unknown[]>;
|
|
}
|
|
|
|
interface MockLeagueMembershipService {
|
|
getLeagueMemberships: (userId: string) => Promise<unknown[]>;
|
|
}
|
|
|
|
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<MockSessionService>(SESSION_SERVICE_TOKEN);
|
|
const authService = container.get<MockAuthService>(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<MockLeagueService>(LEAGUE_SERVICE_TOKEN);
|
|
const membershipService = container.get<MockLeagueMembershipService>(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}`);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|