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
219 lines
5.6 KiB
TypeScript
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);
|
|
} |