datahaven/test/e2e/framework/connectors.ts
Steve Degosserie 17c215d047
refactor(test): reorganize e2e test suites (#373)
## Summary

Reorganizes the test directory structure for better clarity and
maintainability:

- **Rename `test/datahaven/` → `test/moonwall/`**: Clearly identifies
these as Moonwall single-node tests
- **Move `test/framework/` → `test/e2e/framework/`**: Groups e2e test
utilities under a dedicated folder
- **Move `test/suites/` → `test/e2e/suites/`**: Groups e2e test suites
with the framework
- **Add `test/e2e/framework/validators.ts`**: Extracts validator test
helpers from utils into the e2e framework
- **Update documentation**: README.md and E2E_FRAMEWORK_OVERVIEW.md
reflect the new structure

### New Directory Structure

```
test/
├── e2e/
│   ├── suites/          # E2E test suites (Kurtosis-based)
│   └── framework/       # E2E test utilities & helpers
├── moonwall/            # Moonwall single-node tests
├── launcher/            # Network deployment tools
└── ...
```

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-20 15:52:33 +02:00

146 lines
4.3 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/node";
import { ANVIL_FUNDED_ACCOUNTS, type DataHavenApi, logger } from "utils";
import {
type Account,
createPublicClient,
createWalletClient,
fallback,
http,
type PublicClient,
type WalletClient,
webSocket
} from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { anvil } from "viem/chains";
import { socketClientCache } from "viem/utils";
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...");
// Prefer WebSocket for event-heavy public client; fall back to HTTP when WS is unavailable.
const wsUrl = this.connectors.ethereumWsUrl;
const publicTransport = wsUrl?.startsWith("ws")
? fallback([webSocket(wsUrl), http(this.connectors.ethereumRpcUrl)])
: http(this.connectors.ethereumRpcUrl);
// Create Ethereum clients
const publicClient = createPublicClient({
chain: anvil,
transport: publicTransport
});
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...");
// Close any cached WebSocket clients used by viem to prevent reconnect noise after teardown.
try {
for (const client of socketClientCache.values()) {
try {
client.close();
} catch {
// Ignore individual socket close errors
}
}
socketClientCache.clear();
} catch {
// Ignore cache errors during cleanup
}
// Destroy PAPI client
if (connectors.papiClient) {
try {
connectors.papiClient.destroy();
} catch (error) {
// Ignore DisjointError - it occurs when ChainHead subscriptions are already disjointed
// This is harmless and expected during cleanup
const errorName = error instanceof Error ? error.name : String(error);
const errorMessage = error instanceof Error ? error.message : String(error);
if (
errorName === "DisjointError" ||
errorName.includes("disjoint") ||
errorMessage.includes("disjoint") ||
errorMessage.includes("ChainHead disjointed")
) {
// Ignore - this is expected and harmless
} else {
// Re-throw unexpected errors
throw error;
}
}
}
logger.debug("Test connectors cleaned up");
}
}