datahaven/test/cli/handlers/common/kubernetes.ts
Facundo Farall 9b311e00ef
test: 🏗️ Setup e2e testing framework (#104)
## 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>
2025-07-16 18:51:07 +02:00

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 };
};