datahaven/test/framework/connectors.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

103 lines
2.8 KiB
TypeScript

import { datahaven } from "@polkadot-api/descriptors";
import { createClient as createPapiClient, type PolkadotClient } from "polkadot-api";
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
import { getWsProvider } from "polkadot-api/ws-provider/web";
import { ANVIL_FUNDED_ACCOUNTS, type DataHavenApi, logger } from "utils";
import {
type Account,
createPublicClient,
createWalletClient,
http,
type PublicClient,
type WalletClient
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { anvil } from "viem/chains";
import type { LaunchNetworkResult } from "../launcher";
export interface TestConnectors {
// Ethereum connectors
publicClient: PublicClient;
walletClient: WalletClient<any, any, Account>;
// DataHaven connectors
papiClient: PolkadotClient;
dhApi: DataHavenApi;
// Raw URLs
elRpcUrl: string;
dhRpcUrl: string;
}
export class ConnectorFactory {
private connectors: LaunchNetworkResult;
constructor(connectors: LaunchNetworkResult) {
this.connectors = connectors;
}
/**
* Create test connectors for interacting with the launched networks
*/
async createTestConnectors(): Promise<TestConnectors> {
logger.debug("Creating test connectors...");
// Create Ethereum clients
const publicClient = createPublicClient({
chain: anvil,
transport: http(this.connectors.ethereumRpcUrl)
});
const account = privateKeyToAccount(ANVIL_FUNDED_ACCOUNTS[0].privateKey);
const walletClient = createWalletClient({
account,
chain: anvil,
transport: http(this.connectors.ethereumRpcUrl)
});
// Create DataHaven/Substrate clients
// Note: polkadot-api can handle HTTP RPC URLs even when passed to getWsProvider
const wsProvider = getWsProvider(this.connectors.dataHavenRpcUrl);
const papiClient = createPapiClient(withPolkadotSdkCompat(wsProvider));
// Get typed API
const dhApi = papiClient.getTypedApi(datahaven);
logger.debug("Test connectors created successfully");
return {
publicClient,
walletClient,
papiClient,
dhApi,
elRpcUrl: this.connectors.ethereumRpcUrl,
dhRpcUrl: this.connectors.dataHavenRpcUrl
};
}
/**
* Create a wallet client with a specific account
*/
createWalletClient(privateKey: `0x${string}`): WalletClient<any, any, Account> {
const account = privateKeyToAccount(privateKey);
return createWalletClient({
account,
chain: anvil,
transport: http(this.connectors.ethereumRpcUrl)
});
}
/**
* Clean up connections
*/
async cleanup(connectors: TestConnectors): Promise<void> {
logger.debug("Cleaning up test connectors...");
// Destroy PAPI client
if (connectors.papiClient) {
connectors.papiClient.destroy();
}
logger.debug("Test connectors cleaned up");
}
}