tests cleanup
This commit is contained in:
@@ -1,170 +0,0 @@
|
||||
import type { Page, BrowserContext } from '@playwright/test';
|
||||
import type { RouteAccess } from './websiteRouteInventory';
|
||||
|
||||
export type WebsiteAuthContext = 'public' | 'auth' | 'admin' | 'sponsor';
|
||||
|
||||
export type WebsiteSessionDriftMode = 'invalid-cookie' | 'expired' | 'missing-sponsor-id';
|
||||
export type WebsiteFaultMode = 'null-array' | 'missing-field' | 'invalid-date';
|
||||
|
||||
export function authContextForAccess(access: RouteAccess): WebsiteAuthContext {
|
||||
if (access === 'public') return 'public';
|
||||
if (access === 'auth') return 'auth';
|
||||
if (access === 'admin') return 'admin';
|
||||
return 'sponsor';
|
||||
}
|
||||
|
||||
export async function setWebsiteAuthContext(
|
||||
context: BrowserContext,
|
||||
auth: WebsiteAuthContext,
|
||||
options: { sessionDrift?: WebsiteSessionDriftMode; faultMode?: WebsiteFaultMode } = {},
|
||||
): Promise<void> {
|
||||
const domain = 'localhost';
|
||||
const base = { domain, path: '/' };
|
||||
|
||||
// The website uses `gp_session` cookie for authentication
|
||||
// For smoke tests, we use normal login API with seeded demo user credentials
|
||||
// to get real session cookies
|
||||
|
||||
if (auth === 'public') {
|
||||
// No authentication needed
|
||||
await context.clearCookies();
|
||||
return;
|
||||
}
|
||||
|
||||
// For authenticated contexts, we need to perform a normal login
|
||||
// This ensures we get real session cookies with proper structure
|
||||
// Note: All auth contexts use the same seeded demo driver user for simplicity
|
||||
// Role-based access control is tested separately in integration tests
|
||||
|
||||
// Call the normal login API with seeded demo user credentials
|
||||
// Use demo.driver@example.com for all auth contexts (driver role)
|
||||
const response = await fetch('http://localhost:3101/auth/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: 'demo.driver@example.com',
|
||||
password: 'Demo1234!',
|
||||
}),
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Normal login failed: ${response.status}`);
|
||||
}
|
||||
|
||||
// Extract cookies from the response
|
||||
const setCookieHeader = response.headers.get('set-cookie');
|
||||
if (!setCookieHeader) {
|
||||
throw new Error('No cookies set by normal login');
|
||||
}
|
||||
|
||||
// Parse the Set-Cookie headers
|
||||
const cookies = setCookieHeader.split(',').map(cookieStr => {
|
||||
const parts = cookieStr.split(';').map(p => p.trim());
|
||||
const [nameValue, ...attributes] = parts;
|
||||
const [name, value] = nameValue.split('=');
|
||||
|
||||
const cookie: any = {
|
||||
name,
|
||||
value: decodeURIComponent(value),
|
||||
domain: 'localhost',
|
||||
path: '/',
|
||||
expires: Math.floor(Date.now() / 1000) + 3600, // 1 hour
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: 'Lax' as const
|
||||
};
|
||||
|
||||
for (const attr of attributes) {
|
||||
const [attrName, attrValue] = attr.split('=');
|
||||
const lowerName = attrName.toLowerCase();
|
||||
|
||||
if (lowerName === 'path') cookie.path = attrValue;
|
||||
else if (lowerName === 'httponly') cookie.httpOnly = true;
|
||||
else if (lowerName === 'secure') cookie.secure = true;
|
||||
else if (lowerName === 'samesite') cookie.sameSite = attrValue as any;
|
||||
else if (lowerName === 'domain') {
|
||||
// Skip domain from API - we'll use localhost
|
||||
}
|
||||
else if (lowerName === 'max-age') cookie.expires = Math.floor(Date.now() / 1000) + parseInt(attrValue);
|
||||
}
|
||||
|
||||
// For Docker/local testing, ensure cookies work with localhost
|
||||
// Playwright's context.addCookies requires specific settings for localhost
|
||||
if (cookie.domain === 'localhost') {
|
||||
cookie.secure = false; // Localhost doesn't need HTTPS
|
||||
// Keep sameSite as provided by API, but ensure it's compatible
|
||||
if (cookie.sameSite === 'None') {
|
||||
// For SameSite=None, we need Secure=true, but localhost doesn't support it
|
||||
// So we fall back to Lax for local testing
|
||||
cookie.sameSite = 'Lax';
|
||||
}
|
||||
}
|
||||
|
||||
return cookie;
|
||||
});
|
||||
|
||||
// Apply session drift or fault modes if specified
|
||||
if (options.sessionDrift || options.faultMode) {
|
||||
const sessionCookie = cookies.find(c => c.name === 'gp_session');
|
||||
|
||||
if (sessionCookie) {
|
||||
if (options.sessionDrift) {
|
||||
sessionCookie.value = `drift-${options.sessionDrift}-${sessionCookie.value}`;
|
||||
}
|
||||
|
||||
if (options.faultMode) {
|
||||
cookies.push({
|
||||
name: 'gridpilot_fault_mode',
|
||||
value: options.faultMode,
|
||||
domain,
|
||||
path: '/',
|
||||
expires: Math.floor(Date.now() / 1000) + 3600,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: 'Lax' as const
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear existing cookies and add the new ones
|
||||
await context.clearCookies();
|
||||
await context.addCookies(cookies);
|
||||
}
|
||||
|
||||
export type ConsoleCapture = {
|
||||
consoleErrors: string[];
|
||||
pageErrors: string[];
|
||||
};
|
||||
|
||||
export function attachConsoleErrorCapture(page: Page): ConsoleCapture {
|
||||
const consoleErrors: string[] = [];
|
||||
const pageErrors: string[] = [];
|
||||
|
||||
page.on('pageerror', (err) => {
|
||||
pageErrors.push(String(err));
|
||||
});
|
||||
|
||||
page.on('console', (msg) => {
|
||||
const type = msg.type();
|
||||
if (type !== 'error') return;
|
||||
|
||||
const text = msg.text();
|
||||
|
||||
// Filter known benign warnings (keep small + generic).
|
||||
if (text.includes('Download the React DevTools')) return;
|
||||
|
||||
// Next/Image accessibility warning (not a runtime failure for smoke coverage).
|
||||
if (text.includes('Image is missing required "alt" property')) return;
|
||||
|
||||
// React controlled <select> warning (still renders fine; treat as non-fatal for route coverage).
|
||||
if (text.includes('Use the `defaultValue` or `value` props on <select> instead of setting `selected` on <option>.')) return;
|
||||
|
||||
consoleErrors.push(`[${type}] ${text}`);
|
||||
});
|
||||
|
||||
return { consoleErrors, pageErrors };
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export type RouteAccess = 'public' | 'auth' | 'admin' | 'sponsor';
|
||||
|
||||
export type RouteParams = Record<string, string>;
|
||||
|
||||
export type WebsiteRouteDefinition = {
|
||||
pathTemplate: string;
|
||||
params?: RouteParams;
|
||||
access: RouteAccess;
|
||||
expectedPathTemplate?: string;
|
||||
allowNotFound?: boolean;
|
||||
};
|
||||
|
||||
function walkDir(rootDir: string): string[] {
|
||||
const entries = fs.readdirSync(rootDir, { withFileTypes: true });
|
||||
const results: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry.name.startsWith('.')) continue;
|
||||
|
||||
const fullPath = path.join(rootDir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
results.push(...walkDir(fullPath));
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(fullPath);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function toPathTemplate(appDir: string, pageFilePath: string): string {
|
||||
const rel = path.relative(appDir, pageFilePath);
|
||||
const segments = rel.split(path.sep);
|
||||
|
||||
// drop trailing "page.tsx"
|
||||
segments.pop();
|
||||
|
||||
// root page.tsx
|
||||
if (segments.length === 0) return '/';
|
||||
|
||||
return `/${segments.join('/')}`;
|
||||
}
|
||||
|
||||
export function listNextAppPageTemplates(appDir?: string): string[] {
|
||||
const resolvedAppDir = appDir ?? path.join(process.cwd(), 'apps', 'website', 'app');
|
||||
|
||||
const files = walkDir(resolvedAppDir);
|
||||
const pages = files.filter((f) => path.basename(f) === 'page.tsx');
|
||||
|
||||
return pages.map((pagePath) => toPathTemplate(resolvedAppDir, pagePath));
|
||||
}
|
||||
|
||||
export function resolvePathTemplate(pathTemplate: string, params: RouteParams = {}): string {
|
||||
return pathTemplate.replace(/\[([^\]]+)\]/g, (_match, key: string) => {
|
||||
const replacement = params[key];
|
||||
if (!replacement) {
|
||||
throw new Error(`Missing route param "${key}" for template "${pathTemplate}"`);
|
||||
}
|
||||
return replacement;
|
||||
});
|
||||
}
|
||||
|
||||
// Default IDs used to resolve dynamic routes in smoke tests.
|
||||
// These values must be supported by the docker mock API in docker-compose.test.yml.
|
||||
const LEAGUE_ID = 'league-1';
|
||||
const DRIVER_ID = 'driver-1';
|
||||
const TEAM_ID = 'team-1';
|
||||
const RACE_ID = 'race-1';
|
||||
const PROTEST_ID = 'protest-1';
|
||||
|
||||
const ROUTE_META: Record<string, Omit<WebsiteRouteDefinition, 'pathTemplate'>> = {
|
||||
'/': { access: 'public' },
|
||||
|
||||
'/404': { access: 'public' },
|
||||
'/500': { access: 'public' },
|
||||
|
||||
'/admin': { access: 'admin' },
|
||||
'/admin/users': { access: 'admin' },
|
||||
|
||||
'/auth/forgot-password': { access: 'public' },
|
||||
'/auth/login': { access: 'public' },
|
||||
'/auth/reset-password': { access: 'public' },
|
||||
'/auth/signup': { access: 'public' },
|
||||
|
||||
'/dashboard': { access: 'auth' },
|
||||
|
||||
'/drivers': { access: 'public' },
|
||||
'/drivers/[id]': { access: 'public', params: { id: DRIVER_ID } },
|
||||
|
||||
'/leaderboards': { access: 'public' },
|
||||
'/leaderboards/drivers': { access: 'public' },
|
||||
|
||||
'/leagues': { access: 'public' },
|
||||
'/leagues/create': { access: 'auth' },
|
||||
'/leagues/[id]': { access: 'public', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/roster/admin': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/rulebook': { access: 'public', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/schedule': { access: 'public', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/schedule/admin': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/settings': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/sponsorships': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/standings': { access: 'public', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/stewarding': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
'/leagues/[id]/stewarding/protests/[protestId]': {
|
||||
access: 'admin',
|
||||
params: { id: LEAGUE_ID, protestId: PROTEST_ID },
|
||||
},
|
||||
'/leagues/[id]/wallet': { access: 'admin', params: { id: LEAGUE_ID } },
|
||||
|
||||
'/onboarding': { access: 'auth' },
|
||||
|
||||
'/profile': { access: 'auth' },
|
||||
'/profile/leagues': { access: 'auth' },
|
||||
'/profile/liveries': { access: 'auth' },
|
||||
'/profile/liveries/upload': { access: 'auth' },
|
||||
'/profile/settings': { access: 'auth' },
|
||||
'/profile/sponsorship-requests': { access: 'auth' },
|
||||
|
||||
'/races': { access: 'public' },
|
||||
'/races/all': { access: 'public' },
|
||||
'/races/[id]': { access: 'public', params: { id: RACE_ID } },
|
||||
'/races/[id]/results': { access: 'public', params: { id: RACE_ID } },
|
||||
'/races/[id]/stewarding': { access: 'admin', params: { id: RACE_ID } },
|
||||
|
||||
'/sponsor': { access: 'sponsor', expectedPathTemplate: '/sponsor/dashboard' },
|
||||
'/sponsor/billing': { access: 'sponsor' },
|
||||
'/sponsor/campaigns': { access: 'sponsor' },
|
||||
'/sponsor/dashboard': { access: 'sponsor' },
|
||||
'/sponsor/leagues': { access: 'sponsor' },
|
||||
'/sponsor/leagues/[id]': { access: 'sponsor', params: { id: LEAGUE_ID } },
|
||||
'/sponsor/settings': { access: 'sponsor' },
|
||||
'/sponsor/signup': { access: 'public' },
|
||||
|
||||
'/teams': { access: 'public' },
|
||||
'/teams/leaderboard': { access: 'public' },
|
||||
'/teams/[id]': { access: 'public', params: { id: TEAM_ID } },
|
||||
};
|
||||
|
||||
export function getWebsiteRouteInventory(): WebsiteRouteDefinition[] {
|
||||
const discovered = listNextAppPageTemplates();
|
||||
|
||||
const missingMeta = discovered.filter((template) => !ROUTE_META[template]);
|
||||
if (missingMeta.length > 0) {
|
||||
throw new Error(
|
||||
`Missing ROUTE_META entries for discovered pages:\n${missingMeta
|
||||
.slice()
|
||||
.sort()
|
||||
.map((t) => `- ${t}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
|
||||
const extraMeta = Object.keys(ROUTE_META).filter((template) => !discovered.includes(template));
|
||||
if (extraMeta.length > 0) {
|
||||
throw new Error(
|
||||
`ROUTE_META contains templates that are not present as page.tsx routes:\n${extraMeta
|
||||
.slice()
|
||||
.sort()
|
||||
.map((t) => `- ${t}`)
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
|
||||
return discovered
|
||||
.slice()
|
||||
.sort()
|
||||
.map((pathTemplate) => ({ pathTemplate, ...ROUTE_META[pathTemplate] }));
|
||||
}
|
||||
|
||||
export function getWebsiteParamEdgeCases(): WebsiteRouteDefinition[] {
|
||||
return [
|
||||
{ pathTemplate: '/races/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true },
|
||||
{ pathTemplate: '/leagues/[id]', params: { id: 'does-not-exist' }, access: 'public', allowNotFound: true },
|
||||
];
|
||||
}
|
||||
|
||||
export function getWebsiteFaultInjectionRoutes(): WebsiteRouteDefinition[] {
|
||||
return [
|
||||
{ pathTemplate: '/leagues/[id]', params: { id: LEAGUE_ID }, access: 'public' },
|
||||
{ pathTemplate: '/leagues/[id]/schedule/admin', params: { id: LEAGUE_ID }, access: 'admin' },
|
||||
{ pathTemplate: '/sponsor/dashboard', access: 'sponsor' },
|
||||
{ pathTemplate: '/races/[id]', params: { id: RACE_ID }, access: 'public' },
|
||||
];
|
||||
}
|
||||
|
||||
export function getWebsiteAuthDriftRoutes(): WebsiteRouteDefinition[] {
|
||||
return [{ pathTemplate: '/sponsor/dashboard', access: 'sponsor' }];
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* TDD Tests for MediaResolverPort interface contract
|
||||
*
|
||||
* Tests cover:
|
||||
* - Interface contract compliance
|
||||
* - Method signatures
|
||||
* - Return types
|
||||
* - Error handling behavior
|
||||
*/
|
||||
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
// Mock interface for testing
|
||||
interface MediaResolverPort {
|
||||
resolve(ref: MediaReference, baseUrl: string): Promise<string | null>;
|
||||
}
|
||||
|
||||
describe('MediaResolverPort', () => {
|
||||
let mockResolver: MediaResolverPort;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock implementation for testing
|
||||
mockResolver = {
|
||||
resolve: jest.fn(async (ref: MediaReference, baseUrl: string): Promise<string | null> => {
|
||||
// Mock implementation that returns different URLs based on type
|
||||
switch (ref.type) {
|
||||
case 'system-default':
|
||||
return `${baseUrl}/defaults/${ref.variant}`;
|
||||
case 'generated':
|
||||
return `${baseUrl}/generated/${ref.generationRequestId}`;
|
||||
case 'uploaded':
|
||||
return `${baseUrl}/media/${ref.mediaId}`;
|
||||
case 'none':
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
describe('Interface Contract', () => {
|
||||
it('should have a resolve method', () => {
|
||||
expect(typeof mockResolver.resolve).toBe('function');
|
||||
});
|
||||
|
||||
it('should accept MediaReference and string parameters', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
await expect(mockResolver.resolve(ref, baseUrl)).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should return Promise<string | null>', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
expect(result === null || typeof result === 'string').toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('System Default Resolution', () => {
|
||||
it('should resolve system-default avatar to correct URL', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/defaults/avatar');
|
||||
});
|
||||
|
||||
it('should resolve system-default logo to correct URL', async () => {
|
||||
const ref = MediaReference.createSystemDefault('logo');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/defaults/logo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Generated Resolution', () => {
|
||||
it('should resolve generated reference to correct URL', async () => {
|
||||
const ref = MediaReference.createGenerated('req-123');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/generated/req-123');
|
||||
});
|
||||
|
||||
it('should handle generated reference with special characters', async () => {
|
||||
const ref = MediaReference.createGenerated('req-abc-123_XYZ');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/generated/req-abc-123_XYZ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Uploaded Resolution', () => {
|
||||
it('should resolve uploaded reference to correct URL', async () => {
|
||||
const ref = MediaReference.createUploaded('media-456');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/media/media-456');
|
||||
});
|
||||
|
||||
it('should handle uploaded reference with special characters', async () => {
|
||||
const ref = MediaReference.createUploaded('media-abc-456_XYZ');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/media/media-abc-456_XYZ');
|
||||
});
|
||||
});
|
||||
|
||||
describe('None Resolution', () => {
|
||||
it('should resolve none reference to null', async () => {
|
||||
const ref = MediaReference.createNone();
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Base URL Handling', () => {
|
||||
it('should handle base URL without trailing slash', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/defaults/avatar');
|
||||
});
|
||||
|
||||
it('should handle base URL with trailing slash', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com/';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
// Implementation should handle this consistently
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should handle localhost URLs', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'http://localhost:3000';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('http://localhost:3000/defaults/avatar');
|
||||
});
|
||||
|
||||
it('should handle relative URLs', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = '/api';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('/api/defaults/avatar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Error Handling', () => {
|
||||
it('should handle null baseUrl gracefully', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
|
||||
// This should not throw but handle gracefully
|
||||
await expect(mockResolver.resolve(ref, null as any)).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle empty baseUrl gracefully', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
|
||||
// This should not throw but handle gracefully
|
||||
await expect(mockResolver.resolve(ref, '')).resolves.toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle undefined baseUrl gracefully', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
|
||||
// This should not throw but handle gracefully
|
||||
await expect(mockResolver.resolve(ref, undefined as any)).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle very long media IDs', async () => {
|
||||
const longId = 'a'.repeat(1000);
|
||||
const ref = MediaReference.createUploaded(longId);
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe(`https://api.example.com/media/${longId}`);
|
||||
});
|
||||
|
||||
it('should handle Unicode characters in IDs', async () => {
|
||||
const ref = MediaReference.createUploaded('media-日本語-123');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const result = await mockResolver.resolve(ref, baseUrl);
|
||||
|
||||
expect(result).toBe('https://api.example.com/media/media-日本語-123');
|
||||
});
|
||||
|
||||
it('should handle multiple calls with different references', async () => {
|
||||
const refs = [
|
||||
MediaReference.createSystemDefault('avatar'),
|
||||
MediaReference.createGenerated('req-123'),
|
||||
MediaReference.createUploaded('media-456'),
|
||||
MediaReference.createNone()
|
||||
];
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const results = await Promise.all(refs.map(ref => mockResolver.resolve(ref, baseUrl)));
|
||||
|
||||
expect(results).toEqual([
|
||||
'https://api.example.com/defaults/avatar',
|
||||
'https://api.example.com/generated/req-123',
|
||||
'https://api.example.com/media/media-456',
|
||||
null
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Performance Considerations', () => {
|
||||
it('should resolve quickly for simple cases', async () => {
|
||||
const ref = MediaReference.createSystemDefault('avatar');
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const start = Date.now();
|
||||
await mockResolver.resolve(ref, baseUrl);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(duration).toBeLessThan(100); // Should be very fast
|
||||
});
|
||||
|
||||
it('should handle concurrent resolutions', async () => {
|
||||
const refs = Array.from({ length: 100 }, (_, i) =>
|
||||
MediaReference.createUploaded(`media-${i}`)
|
||||
);
|
||||
const baseUrl = 'https://api.example.com';
|
||||
|
||||
const start = Date.now();
|
||||
const results = await Promise.all(refs.map(ref => mockResolver.resolve(ref, baseUrl)));
|
||||
const duration = Date.now() - start;
|
||||
|
||||
expect(results.length).toBe(100);
|
||||
expect(duration).toBeLessThan(1000); // Should handle 100 concurrent calls quickly
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user