website refactor
This commit is contained in:
70
tests/unit/website/BaseApiClient.test.ts
Normal file
70
tests/unit/website/BaseApiClient.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { BaseApiClient } from '../../../apps/website/lib/api/base/BaseApiClient';
|
||||
import { Logger } from '../../../apps/website/lib/interfaces/Logger';
|
||||
import { ErrorReporter } from '../../../apps/website/lib/interfaces/ErrorReporter';
|
||||
|
||||
describe('BaseApiClient - Invariants', () => {
|
||||
let client: BaseApiClient;
|
||||
let mockLogger: Logger;
|
||||
let mockErrorReporter: ErrorReporter;
|
||||
|
||||
beforeEach(() => {
|
||||
mockLogger = {
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
};
|
||||
mockErrorReporter = {
|
||||
report: vi.fn(),
|
||||
};
|
||||
client = new BaseApiClient(
|
||||
'https://api.example.com',
|
||||
mockErrorReporter,
|
||||
mockLogger
|
||||
);
|
||||
});
|
||||
|
||||
describe('classifyError()', () => {
|
||||
it('should classify 5xx as SERVER_ERROR', () => {
|
||||
expect((client as any).classifyError(500)).toBe('SERVER_ERROR');
|
||||
expect((client as any).classifyError(503)).toBe('SERVER_ERROR');
|
||||
});
|
||||
|
||||
it('should classify 429 as RATE_LIMIT_ERROR', () => {
|
||||
expect((client as any).classifyError(429)).toBe('RATE_LIMIT_ERROR');
|
||||
});
|
||||
|
||||
it('should classify 401/403 as AUTH_ERROR', () => {
|
||||
expect((client as any).classifyError(401)).toBe('AUTH_ERROR');
|
||||
expect((client as any).classifyError(403)).toBe('AUTH_ERROR');
|
||||
});
|
||||
|
||||
it('should classify 400 as VALIDATION_ERROR', () => {
|
||||
expect((client as any).classifyError(400)).toBe('VALIDATION_ERROR');
|
||||
});
|
||||
|
||||
it('should classify 404 as NOT_FOUND', () => {
|
||||
expect((client as any).classifyError(404)).toBe('NOT_FOUND');
|
||||
});
|
||||
|
||||
it('should classify other 4xx as UNKNOWN_ERROR', () => {
|
||||
expect((client as any).classifyError(418)).toBe('UNKNOWN_ERROR');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRetryableError()', () => {
|
||||
it('should return true for retryable error types', () => {
|
||||
expect((client as any).isRetryableError('NETWORK_ERROR')).toBe(true);
|
||||
expect((client as any).isRetryableError('SERVER_ERROR')).toBe(true);
|
||||
expect((client as any).isRetryableError('RATE_LIMIT_ERROR')).toBe(true);
|
||||
expect((client as any).isRetryableError('TIMEOUT_ERROR')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for non-retryable error types', () => {
|
||||
expect((client as any).isRetryableError('AUTH_ERROR')).toBe(false);
|
||||
expect((client as any).isRetryableError('VALIDATION_ERROR')).toBe(false);
|
||||
expect((client as any).isRetryableError('NOT_FOUND')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
69
tests/unit/website/RouteConfig.test.ts
Normal file
69
tests/unit/website/RouteConfig.test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { routeMatchers, routes } from '../../../apps/website/lib/routing/RouteConfig';
|
||||
|
||||
describe('RouteConfig - routeMatchers Invariants', () => {
|
||||
describe('isPublic()', () => {
|
||||
it('should return true for exact public matches', () => {
|
||||
expect(routeMatchers.isPublic('/')).toBe(true);
|
||||
expect(routeMatchers.isPublic('/leagues')).toBe(true);
|
||||
expect(routeMatchers.isPublic('/auth/login')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true for top-level detail pages (league, race, driver, team)', () => {
|
||||
expect(routeMatchers.isPublic('/leagues/123')).toBe(true);
|
||||
expect(routeMatchers.isPublic('/races/456')).toBe(true);
|
||||
expect(routeMatchers.isPublic('/drivers/789')).toBe(true);
|
||||
expect(routeMatchers.isPublic('/teams/abc')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for "leagues/create" (protected)', () => {
|
||||
expect(routeMatchers.isPublic('/leagues/create')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for nested protected routes', () => {
|
||||
expect(routeMatchers.isPublic('/dashboard')).toBe(false);
|
||||
expect(routeMatchers.isPublic('/profile/settings')).toBe(false);
|
||||
expect(routeMatchers.isPublic('/admin/users')).toBe(false);
|
||||
expect(routeMatchers.isPublic('/sponsor/dashboard')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for sponsor signup (public)', () => {
|
||||
expect(routeMatchers.isPublic('/sponsor/signup')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for unknown routes', () => {
|
||||
expect(routeMatchers.isPublic('/unknown-route')).toBe(false);
|
||||
expect(routeMatchers.isPublic('/api/something')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('requiresRole()', () => {
|
||||
it('should return admin roles for admin routes', () => {
|
||||
const roles = routeMatchers.requiresRole('/admin');
|
||||
expect(roles).toContain('admin');
|
||||
expect(roles).toContain('super-admin');
|
||||
|
||||
const userRoles = routeMatchers.requiresRole('/admin/users');
|
||||
expect(userRoles).toEqual(roles);
|
||||
});
|
||||
|
||||
it('should return sponsor role for sponsor routes', () => {
|
||||
expect(routeMatchers.requiresRole('/sponsor/dashboard')).toEqual(['sponsor']);
|
||||
expect(routeMatchers.requiresRole('/sponsor/billing')).toEqual(['sponsor']);
|
||||
});
|
||||
|
||||
it('should return null for public routes', () => {
|
||||
expect(routeMatchers.requiresRole('/')).toBeNull();
|
||||
expect(routeMatchers.requiresRole('/leagues')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for non-role protected routes', () => {
|
||||
expect(routeMatchers.requiresRole('/dashboard')).toBeNull();
|
||||
expect(routeMatchers.requiresRole('/profile')).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for sponsor signup (public)', () => {
|
||||
expect(routeMatchers.requiresRole('/sponsor/signup')).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
75
tests/unit/website/apiBaseUrl.test.ts
Normal file
75
tests/unit/website/apiBaseUrl.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { getWebsiteApiBaseUrl } from '../../../apps/website/lib/config/apiBaseUrl';
|
||||
|
||||
describe('getWebsiteApiBaseUrl()', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
process.env = { ...originalEnv };
|
||||
// Clear relevant env vars
|
||||
delete process.env.NEXT_PUBLIC_API_BASE_URL;
|
||||
delete process.env.API_BASE_URL;
|
||||
delete process.env.NODE_ENV;
|
||||
delete process.env.CI;
|
||||
delete process.env.DOCKER;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('Browser Context', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('window', {});
|
||||
});
|
||||
|
||||
it('should use NEXT_PUBLIC_API_BASE_URL if provided', () => {
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL = 'https://api.example.com/';
|
||||
expect(getWebsiteApiBaseUrl()).toBe('https://api.example.com');
|
||||
});
|
||||
|
||||
it('should throw if missing env in test-like environment (CI)', () => {
|
||||
process.env.CI = 'true';
|
||||
expect(() => getWebsiteApiBaseUrl()).toThrow(/Missing NEXT_PUBLIC_API_BASE_URL/);
|
||||
});
|
||||
|
||||
it('should throw if missing env in test-like environment (DOCKER)', () => {
|
||||
process.env.DOCKER = 'true';
|
||||
expect(() => getWebsiteApiBaseUrl()).toThrow(/Missing NEXT_PUBLIC_API_BASE_URL/);
|
||||
});
|
||||
|
||||
it('should fallback to localhost in development (non-docker)', () => {
|
||||
process.env.NODE_ENV = 'development';
|
||||
expect(getWebsiteApiBaseUrl()).toBe('http://localhost:3001');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Server Context', () => {
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('window', undefined);
|
||||
});
|
||||
|
||||
it('should prioritize API_BASE_URL over NEXT_PUBLIC_API_BASE_URL', () => {
|
||||
process.env.API_BASE_URL = 'https://internal-api.example.com';
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL = 'https://public-api.example.com';
|
||||
expect(getWebsiteApiBaseUrl()).toBe('https://internal-api.example.com');
|
||||
});
|
||||
|
||||
it('should use NEXT_PUBLIC_API_BASE_URL if API_BASE_URL is missing', () => {
|
||||
process.env.NEXT_PUBLIC_API_BASE_URL = 'https://public-api.example.com';
|
||||
expect(getWebsiteApiBaseUrl()).toBe('https://public-api.example.com');
|
||||
});
|
||||
|
||||
it('should throw if missing env in test-like environment (CI)', () => {
|
||||
process.env.CI = 'true';
|
||||
expect(() => getWebsiteApiBaseUrl()).toThrow(/Missing API_BASE_URL/);
|
||||
});
|
||||
|
||||
it('should fallback to api:3000 in production (non-test environment)', () => {
|
||||
process.env.NODE_ENV = 'production';
|
||||
expect(getWebsiteApiBaseUrl()).toBe('http://api:3000');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user