Files
gridpilot.gg/tests/integration/harness/index.ts
Marc Mintel a0f41f242f
Some checks failed
CI / lint-typecheck (pull_request) Failing after 4m51s
CI / tests (pull_request) Has been skipped
CI / contract-tests (pull_request) Has been skipped
CI / e2e-tests (pull_request) Has been skipped
CI / comment-pr (pull_request) Has been skipped
CI / commit-types (pull_request) Has been skipped
integration tests
2026-01-23 00:46:34 +01:00

219 lines
5.6 KiB
TypeScript

/**
* Integration Test Harness - Main Entry Point
* Provides reusable setup, teardown, and utilities for integration tests
*/
import { DockerManager } from './docker-manager';
import { DatabaseManager } from './database-manager';
import { ApiClient } from './api-client';
import { DataFactory } from './data-factory';
export interface IntegrationTestConfig {
api: {
baseUrl: string;
port: number;
};
database: {
host: string;
port: number;
database: string;
user: string;
password: string;
};
timeouts?: {
setup?: number;
teardown?: number;
test?: number;
};
}
export class IntegrationTestHarness {
private docker: DockerManager;
private database: DatabaseManager;
private api: ApiClient;
private factory: DataFactory;
private config: IntegrationTestConfig;
constructor(config: IntegrationTestConfig) {
this.config = {
timeouts: {
setup: 120000,
teardown: 30000,
test: 60000,
...config.timeouts,
},
...config,
};
this.docker = DockerManager.getInstance();
this.database = new DatabaseManager(config.database);
this.api = new ApiClient({ baseUrl: config.api.baseUrl, timeout: 60000 });
const { host, port, database, user, password } = config.database;
const dbUrl = `postgresql://${user}:${password}@${host}:${port}/${database}`;
this.factory = new DataFactory(dbUrl);
}
/**
* Setup hook - starts Docker services and prepares database
* Called once before all tests in a suite
*/
async beforeAll(): Promise<void> {
console.log('[Harness] Starting integration test setup...');
// Start Docker services
await this.docker.start();
// Wait for database to be ready
await this.database.waitForReady(this.config.timeouts?.setup);
// Wait for API to be ready
await this.api.waitForReady(this.config.timeouts?.setup);
console.log('[Harness] ✓ Setup complete - all services ready');
}
/**
* Teardown hook - stops Docker services and cleans up
* Called once after all tests in a suite
*/
async afterAll(): Promise<void> {
console.log('[Harness] Starting integration test teardown...');
try {
await this.database.close();
this.docker.stop();
console.log('[Harness] ✓ Teardown complete');
} catch (error) {
console.warn('[Harness] Teardown warning:', error);
}
}
/**
* Setup hook - prepares database for each test
* Called before each test
*/
async beforeEach(): Promise<void> {
// Truncate all tables to ensure clean state
await this.database.truncateAllTables();
// Optionally seed minimal required data
// await this.database.seedMinimalData();
}
/**
* Teardown hook - cleanup after each test
* Called after each test
*/
async afterEach(): Promise<void> {
// Clean up any test-specific resources
// This can be extended by individual tests
}
/**
* Get database manager
*/
getDatabase(): DatabaseManager {
return this.database;
}
/**
* Get API client
*/
getApi(): ApiClient {
return this.api;
}
/**
* Get Docker manager
*/
getDocker(): DockerManager {
return this.docker;
}
/**
* Get data factory
*/
getFactory(): DataFactory {
return this.factory;
}
/**
* Execute database transaction with automatic rollback
* Useful for tests that need to verify transaction behavior
*/
async withTransaction<T>(callback: (db: DatabaseManager) => Promise<T>): Promise<T> {
await this.database.begin();
try {
const result = await callback(this.database);
await this.database.rollback(); // Always rollback in tests
return result;
} catch (error) {
await this.database.rollback();
throw error;
}
}
/**
* Helper to verify constraint violations
*/
async expectConstraintViolation(
operation: () => Promise<unknown>,
expectedConstraint?: string
): Promise<void> {
try {
await operation();
throw new Error('Expected constraint violation but operation succeeded');
} catch (error) {
// Check if it's a constraint violation
const message = error instanceof Error ? error.message : String(error);
const isConstraintError =
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: ${message}`);
}
if (expectedConstraint && !message.includes(expectedConstraint)) {
throw new Error(`Expected constraint '${expectedConstraint}' but got: ${message}`);
}
}
}
}
// Default configuration for docker-compose.test.yml
export const DEFAULT_TEST_CONFIG: IntegrationTestConfig = {
api: {
baseUrl: 'http://localhost:3101',
port: 3101,
},
database: {
host: 'localhost',
port: 5433,
database: 'gridpilot_test',
user: 'gridpilot_test_user',
password: 'gridpilot_test_pass',
},
timeouts: {
setup: 120000,
teardown: 30000,
test: 60000,
},
};
/**
* Create a test harness with default configuration
*/
export function createTestHarness(config?: Partial<IntegrationTestConfig>): IntegrationTestHarness {
const mergedConfig = {
...DEFAULT_TEST_CONFIG,
...config,
api: { ...DEFAULT_TEST_CONFIG.api, ...config?.api },
database: { ...DEFAULT_TEST_CONFIG.database, ...config?.database },
timeouts: { ...DEFAULT_TEST_CONFIG.timeouts, ...config?.timeouts },
};
return new IntegrationTestHarness(mergedConfig);
}