integration tests
This commit is contained in:
215
tests/integration/harness/index.ts
Normal file
215
tests/integration/harness/index.ts
Normal file
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* 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 });
|
||||
this.factory = new DataFactory(this.database);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<any>,
|
||||
expectedConstraint?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await operation();
|
||||
throw new Error('Expected constraint violation but operation succeeded');
|
||||
} catch (error: any) {
|
||||
// Check if it's a constraint violation
|
||||
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
|
||||
|
||||
if (!isConstraintError) {
|
||||
throw new Error(`Expected constraint violation but got: ${error.message}`);
|
||||
}
|
||||
|
||||
if (expectedConstraint && !error.message.includes(expectedConstraint)) {
|
||||
throw new Error(`Expected constraint '${expectedConstraint}' but got: ${error.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);
|
||||
}
|
||||
Reference in New Issue
Block a user