website refactor

This commit is contained in:
2026-01-17 18:28:10 +01:00
parent 6d57f8b1ce
commit 64d9e7fd16
44 changed files with 1729 additions and 415 deletions

View File

@@ -0,0 +1,91 @@
import { spawn, ChildProcess } from 'child_process';
import { join } from 'path';
export interface WebsiteServerHarnessOptions {
port?: number;
env?: Record<string, string>;
cwd?: string;
}
export class WebsiteServerHarness {
private process: ChildProcess | null = null;
private logs: string[] = [];
private port: number;
constructor(options: WebsiteServerHarnessOptions = {}) {
this.port = options.port || 3000;
}
async start(): Promise<void> {
return new Promise((resolve, reject) => {
const cwd = join(process.cwd(), 'apps/website');
// Use 'npm run dev' or 'npm run start' depending on environment
// For integration tests, 'dev' is often easier if we don't want to build first,
// but 'start' is more realistic for SSR.
// Assuming 'npm run dev' for now as it's faster for local tests.
this.process = spawn('npm', ['run', 'dev', '--', '-p', this.port.toString()], {
cwd,
env: {
...process.env,
PORT: this.port.toString(),
...((this.process as unknown as { env: Record<string, string> })?.env || {}),
},
shell: true,
});
this.process.stdout?.on('data', (data) => {
const str = data.toString();
this.logs.push(str);
if (str.includes('ready') || str.includes('started') || str.includes('Local:')) {
resolve();
}
});
this.process.stderr?.on('data', (data) => {
const str = data.toString();
this.logs.push(str);
console.error(`[Website Server Error] ${str}`);
});
this.process.on('error', (err) => {
reject(err);
});
this.process.on('exit', (code) => {
if (code !== 0 && code !== null) {
console.error(`Website server exited with code ${code}`);
}
});
// Timeout after 30 seconds
setTimeout(() => {
reject(new Error('Website server failed to start within 30s'));
}, 30000);
});
}
async stop(): Promise<void> {
if (this.process) {
this.process.kill();
this.process = null;
}
}
getLogs(): string[] {
return this.logs;
}
getLogTail(lines: number = 60): string {
return this.logs.slice(-lines).join('');
}
hasErrorPatterns(): boolean {
const errorPatterns = [
'uncaughtException',
'unhandledRejection',
'Error: ',
];
return this.logs.some(log => errorPatterns.some(pattern => log.includes(pattern)));
}
}

View File

@@ -20,7 +20,7 @@ export class ApiClient {
/**
* Make HTTP request to API
*/
private async request<T>(method: string, path: string, body?: any, headers: Record<string, string> = {}): Promise<T> {
private async request<T>(method: string, path: string, body?: unknown, headers: Record<string, string> = {}): Promise<T> {
const url = `${this.baseUrl}${path}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@@ -64,17 +64,17 @@ export class ApiClient {
}
// POST requests
async post<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
async post<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T> {
return this.request<T>('POST', path, body, headers);
}
// PUT requests
async put<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
async put<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T> {
return this.request<T>('PUT', path, body, headers);
}
// PATCH requests
async patch<T>(path: string, body: any, headers?: Record<string, string>): Promise<T> {
async patch<T>(path: string, body: unknown, headers?: Record<string, string>): Promise<T> {
return this.request<T>('PATCH', path, body, headers);
}

View File

@@ -235,7 +235,7 @@ export class DataFactory {
/**
* Clean up specific entities
*/
async deleteEntities(entities: { id: any }[], entityType: string) {
async deleteEntities(entities: { id: string | number }[], entityType: string) {
const repository = this.dataSource.getRepository(entityType);
for (const entity of entities) {
await repository.delete(entity.id);

View File

@@ -65,7 +65,7 @@ export class DatabaseManager {
/**
* Execute query with automatic client management
*/
async query(text: string, params?: any[]): Promise<QueryResult> {
async query(text: string, params?: unknown[]): Promise<QueryResult> {
const client = await this.getClient();
return client.query(text, params);
}
@@ -138,8 +138,6 @@ export class DatabaseManager {
* Seed minimal test data
*/
async seedMinimalData(): Promise<void> {
const client = await this.getClient();
// Insert minimal required data for tests
// This will be extended based on test requirements
@@ -164,13 +162,13 @@ export class DatabaseManager {
ORDER BY log_time DESC
`, [since]);
return result.rows.map(r => r.message);
return (result.rows as { message: string }[]).map(r => r.message);
}
/**
* Get table constraints
*/
async getTableConstraints(tableName: string): Promise<any[]> {
async getTableConstraints(tableName: string): Promise<unknown[]> {
const client = await this.getClient();
const result = await client.query(`

View File

@@ -155,26 +155,27 @@ export class IntegrationTestHarness {
* Helper to verify constraint violations
*/
async expectConstraintViolation(
operation: () => Promise<any>,
operation: () => Promise<unknown>,
expectedConstraint?: string
): Promise<void> {
try {
await operation();
throw new Error('Expected constraint violation but operation succeeded');
} catch (error: any) {
} catch (error) {
// Check if it's a constraint violation
const message = error instanceof Error ? error.message : String(error);
const isConstraintError =
error.message?.includes('constraint') ||
error.message?.includes('23505') || // Unique violation
error.message?.includes('23503') || // Foreign key violation
error.message?.includes('23514'); // Check violation
message.includes('constraint') ||
message.includes('23505') || // Unique violation
message.includes('23503') || // Foreign key violation
message.includes('23514'); // Check violation
if (!isConstraintError) {
throw new Error(`Expected constraint violation but got: ${error.message}`);
throw new Error(`Expected constraint violation but got: ${message}`);
}
if (expectedConstraint && !error.message.includes(expectedConstraint)) {
throw new Error(`Expected constraint '${expectedConstraint}' but got: ${error.message}`);
if (expectedConstraint && !message.includes(expectedConstraint)) {
throw new Error(`Expected constraint '${expectedConstraint}' but got: ${message}`);
}
}
}