- IAM OIDC: the shared Hanzo IAM issues under its canonical login host (issuer = https://hanzo.id), so assert the issuer contains "hanzo" rather than the backend hostname "iam.hanzo.ai". - Commerce health lives at /health (200); /healthz 404s — fix the path. - Bridge frontend is an SPA whose catch-all returns 200 (HTML) for any path, which fooled the "supported chains" probe into JSON-parsing HTML. Guard the parse so a non-JSON 200 falls through to the frontend-up fallback. - security-swap: three flow tests called gotoSwap() without the try/catch the sibling tests use, so they hard-failed on lux.exchange's TLS error instead of self-pending. Wrap them to complete the file's existing graceful-degradation pattern (the exchange-down defect stays surfaced by 02-exchange). Co-authored-by: Hanzo Dev <[email protected]>
183 lines
7.2 KiB
TypeScript
183 lines
7.2 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import {
|
|
getUrls,
|
|
rpcCall,
|
|
httpStatus, httpGetJson,
|
|
snap,
|
|
} from './helpers'
|
|
|
|
const urls = getUrls()
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Bridge + MPC Infrastructure — deeper checks beyond basic reachability
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// ── MPC Dashboard ──────────────────────────────────────────────────
|
|
test.describe('Bridge MPC — Dashboard', () => {
|
|
test('MPC dashboard loads with valid HTML', async ({ request, page }) => {
|
|
const status = await httpStatus(request, urls.mpc)
|
|
if (status === 0) {
|
|
test.skip(true, 'MPC dashboard unreachable')
|
|
}
|
|
const res = await page.goto(urls.mpc, { timeout: 60_000, waitUntil: 'domcontentloaded' })
|
|
const pageStatus = res?.status() || 0
|
|
console.log(` MPC dashboard HTTP: ${pageStatus}`)
|
|
expect(pageStatus).toBeLessThan(500)
|
|
const title = await page.title()
|
|
expect(title.length).toBeGreaterThan(0)
|
|
await snap(page, 'mpc-dashboard')
|
|
})
|
|
})
|
|
|
|
// ── MPC API Health ─────────────────────────────────────────────────
|
|
test.describe('Bridge MPC — API Health', () => {
|
|
test('MPC API health endpoint responds', async ({ request }) => {
|
|
// Try common health paths
|
|
const paths = [
|
|
`${urls.mpc}/api/health`,
|
|
`${urls.mpc}/health`,
|
|
`${urls.mpc}/api/v1/health`,
|
|
`${urls.mpc}/api/status`,
|
|
]
|
|
let responded = false
|
|
for (const path of paths) {
|
|
const status = await httpStatus(request, path)
|
|
if (status > 0 && status < 500) {
|
|
console.log(` MPC API health: ${path} -> ${status}`)
|
|
responded = true
|
|
break
|
|
}
|
|
}
|
|
if (!responded) {
|
|
console.log(' No MPC health endpoint found (checking base URL)')
|
|
const baseStatus = await httpStatus(request, urls.mpc)
|
|
console.log(` MPC base: ${baseStatus}`)
|
|
expect(baseStatus).toBeGreaterThan(0)
|
|
}
|
|
})
|
|
|
|
test('MPC API returns node info if available', async ({ request }) => {
|
|
const paths = [
|
|
`${urls.mpc}/api/v1/nodes`,
|
|
`${urls.mpc}/api/nodes`,
|
|
`${urls.mpc}/api/v1/info`,
|
|
]
|
|
for (const path of paths) {
|
|
const status = await httpStatus(request, path)
|
|
if (status === 200) {
|
|
const data = await httpGetJson(request, path)
|
|
console.log(` MPC nodes endpoint: ${path}`)
|
|
console.log(` Response: ${JSON.stringify(data).slice(0, 200)}`)
|
|
return
|
|
}
|
|
}
|
|
console.log(' MPC node info endpoint not found (service may be auth-protected)')
|
|
// MPC service being live is sufficient
|
|
const baseStatus = await httpStatus(request, urls.mpc)
|
|
expect(baseStatus).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
// ── Bridge API Endpoints ───────────────────────────────────────────
|
|
test.describe('Bridge MPC — Bridge API', () => {
|
|
test('bridge API responds to supported chains query', async ({ request }) => {
|
|
const paths = [
|
|
`${urls.bridge}/api/v1/chains`,
|
|
`${urls.bridge}/api/v1/supported-chains`,
|
|
`${urls.bridge}/api/chains`,
|
|
]
|
|
for (const path of paths) {
|
|
const status = await httpStatus(request, path)
|
|
if (status === 200) {
|
|
try {
|
|
const data = await httpGetJson(request, path)
|
|
console.log(` Supported chains endpoint: ${path}`)
|
|
console.log(` Response: ${JSON.stringify(data).slice(0, 200)}`)
|
|
return
|
|
} catch {
|
|
// 200 but non-JSON (SPA catch-all) — not a real API endpoint.
|
|
}
|
|
}
|
|
}
|
|
console.log(' No bridge chains endpoint found (bridge may not expose chain list)')
|
|
// Bridge frontend being up is sufficient
|
|
const baseStatus = await httpStatus(request, urls.bridge)
|
|
expect(baseStatus).toBeGreaterThan(0)
|
|
})
|
|
|
|
test('bridge fee estimation endpoint', async ({ request }) => {
|
|
const paths = [
|
|
`${urls.bridge}/api/v1/fees`,
|
|
`${urls.bridge}/api/v1/estimate`,
|
|
`${urls.bridge}/api/fees`,
|
|
]
|
|
for (const path of paths) {
|
|
const status = await httpStatus(request, path)
|
|
if (status > 0 && status < 500) {
|
|
console.log(` Bridge fee endpoint: ${path} -> ${status}`)
|
|
return
|
|
}
|
|
}
|
|
console.log(' No bridge fee endpoint found (fee estimation may be client-side)')
|
|
const baseStatus = await httpStatus(request, urls.bridge)
|
|
expect(baseStatus).toBeGreaterThan(0)
|
|
})
|
|
})
|
|
|
|
// ── Bridge Contract Verification ───────────────────────────────────
|
|
test.describe('Bridge MPC — Bridge Contracts on C-Chain', () => {
|
|
// Common bridge precompile / contract addresses
|
|
const NATIVE_MINTER = '0x0200000000000000000000000000000000000001'
|
|
const TX_ALLOW_LIST = '0x0200000000000000000000000000000000000002'
|
|
const FEE_MANAGER = '0x0200000000000000000000000000000000000003'
|
|
|
|
test('NativeMinter precompile is accessible', async ({ request }) => {
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
NATIVE_MINTER, 'latest',
|
|
]) as string
|
|
console.log(` NativeMinter (${NATIVE_MINTER}): code=${code.slice(0, 20)}${code.length > 20 ? '...' : ''}`)
|
|
// Precompile may return 0x (not deployed) or have code
|
|
expect(code).toBeDefined()
|
|
})
|
|
|
|
test('FeeManager precompile is accessible', async ({ request }) => {
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
FEE_MANAGER, 'latest',
|
|
]) as string
|
|
console.log(` FeeManager (${FEE_MANAGER}): code=${code.slice(0, 20)}${code.length > 20 ? '...' : ''}`)
|
|
expect(code).toBeDefined()
|
|
})
|
|
|
|
test('TxAllowList precompile is accessible', async ({ request }) => {
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
TX_ALLOW_LIST, 'latest',
|
|
]) as string
|
|
console.log(` TxAllowList (${TX_ALLOW_LIST}): code=${code.slice(0, 20)}${code.length > 20 ? '...' : ''}`)
|
|
expect(code).toBeDefined()
|
|
})
|
|
})
|
|
|
|
// ── Cross-Chain Asset Verification ─────────────────────────────────
|
|
test.describe('Bridge MPC — Cross-Chain Asset Verification', () => {
|
|
test('C-Chain native asset has balance at genesis', async ({ request }) => {
|
|
// Treasury or burn address should have non-zero LUX
|
|
const result = await rpcCall(request, urls.rpc, 'eth_getBalance', [
|
|
'0x0100000000000000000000000000000000000000', 'latest',
|
|
]) as string
|
|
const balance = BigInt(result)
|
|
console.log(` Balance at 0x01..00: ${balance}`)
|
|
// Just confirm the query works
|
|
expect(result).toMatch(/^0x/)
|
|
})
|
|
|
|
test('warp messenger precompile exists', async ({ request }) => {
|
|
// Warp precompile for cross-chain messaging
|
|
const WARP_PRECOMPILE = '0x0200000000000000000000000000000000000005'
|
|
const code = await rpcCall(request, urls.rpc, 'eth_getCode', [
|
|
WARP_PRECOMPILE, 'latest',
|
|
]) as string
|
|
console.log(` Warp messenger (${WARP_PRECOMPILE}): code=${code.slice(0, 20)}${code.length > 20 ? '...' : ''}`)
|
|
expect(code).toBeDefined()
|
|
})
|
|
})
|