mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## Implement E2E Testing Framework with Isolated Networks ### Summary Refactors the existing E2E testing infrastructure to provide isolated test environments with parallel execution support. Each test suite now runs in its own network namespace, preventing resource conflicts. ### Key Changes - **New Testing Framework** (`test/framework/`): Base classes for test lifecycle management with automatic setup/teardown - **Launcher Module** (`test/launcher/`): Extracted network orchestration logic from CLI handlers for reusability - **Parallel Execution**: Added `test-parallel.ts` script with concurrency limits to prevent resource exhaustion - **Test Isolation**: Each suite gets unique network IDs (format: `suiteName-timestamp`) and Docker networks - **Improved Test Organization**: Migrated tests to new framework, deprecated old test structure ### Test Improvements - Added 4 new test suites demonstrating framework usage. : - `contracts.test.ts` - Smart contract deployment/interaction - `datahaven-substrate.test.ts` - Substrate API operations - `cross-chain.test.ts` - Snowbridge cross-chain messaging - `ethereum-basic.test.ts` - Ethereum network operations > [!WARNING] The test suites themselves are bad and shouldn't be consider examples of good tests. They were AI generated just to test the concurrency of test runners ### Documentation - Added comprehensive framework overview (`E2E_FRAMEWORK_OVERVIEW.md`) - Updated README with parallel testing commands - Added test patterns and best practices ### Breaking Changes - Old test suites moved to `e2e - DEPRECATED/` directory - Test execution now requires extending `BaseTestSuite` class ### Testing Run tests with: `bun test:e2e` or `bun test:e2e:parallel` (with concurrency limits) ### TODO - [ ] Implement good test examples. - [ ] Implement useful test utils (like waiting for an event to show up in DataHaven or Ethereum). - [ ] Enforce tests with CI (currently cannot be done due to intermittent error when sending a transaction with PAPI). --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: undercover-cactus <lola@moonsonglabs.com>
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
import { logger } from "utils";
|
|
import type { LaunchedNetwork } from "../../../launcher/types/launchedNetwork";
|
|
|
|
/**
|
|
* Forwards a port from a Kubernetes service to localhost and returns a cleanup function.
|
|
*
|
|
* @param serviceName - The name of the Kubernetes service to forward from
|
|
* @param localPort - The local port to bind to
|
|
* @param kubePort - The Kubernetes service port to forward from
|
|
* @param launchedNetwork - The launched network instance containing namespace info
|
|
* @param options - Optional configuration
|
|
* @returns Promise<{ cleanup: () => Promise<void> }> - Object containing cleanup function
|
|
*/
|
|
export const forwardPort = async (
|
|
serviceName: string,
|
|
localPort: number,
|
|
kubePort: number,
|
|
launchedNetwork: LaunchedNetwork
|
|
): Promise<{ cleanup: () => Promise<void> }> => {
|
|
logger.info(
|
|
`🔗 Setting up port forward: localhost:${localPort} -> svc/dh-validator-0:${kubePort}`
|
|
);
|
|
|
|
// Start kubectl port-forward as a background process using Bun.spawn
|
|
const portForwardProcess = Bun.spawn(
|
|
[
|
|
"kubectl",
|
|
"port-forward",
|
|
`svc/${serviceName}`,
|
|
"-n",
|
|
launchedNetwork.kubeNamespace,
|
|
`${localPort}:${kubePort}`
|
|
],
|
|
{
|
|
stdout: "pipe",
|
|
stderr: "pipe"
|
|
}
|
|
);
|
|
|
|
// Check if the process is still running (didn't exit due to error)
|
|
if (portForwardProcess.exitCode !== null) {
|
|
const stderr = await new Response(portForwardProcess.stderr).text();
|
|
throw new Error(`Port forward failed to start: ${stderr}`);
|
|
}
|
|
|
|
logger.success(
|
|
`Port forward established: localhost:${localPort} -> svc/dh-validator-0:${kubePort}`
|
|
);
|
|
|
|
// Return cleanup function
|
|
const cleanup = async (): Promise<void> => {
|
|
logger.info(`🧹 Cleaning up port forward for localhost:${localPort}`);
|
|
|
|
if (!portForwardProcess.killed) {
|
|
portForwardProcess.kill();
|
|
|
|
// Wait for process to actually exit
|
|
try {
|
|
await portForwardProcess.exited;
|
|
} catch (error) {
|
|
// Process was killed, this is expected
|
|
logger.debug(`Port forward process killed: ${error}`);
|
|
}
|
|
}
|
|
|
|
logger.success(`Port forward cleanup completed for localhost:${localPort}`);
|
|
};
|
|
|
|
// Add a cleanup handler that doesn't interfere with exit codes
|
|
const exitHandler = () => {
|
|
if (!portForwardProcess.killed) {
|
|
portForwardProcess.kill();
|
|
}
|
|
};
|
|
|
|
process.on("exit", exitHandler);
|
|
process.on("SIGINT", exitHandler);
|
|
process.on("SIGTERM", exitHandler);
|
|
|
|
return { cleanup };
|
|
};
|