feat: Add functional health checks for test prerequisites (#65)
Replaces basic "service is up" checks with functional verification that tests can actually use the services. ## Changes ### New Health Checks - **RPC Proxy**: Verifies eth_call works and deployed contracts are accessible - **GraphQL**: Confirms Ponder has indexed data with non-zero stats - **Web App**: Validates endpoint accessibility ### Improvements - Clear error messages explain what failed and how to fix it - Checks verify actual functionality, not just HTTP 200 responses - Fails fast before tests run with cryptic errors ### Files - `tests/setup/health-checks.ts` - Core health check functions - `tests/setup/stack.ts` - Integration with waitForStackReady() - `tests/HEALTH_CHECKS.md` - Documentation and troubleshooting guide ## Error Message Example Before: ``` RPC health check failed with status 404 ``` After: ``` ❌ Stack health check failed Failed services: • RPC Proxy: RPC proxy returned HTTP 404 Expected 200, got 404. Check if Anvil is running and RPC_URL is correct. • GraphQL Indexer: GraphQL has no indexed data yet Ponder is running but has not indexed contract events. Troubleshooting: 1. Check stack logs: tail tests/.stack.log 2. Verify services are running: ./scripts/dev.sh status 3. Restart stack: ./scripts/dev.sh restart --full ``` ## Benefits - ✅ Tests fail fast with clear error messages - ✅ Catches configuration issues before tests run - ✅ Verifies services are actually usable, not just running resolves #61 Co-authored-by: johba <johba@harb.eth> Reviewed-on: https://codeberg.org/johba/harb/pulls/65
This commit is contained in:
parent
1645865c5a
commit
b1f40374cd
3 changed files with 472 additions and 42 deletions
|
|
@ -3,6 +3,11 @@ import { readFile } from 'node:fs/promises';
|
|||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { promisify } from 'node:util';
|
||||
import {
|
||||
runAllHealthChecks,
|
||||
formatHealthCheckError,
|
||||
type HealthCheckResult,
|
||||
} from './health-checks.js';
|
||||
|
||||
const exec = promisify(execCallback);
|
||||
|
||||
|
|
@ -33,44 +38,19 @@ async function run(command: string): Promise<void> {
|
|||
await exec(command, { cwd: repoRoot, shell: true });
|
||||
}
|
||||
|
||||
async function checkRpcReady(rpcUrl: string): Promise<void> {
|
||||
const response = await fetch(rpcUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_chainId', params: [] }),
|
||||
});
|
||||
/**
|
||||
* Run functional health checks that verify services are actually usable, not just running
|
||||
*/
|
||||
async function checkStackFunctional(
|
||||
rpcUrl: string,
|
||||
webAppUrl: string,
|
||||
graphqlUrl: string
|
||||
): Promise<void> {
|
||||
const results = await runAllHealthChecks({ rpcUrl, webAppUrl, graphqlUrl });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`RPC health check failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const payload = await response.json();
|
||||
if (!payload?.result) {
|
||||
throw new Error('RPC health check returned no result');
|
||||
}
|
||||
}
|
||||
|
||||
async function checkWebAppReady(webAppUrl: string): Promise<void> {
|
||||
const url = webAppUrl.endsWith('/') ? `${webAppUrl}app/` : `${webAppUrl}/app/`;
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
redirect: 'manual',
|
||||
});
|
||||
|
||||
if (!response.ok && response.status !== 308) {
|
||||
throw new Error(`Web app health check failed with status ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function checkGraphqlReady(graphqlUrl: string): Promise<void> {
|
||||
const response = await fetch(graphqlUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ query: '{ __typename }' }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`GraphQL health check failed with status ${response.status}`);
|
||||
const failures = results.filter(r => !r.success);
|
||||
if (failures.length > 0) {
|
||||
throw new Error(formatHealthCheckError(results));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,11 +83,8 @@ export async function waitForStackReady(options: {
|
|||
|
||||
while (Date.now() - start < timeoutMs) {
|
||||
try {
|
||||
await Promise.all([
|
||||
checkRpcReady(rpcUrl),
|
||||
checkWebAppReady(webAppUrl),
|
||||
checkGraphqlReady(graphqlUrl),
|
||||
]);
|
||||
await checkStackFunctional(rpcUrl, webAppUrl, graphqlUrl);
|
||||
console.log('✅ All stack health checks passed');
|
||||
return;
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue