Merge pull request 'fix: tests/setup/stack.ts: contract addresses have no env var override path (#391)' (#411) from fix/issue-391 into master

This commit is contained in:
johba 2026-03-03 00:04:04 +01:00
commit 10643618f7
3 changed files with 74 additions and 21 deletions

View file

@ -95,6 +95,22 @@ For testing login: `lobsterDao`, `test123`, `lobster-x010syqe?412!`
After bootstrap, addresses are in `/home/debian/harb/tmp/containers/contracts.env`.
Landing sources this file on startup for `VITE_KRAIKEN_ADDRESS` and `VITE_STAKE_ADDRESS`.
## E2E Test Environment Variables
The Playwright test setup (`tests/setup/stack.ts`) reads stack coordinates from env vars, falling back to `onchain/deployments-local.json` when they are absent.
| Variable | Purpose |
|---|---|
| `STACK_RPC_URL` | RPC endpoint (default: `http://localhost:8081/api/rpc`) |
| `STACK_WEBAPP_URL` | Web app base URL (default: `http://localhost:8081`) |
| `STACK_GRAPHQL_URL` | GraphQL endpoint (default: `http://localhost:8081/api/graphql`) |
| `STACK_KRAIKEN_ADDRESS` | Kraiken contract address (overrides deployments-local.json) |
| `STACK_STAKE_ADDRESS` | Stake contract address (overrides deployments-local.json) |
| `STACK_LM_ADDRESS` | LiquidityManager contract address (overrides deployments-local.json) |
| `STACK_OPTIMIZER_PROXY_ADDRESS` | OptimizerProxy address (optional; enables optimizer integration tests) |
When all three of `STACK_KRAIKEN_ADDRESS`, `STACK_STAKE_ADDRESS`, and `STACK_LM_ADDRESS` are set, the deployments file is not read at all, which allows tests to run in containerised environments that have no local checkout.
## Playwright Testing
```bash

View file

@ -18,8 +18,12 @@ export interface HealthCheckResult {
* 1. Checking eth_chainId returns the expected chain
* 2. Making an eth_call to verify contract accessibility
* 3. Confirming deployed contracts are accessible
*
* @param kraikenAddress - Optional Kraiken contract address. When provided the
* deployments-local.json file is not read, which allows the check to succeed
* in containerised environments that supply addresses via env vars.
*/
export async function checkRpcFunctional(rpcUrl: string): Promise<HealthCheckResult> {
export async function checkRpcFunctional(rpcUrl: string, kraikenAddress?: string): Promise<HealthCheckResult> {
const service = 'RPC Proxy';
try {
@ -54,24 +58,27 @@ export async function checkRpcFunctional(rpcUrl: string): Promise<HealthCheckRes
};
}
// Step 2: Load deployed contracts and verify accessibility
const deploymentsPath = resolve(repoRoot, 'onchain', 'deployments-local.json');
let deployments: { contracts: { Kraiken: string } };
// Step 2: Resolve the Kraiken address for the eth_call probe.
// Use the address supplied by the caller; fall back to deployments-local.json.
let resolvedKraikenAddress = kraikenAddress;
try {
const deploymentsContent = await readFile(deploymentsPath, 'utf-8');
deployments = JSON.parse(deploymentsContent);
} catch (error) {
return {
success: false,
service,
message: 'Failed to load contract deployments',
details: `Could not read ${deploymentsPath}. Run stack setup first.`
};
if (!resolvedKraikenAddress) {
const deploymentsPath = resolve(repoRoot, 'onchain', 'deployments-local.json');
try {
const deploymentsContent = await readFile(deploymentsPath, 'utf-8');
const deployments: { contracts: { Kraiken: string } } = JSON.parse(deploymentsContent);
resolvedKraikenAddress = deployments.contracts.Kraiken;
} catch (error) {
return {
success: false,
service,
message: 'Failed to load contract deployments',
details: `Could not read ${deploymentsPath}. Run stack setup first or set STACK_KRAIKEN_ADDRESS.`
};
}
}
// Step 3: Verify we can make eth_call to deployed Kraiken contract
const kraikenAddress = deployments.contracts.Kraiken;
const totalSupplyCalldata = '0x18160ddd'; // totalSupply() selector
const callResponse = await fetch(rpcUrl, {
@ -82,7 +89,7 @@ export async function checkRpcFunctional(rpcUrl: string): Promise<HealthCheckRes
id: 2,
method: 'eth_call',
params: [{
to: kraikenAddress,
to: resolvedKraikenAddress,
data: totalSupplyCalldata
}, 'latest']
}),
@ -104,7 +111,7 @@ export async function checkRpcFunctional(rpcUrl: string): Promise<HealthCheckRes
success: false,
service,
message: 'Contract call returned RPC error',
details: `${callPayload.error.message}. Kraiken contract at ${kraikenAddress} may not be deployed.`
details: `${callPayload.error.message}. Kraiken contract at ${resolvedKraikenAddress} may not be deployed.`
};
}
@ -271,16 +278,20 @@ export async function checkWebAppAccessible(webAppUrl: string): Promise<HealthCh
}
/**
* Run all functional health checks and return detailed results
* Run all functional health checks and return detailed results.
* Accepts an optional `contracts` field so that the Kraiken address resolved
* by getStackConfig() (including any STACK_KRAIKEN_ADDRESS env var override)
* is reused here, avoiding a redundant file read.
*/
export async function runAllHealthChecks(options: {
rpcUrl: string;
webAppUrl: string;
graphqlUrl: string;
contracts?: { Kraiken: string };
}): Promise<HealthCheckResult[]> {
// Skip GraphQL check temporarily - Ponder crashed but staking still works
const results = await Promise.all([
checkRpcFunctional(options.rpcUrl),
checkRpcFunctional(options.rpcUrl, options.contracts?.Kraiken),
checkWebAppAccessible(options.webAppUrl),
// checkGraphqlIndexed(options.graphqlUrl),
]);

View file

@ -24,18 +24,44 @@ export interface StackConfig {
}
/**
* Load contract addresses from deployments file
* Load contract addresses, preferring env var overrides over the deployments file.
* Env vars: STACK_KRAIKEN_ADDRESS, STACK_STAKE_ADDRESS, STACK_LM_ADDRESS.
* Falls back to onchain/deployments-local.json when env vars are absent.
*/
function loadContractAddresses(): ContractAddresses {
const envKraiken = process.env.STACK_KRAIKEN_ADDRESS;
const envStake = process.env.STACK_STAKE_ADDRESS;
const envLm = process.env.STACK_LM_ADDRESS;
if (envKraiken && envStake && envLm) {
const envOptimizerProxy = process.env.STACK_OPTIMIZER_PROXY_ADDRESS;
return {
Kraiken: envKraiken,
Stake: envStake,
LiquidityManager: envLm,
...(envOptimizerProxy !== undefined ? { OptimizerProxy: envOptimizerProxy } : {}),
};
}
let fileContracts!: ContractAddresses;
try {
const deploymentsPath = join(process.cwd(), 'onchain', 'deployments-local.json');
const deploymentsJson = readFileSync(deploymentsPath, 'utf-8');
const deployments = JSON.parse(deploymentsJson);
return deployments.contracts;
fileContracts = deployments.contracts;
} catch (error) {
console.error('Failed to load contract addresses from deployments-local.json:', error);
throw new Error('Cannot run tests without deployed contract addresses');
}
return {
Kraiken: envKraiken ?? fileContracts.Kraiken,
Stake: envStake ?? fileContracts.Stake,
LiquidityManager: envLm ?? fileContracts.LiquidityManager,
...(fileContracts.OptimizerProxy !== undefined
? { OptimizerProxy: fileContracts.OptimizerProxy }
: {}),
};
}
/**