fix issues

This commit is contained in:
2026-01-01 22:46:59 +01:00
parent 206a03ec48
commit 79913bb45e
336 changed files with 3932 additions and 76 deletions

View File

@@ -0,0 +1,211 @@
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.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');
});
});
});

View File

@@ -0,0 +1,17 @@
import { describe, it, expect } from 'vitest';
import { getWebsitePublicEnv, getWebsiteServerEnv, isTruthyEnv } from './env';
describe('env', () => {
it('should be defined', () => {
expect(getWebsiteServerEnv()).toBeDefined();
expect(getWebsitePublicEnv()).toBeDefined();
});
it('should interpret truthy env strings', () => {
expect(isTruthyEnv(undefined)).toBe(false);
expect(isTruthyEnv('0')).toBe(false);
expect(isTruthyEnv('false')).toBe(false);
expect(isTruthyEnv('TRUE')).toBe(true);
expect(isTruthyEnv('1')).toBe(true);
});
});

View File

@@ -0,0 +1,99 @@
import { describe, it, expect } from 'vitest';
import { mediaConfig, type MediaConfig } from './mediaConfig';
describe('mediaConfig', () => {
describe('avatars', () => {
it('should have a default fallback path', () => {
expect(mediaConfig.avatars.defaultFallback).toBe('/images/avatars/neutral-default-avatar.jpeg');
});
it('should have all avatar type paths', () => {
expect(mediaConfig.avatars.paths).toHaveProperty('male');
expect(mediaConfig.avatars.paths).toHaveProperty('female');
expect(mediaConfig.avatars.paths).toHaveProperty('neutral');
});
it('should have correct male avatar path', () => {
expect(mediaConfig.avatars.paths.male).toBe('/images/avatars/male-default-avatar.jpg');
});
it('should have correct female avatar path', () => {
expect(mediaConfig.avatars.paths.female).toBe('/images/avatars/female-default-avatar.jpeg');
});
it('should have correct neutral avatar path', () => {
expect(mediaConfig.avatars.paths.neutral).toBe('/images/avatars/neutral-default-avatar.jpeg');
});
});
describe('api', () => {
it('should have avatar function that returns correct path', () => {
const result = mediaConfig.api.avatar('driver-123');
expect(result).toBe('/media/avatar/driver-123');
});
it('should have teamLogo function that returns correct path', () => {
const result = mediaConfig.api.teamLogo('team-456');
expect(result).toBe('/media/teams/team-456/logo');
});
it('should have trackImage function that returns correct path', () => {
const result = mediaConfig.api.trackImage('track-789');
expect(result).toBe('/media/tracks/track-789/image');
});
it('should have sponsorLogo function that returns correct path', () => {
const result = mediaConfig.api.sponsorLogo('sponsor-abc');
expect(result).toBe('/media/sponsors/sponsor-abc/logo');
});
it('should have categoryIcon function that returns correct path', () => {
const result = mediaConfig.api.categoryIcon('category-xyz');
expect(result).toBe('/media/categories/category-xyz/icon');
});
it('should handle special characters in IDs', () => {
const result = mediaConfig.api.avatar('driver-with-special_chars.123');
expect(result).toBe('/media/avatar/driver-with-special_chars.123');
});
});
describe('structure', () => {
it('should match expected MediaConfig interface', () => {
const config: MediaConfig = mediaConfig;
expect(config).toHaveProperty('avatars');
expect(config).toHaveProperty('api');
expect(typeof config.avatars.defaultFallback).toBe('string');
expect(typeof config.avatars.paths).toBe('object');
expect(typeof config.api.avatar).toBe('function');
expect(typeof config.api.teamLogo).toBe('function');
expect(typeof config.api.trackImage).toBe('function');
expect(typeof config.api.sponsorLogo).toBe('function');
expect(typeof config.api.categoryIcon).toBe('function');
});
it('should be immutable (as const)', () => {
// The config is declared as 'as const', so it should be readonly
// This test verifies the type is correct
const config: MediaConfig = mediaConfig;
expect(config).toBeDefined();
});
});
describe('consistency', () => {
it('should use consistent paths for avatars', () => {
// Default fallback should match neutral path
expect(mediaConfig.avatars.defaultFallback).toBe(mediaConfig.avatars.paths.neutral);
});
it('should all start with /media/ prefix', () => {
expect(mediaConfig.api.avatar('test')).toMatch(/^\/media\//);
expect(mediaConfig.api.teamLogo('test')).toMatch(/^\/media\//);
expect(mediaConfig.api.trackImage('test')).toMatch(/^\/media\//);
expect(mediaConfig.api.sponsorLogo('test')).toMatch(/^\/media\//);
expect(mediaConfig.api.categoryIcon('test')).toMatch(/^\/media\//);
});
});
});