Files
universe/e2e/33-bounty-bot.spec.ts
zeekayandHanzo Dev 1e40737310 test(e2e): dao onchain / vote / work / bounty / pars coverage
Regression-test the full DAO/governance/work-market surface across the live
orgs, read-only (no txs/writes/signing), asserting REAL functionality:

- 30-dao-onchain: BountyV1/ReputationV1/Governor/Safe on Zoo 200200 + Pars
  494949 via eth_call/eth_getCode — code present, bountyCount>=1, stateOf(0)==
  Paid(6), treasury()==org DAO Safe, ReputationV1.writer()==BountyV1 (wiring).
  Lux 96369: canonical CREATE2 Safe suite present, dead Governor asserted 0-byte
  (staged post-flag-day). Hanzo 36963 skip-gated until deploy lands.
- 31-vote-surfaces: lux/zoo/hanzo/pars.vote render + correct brand + ZERO cross-
  brand/competitor leakage + no white-screen (#root descendants + body text) +
  Connect Wallet present. reads-chain gated skip-until-live (Decent-fork shells).
- 32-work-boards: work.zoo.network LIVE badge (green, not FIXTURE), bounty in
  Paid column, on-chain ReputationV1 block, UI bounty count == chain bountyCount.
  work.{lux,hanzo,pars} skip-until-live.
- 33-bounty-bot: bounties.zoo.network webhook routed+POST-only (405), unsigned
  POST rejected (401), healthz internal-scoped (404 public correct). No secrets.
- 34-pars-chain: Pars 494949 chainId + monotonic height + explorer renders.

helpers.ts: additive ethCall/ethGetCode/hasCode/decodeAddress/encodeUint +
HANZO/PARS API domains + orgRpc() (one way to resolve any org's C-Chain RPC).
17-governance: drop dead unused SELECTORS block (on-chain contract asserts now
own their home in 30-dao-onchain). package.json: wire specs into test:full +
per-spec scripts.

36 active tests pass live, 8 skip-gated (ready to flip). Suite stays GREEN.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 15:46:03 -07:00

52 lines
2.7 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { httpStatus } from './helpers'
// ---------------------------------------------------------------------------
// Bounty bot — bounties.zoo.network (GitHub → BountyV1 automation service)
// ---------------------------------------------------------------------------
// Read-only liveness probes. No secrets are sent or asserted. We assert the
// service is ROUTED and ALIVE by its method/scope contract, not by a 200:
// • GET /v1/github/webhook → 405 — the route exists but is POST-only, so a GET
// is Method-Not-Allowed. This proves the handler is wired and reachable.
// • POST /v1/github/webhook (unsigned) → 401 — the handler runs and REJECTS an
// unauthenticated delivery (HMAC signature missing). Proves it's alive and
// gated, without needing or revealing the webhook secret.
// • GET /v1/healthz → 404 — healthz is internal-scoped (cluster probe only), so
// a 404 from the public edge is the CORRECT, expected answer. A 200 here would
// mean the internal health surface leaked publicly.
const BASE = 'https://bounties.zoo.network'
test.describe('Bounty Bot — bounties.zoo.network', () => {
test('GET /v1/github/webhook is routed and POST-only (405)', async ({ request }) => {
const status = await httpStatus(request, `${BASE}/v1/github/webhook`)
console.log(` GET /v1/github/webhook → ${status} (expected 405, POST-only)`)
expect(status).toBe(405)
})
test('unsigned POST /v1/github/webhook is rejected (401), handler alive', async ({ request }) => {
// Empty body, no X-Hub-Signature-256 — the service must refuse it.
const res = await request.post(`${BASE}/v1/github/webhook`, {
data: {},
headers: { 'Content-Type': 'application/json' },
ignoreHTTPSErrors: true,
timeout: 15_000,
})
const status = res.status()
console.log(` POST /v1/github/webhook (unsigned) → ${status}`)
// 401 today (signature required). Accept the auth-reject family so a tightened
// gate (403) or a body-shape reject (400) still passes — never a 2xx (which
// would mean an unauthenticated delivery was accepted).
expect([400, 401, 403]).toContain(status)
expect(status).toBeLessThan(500)
})
test('GET /v1/healthz is internal-scoped (404 public is correct)', async ({ request }) => {
const status = await httpStatus(request, `${BASE}/v1/healthz`)
console.log(` GET /v1/healthz → ${status} (expected 404 — internal-scoped, not public)`)
// 404 is the CORRECT public answer: healthz serves cluster probes only. If this
// ever returns 200 the internal health surface has been exposed publicly.
expect(status).toBe(404)
})
})