{
  "$schema": "https://hyperjs.ai/schema/registry-item.json",
  "name": "testing",
  "version": "0.1.0",
  "description": "Testing helpers for Hyper apps — app.test, fakeRequest, matchers, memory stores, fuzz.",
  "readme": "# @hyper/testing\n\nTesting helpers for Hyper apps — `call`, matchers, memory stores, fuzz.\n\n## Install\n\nComponents are installed as source into your repo, not pulled from npm:\n\n```bash\nbunx hyper add testing\n```\n\nWires the alias `@hyper/testing` to `src/hyper/testing/` (configurable in `hyper.config.json`). See [hyperjs.ai](https://hyperjs.ai) for the full registry.\n\n## Usage\n\n```ts\nimport { Hyper, ok } from \"@hyper/core\"\nimport { assertResponse, call } from \"@hyper/testing\"\n\nconst app = new Hyper().get(\"/\", () => ok({ hello: \"world\" }))\n\nconst res = await call(app, \"GET\", \"/\")\nassertResponse(res).isOk()\n```\n\n`call` accepts both `Hyper` instances and built `HyperApp` values, so the same helper works for unit and integration tests.\n\n## Docs\n\nSee the [main README](../../README.md) and [docs/](../../docs) for guides and integration recipes.\n\n## License\n\nMIT\n",
  "registryDeps": [
    "core"
  ],
  "peerDeps": {},
  "optionalPeerDeps": {},
  "files": [
    {
      "path": "testing/assert.ts",
      "contents": "/**\n * `assertResponse(res)` — fluent matcher that integrates with bun:test\n * expect failures.\n *\n * Each matcher returns `this` for chaining. On mismatch we throw with a\n * descriptive message; bun:test surfaces the throw as a failed expect.\n */\n\n/** Minimal subset of match semantics we need — deep partial equality. */\nfunction matches(actual: unknown, expected: unknown): boolean {\n  if (expected instanceof RegExp) return typeof actual === \"string\" && expected.test(actual)\n  if (Array.isArray(expected)) {\n    if (!Array.isArray(actual) || actual.length < expected.length) return false\n    return expected.every((e, i) => matches(actual[i], e))\n  }\n  if (expected && typeof expected === \"object\") {\n    if (!actual || typeof actual !== \"object\") return false\n    for (const [k, v] of Object.entries(expected)) {\n      if (!matches((actual as Record<string, unknown>)[k], v)) return false\n    }\n    return true\n  }\n  return Object.is(actual, expected)\n}\n\nexport interface Assertion {\n  readonly raw: Response\n  hasStatus(code: number): Assertion\n  hasHeader(name: string, matcher?: string | RegExp): Assertion\n  hasCookie(name: string): Assertion\n  hasJson(partial: unknown): Promise<Assertion>\n  hasText(text: string | RegExp): Promise<Assertion>\n  isError(shape?: { code?: string; status?: number; message?: string | RegExp }): Promise<Assertion>\n  json<T = unknown>(): Promise<T>\n}\n\nexport function assertResponse(res: Response): Assertion {\n  const self: Assertion = {\n    raw: res,\n    hasStatus(code) {\n      if (res.status !== code) throw new Error(`expected status ${code}, got ${res.status}`)\n      return self\n    },\n    hasHeader(name, matcher) {\n      const v = res.headers.get(name)\n      if (v === null) throw new Error(`expected header ${name} to be set`)\n      if (matcher !== undefined && !matches(v, matcher)) {\n        throw new Error(`header ${name}=${v} did not match ${String(matcher)}`)\n      }\n      return self\n    },\n    hasCookie(name) {\n      const set = res.headers.getSetCookie\n        ? res.headers.getSetCookie()\n        : [res.headers.get(\"set-cookie\") ?? \"\"]\n      const found = set.some((c) => c?.startsWith(`${name}=`))\n      if (!found) throw new Error(`expected Set-Cookie for ${name}`)\n      return self\n    },\n    async hasJson(partial) {\n      const ct = res.headers.get(\"content-type\") ?? \"\"\n      if (!ct.includes(\"application/json\"))\n        throw new Error(`expected JSON response, got ${ct || \"(none)\"}`)\n      const body = await res.clone().json()\n      if (!matches(body, partial)) {\n        throw new Error(\n          `response body did not match.\\n  expected: ${JSON.stringify(partial)}\\n  actual:   ${JSON.stringify(body)}`,\n        )\n      }\n      return self\n    },\n    async hasText(text) {\n      const body = await res.clone().text()\n      if (!matches(body, text)) throw new Error(`response text did not match ${String(text)}`)\n      return self\n    },\n    async isError(shape = {}) {\n      const body = (await res\n        .clone()\n        .json()\n        .catch(() => null)) as { error?: Record<string, unknown> } | null\n      const err = body?.error ?? null\n      if (!err) throw new Error(\"expected Hyper error envelope\")\n      if (shape.code !== undefined && err.code !== shape.code) {\n        throw new Error(`expected error code ${shape.code}, got ${String(err.code)}`)\n      }\n      if (shape.status !== undefined && res.status !== shape.status) {\n        throw new Error(`expected error status ${shape.status}, got ${res.status}`)\n      }\n      if (shape.message !== undefined && !matches(err.message, shape.message)) {\n        throw new Error(`error.message did not match ${String(shape.message)}`)\n      }\n      return self\n    },\n    async json<T>() {\n      return (await res.clone().json()) as T\n    },\n  }\n  return self\n}\n",
      "sha256": "f6ff3c616f6c33f691d185a989becf36ebb93fb42d5e3faf0a85163efa08013d"
    },
    {
      "path": "testing/auth.ts",
      "contents": "/**\n * Auth test helpers.\n *\n * `signJwtHS256({...})` — produces a real signed HS256 JWT you can put\n * in the `authorization: Bearer <token>` header. Bun.CryptoHasher is the\n * same primitive `@hyper/auth-jwt` uses, so end-to-end tests exercise\n * the production verify path.\n */\n\nimport { createHmac } from \"node:crypto\"\n\nexport interface SignJwtOptions {\n  readonly secret: string\n  readonly payload: Record<string, unknown>\n  readonly expiresInMs?: number\n  readonly now?: () => number\n}\n\nexport function signJwtHS256(opts: SignJwtOptions): string {\n  const now = (opts.now ?? Date.now)()\n  const header = { alg: \"HS256\", typ: \"JWT\" }\n  const payload: Record<string, unknown> = {\n    iat: Math.floor(now / 1000),\n    ...(opts.expiresInMs ? { exp: Math.floor((now + opts.expiresInMs) / 1000) } : {}),\n    ...opts.payload,\n  }\n  const h = b64url(JSON.stringify(header))\n  const p = b64url(JSON.stringify(payload))\n  const sig = createHmac(\"sha256\", opts.secret).update(`${h}.${p}`).digest()\n  return `${h}.${p}.${b64urlBuf(sig)}`\n}\n\n/** Convenience — `asUser({ id })` + a real bearer header in one call. */\nexport function bearerAsUser(opts: {\n  readonly secret: string\n  readonly id: string\n  readonly roles?: readonly string[]\n  readonly expiresInMs?: number\n}): { authorization: string } {\n  const token = signJwtHS256({\n    secret: opts.secret,\n    payload: { sub: opts.id, ...(opts.roles && { roles: opts.roles }) },\n    ...(opts.expiresInMs !== undefined && { expiresInMs: opts.expiresInMs }),\n  })\n  return { authorization: `Bearer ${token}` }\n}\n\nfunction b64url(s: string): string {\n  return b64urlBuf(Buffer.from(s, \"utf8\"))\n}\nfunction b64urlBuf(buf: Buffer): string {\n  return buf.toString(\"base64\").replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\")\n}\n",
      "sha256": "af50e6bd96c175ccf03e6c18b516605b59163831a3ebf5e11cfdee08fa15402b"
    },
    {
      "path": "testing/call.ts",
      "contents": "/**\n * `call(app, method, path, init?)` — runs the full pipeline in-process,\n * returns a real Response. The recommended integration-test primitive.\n *\n * Accepts either a `HyperApp` (from `app({...})`) or a `Hyper`\n * instance (from `new Hyper()` / `hyper()`). The latter is lowered via\n * `.build()` so tests never need to call it manually.\n */\n\nimport { type HttpMethod, Hyper, type HyperApp } from \"@hyper/core\"\nimport { type FakeRequestInit, fakeRequest } from \"./request.ts\"\n\nexport function call(\n  app: HyperApp | Hyper,\n  method: HttpMethod,\n  path: string,\n  init: FakeRequestInit = {},\n): Promise<Response> {\n  const built = app instanceof Hyper ? app.build() : app\n  return built.fetch(fakeRequest(method, path, init))\n}\n",
      "sha256": "2cc5d6ead4d3687cab3ba274cee244524bbc4634223ca0a0f14bb39fe2f552d8"
    },
    {
      "path": "testing/capture.ts",
      "contents": "/**\n * `captureEvents(app)` — attaches a plugin that siphons every wide log\n * event into an in-memory array, so tests can assert on observability\n * contracts (what routes emit, what redaction shape, etc.).\n *\n * We don't import @hyper/log here to keep the peer-dep boundary clean;\n * instead we install a plugin with a request.after hook that drains\n * whatever structured log the ctx exposes. Users can additionally plug\n * @hyper/log's `captureDrain` for full fidelity.\n */\n\nimport type { HyperApp, HyperPlugin, TestOverrides } from \"@hyper/core\"\n\nexport interface CapturedEvent {\n  readonly method: string\n  readonly path: string\n  readonly status: number\n  readonly durationMs: number\n  readonly [k: string]: unknown\n}\n\nexport interface EventCapture {\n  readonly events: readonly CapturedEvent[]\n  /** Convenience: find the first event matching a partial shape. */\n  find(match: Partial<CapturedEvent>): CapturedEvent | undefined\n  /** True if at least one event matches. */\n  has(match: Partial<CapturedEvent>): boolean\n  /** Clear recorded events — useful between test cases. */\n  clear(): void\n  /** Stop recording and detach. */\n  stop(): void\n}\n\n/**\n * Install the capture plugin on a test-scoped app clone. Returns the\n * capture handle plus a new HyperApp with capture active.\n */\nexport function captureEvents(\n  base: HyperApp,\n  opts: TestOverrides = {},\n): {\n  readonly app: HyperApp\n  readonly capture: EventCapture\n} {\n  const events: CapturedEvent[] = []\n  let stopped = false\n  const plugin: HyperPlugin = {\n    name: \"@hyper/testing:capture\",\n    request: {\n      after({ req, res, route }) {\n        if (stopped) return\n        const url = new URL(req.url)\n        events.push({\n          method: req.method,\n          path: route?.path ?? url.pathname,\n          status: res.status,\n          durationMs: 0,\n        })\n      },\n    },\n  }\n  const existing = opts.plugins ?? {}\n  const app = base.test({\n    ...opts,\n    plugins: { ...existing, add: [...(existing.add ?? []), plugin] },\n  })\n  const capture: EventCapture = {\n    get events() {\n      return events\n    },\n    find: (m) => events.find((e) => matchesPartial(e, m)),\n    has: (m) => events.some((e) => matchesPartial(e, m)),\n    clear: () => {\n      events.length = 0\n    },\n    stop: () => {\n      stopped = true\n    },\n  }\n  return { app, capture }\n}\n\nfunction matchesPartial(ev: CapturedEvent, m: Partial<CapturedEvent>): boolean {\n  for (const [k, v] of Object.entries(m)) {\n    if ((ev as Record<string, unknown>)[k] !== v) return false\n  }\n  return true\n}\n",
      "sha256": "f2ece1c2ca6b6669ca3d28ca03a66fde8c1e27f81e1c20a0355218e7b6b7ff23"
    },
    {
      "path": "testing/clock.ts",
      "contents": "/**\n * Test clock — a single abstraction plugins can consume instead of\n * `Date.now()`. Plugins accept `clock?: Clock` in their config; at test\n * time you pass a fake clock and call `advanceTime(ms)`.\n *\n * We deliberately do NOT monkey-patch global `Date`. Explicit clock\n * injection is the contract.\n */\n\nexport interface Clock {\n  readonly now: () => number\n}\n\nexport interface TestClock extends Clock {\n  /** Move the clock forward by `ms`. Pending timers are not drained. */\n  readonly advance: (ms: number) => void\n  /** Reset the clock to `t0`. */\n  readonly reset: (t0?: number) => void\n}\n\nexport function testClock(t0 = 1_700_000_000_000): TestClock {\n  let t = t0\n  return {\n    now: () => t,\n    advance: (ms: number) => {\n      t += ms\n    },\n    reset: (r = t0) => {\n      t = r\n    },\n  }\n}\n\nexport const systemClock: Clock = { now: () => Date.now() }\n\n/** Ambient helper — tests call this to advance a shared clock. */\nlet ambient: TestClock | undefined\nexport function useTestClock(clock: TestClock): TestClock {\n  ambient = clock\n  return clock\n}\nexport function advanceTime(ms: number): void {\n  if (!ambient)\n    throw new Error(\"advanceTime: no ambient test clock — call useTestClock(testClock()) first\")\n  ambient.advance(ms)\n}\n",
      "sha256": "97998c629b1329357201c77520115bb987f098aa669d7dfc40918536915e73db"
    },
    {
      "path": "testing/fuzz.ts",
      "contents": "/**\n * @hyper/testing/fuzz — request-boundary attack corpus.\n *\n * `fuzzRoute(app, \"POST /users\")` hammers the given route with a\n * baseline set of nasty inputs. Each entry expects the framework to\n * answer with a 4xx (never a 500, never a hang, never silent corruption).\n *\n * Consumers get parity coverage with the framework's own fuzz suite.\n */\n\nimport type { HttpMethod, HyperApp } from \"@hyper/core\"\nimport { fakeRequest } from \"./request.ts\"\n\nexport interface FuzzCase {\n  readonly name: string\n  /** Expected status range. Default: 4xx. */\n  readonly expectStatus?: (status: number) => boolean\n  /** Builds a Request for the given route target. */\n  readonly build: (method: HttpMethod, path: string) => Request\n}\n\nconst OVERSIZED_BODY = \"x\".repeat(2 * 1024 * 1024) // 2 MB (> 1 MB default)\n\nconst ATTACK_CASES: readonly FuzzCase[] = [\n  {\n    name: \"proto-pollution via __proto__\",\n    build: (m, p) =>\n      fakeRequest(m, p, { json: JSON.parse('{\"__proto__\": {\"polluted\": true}, \"ok\": 1}') }),\n  },\n  {\n    name: \"proto-pollution via constructor.prototype\",\n    build: (m, p) =>\n      fakeRequest(m, p, {\n        json: JSON.parse('{\"constructor\": {\"prototype\": {\"p\": 1}}}'),\n      }),\n  },\n  {\n    name: \"oversized body (>1MB)\",\n    build: (m, p) => fakeRequest(m, p, { text: OVERSIZED_BODY }),\n    expectStatus: (s) => s === 413 || (s >= 400 && s < 500),\n  },\n  {\n    name: \"malformed JSON\",\n    expectStatus: (s) => s === 400,\n    build: (m, p) =>\n      new Request(new URL(`http://local${p}`), {\n        method: m,\n        headers: { \"content-type\": \"application/json\" },\n        body: \"{oops\",\n      }),\n  },\n  {\n    name: \"path traversal in path segment\",\n    build: (m, _p) => fakeRequest(m, \"/../../../etc/passwd\"),\n    expectStatus: (s) => s >= 400 && s < 500,\n  },\n  {\n    name: \"smuggled method via X-HTTP-Method-Override\",\n    build: (m, p) => fakeRequest(m, p, { headers: { \"x-http-method-override\": \"DELETE\" } }),\n    // The framework must NOT coerce the method — 200/404 acceptable, but\n    // never a 'DELETE' being honored. We only check the request succeeds\n    // or fails without silently swapping verbs.\n    expectStatus: (s) => s < 500,\n  },\n  {\n    name: \"overlong header\",\n    build: (m, p) => fakeRequest(m, p, { headers: { \"x-big\": \"y\".repeat(65_000) } }),\n    expectStatus: (s) => s < 500,\n  },\n  {\n    name: \"XSS-ish cookie\",\n    build: (m, p) => fakeRequest(m, p, { cookie: { sid: '\"><script>alert(1)</script>' } }),\n    expectStatus: (s) => s < 500,\n  },\n  {\n    name: \"null byte in path\",\n    build: (m, _p) => fakeRequest(m, \"/users/\\x00id\"),\n    expectStatus: (s) => s >= 400 && s < 500,\n  },\n  {\n    name: \"empty JSON body\",\n    build: (m, p) =>\n      fakeRequest(m, p, { text: \"\", headers: { \"content-type\": \"application/json\" } }),\n    expectStatus: (s) => s < 500,\n  },\n  {\n    name: \"duplicate content-length\",\n    build: (m, p) =>\n      new Request(new URL(`http://local${p}`), {\n        method: m,\n        headers: { \"content-type\": \"application/json\", \"content-length\": \"100\" },\n        body: \"{}\",\n      }),\n    expectStatus: (s) => s < 500,\n  },\n]\n\nexport interface FuzzReport {\n  readonly method: HttpMethod\n  readonly path: string\n  readonly passed: readonly FuzzResult[]\n  readonly failed: readonly FuzzResult[]\n  readonly ok: boolean\n}\n\nexport interface FuzzResult {\n  readonly case: string\n  readonly status: number\n  readonly accepted: boolean\n  readonly error?: string\n}\n\n/**\n * Run every case in the corpus against `METHOD PATH`. Returns a report\n * describing which cases were handled correctly (non-500, matching the\n * expected status predicate).\n */\nexport async function fuzzRoute(\n  app: HyperApp,\n  entry: `${HttpMethod} ${string}`,\n  opts: { readonly rounds?: number; readonly extraCases?: readonly FuzzCase[] } = {},\n): Promise<FuzzReport> {\n  const [method, path] = entry.split(\" \") as [HttpMethod, string]\n  const cases = [...ATTACK_CASES, ...(opts.extraCases ?? [])]\n  const rounds = Math.max(1, opts.rounds ?? 1)\n  const passed: FuzzResult[] = []\n  const failed: FuzzResult[] = []\n  for (const c of cases) {\n    const expect = c.expectStatus ?? defaultExpect\n    for (let i = 0; i < rounds; i++) {\n      let status = 0\n      let error: string | undefined\n      try {\n        const res = await app.fetch(c.build(method, path))\n        status = res.status\n      } catch (e) {\n        error = e instanceof Error ? e.message : String(e)\n      }\n      const accepted = !error && expect(status)\n      const result: FuzzResult = {\n        case: c.name,\n        status,\n        accepted,\n        ...(error !== undefined && { error }),\n      }\n      ;(accepted ? passed : failed).push(result)\n    }\n  }\n  return { method, path, passed, failed, ok: failed.length === 0 }\n}\n\nfunction defaultExpect(status: number): boolean {\n  return status >= 400 && status < 500\n}\n\n/** Re-exported so framework-internal tests can use the same corpus. */\nexport { ATTACK_CASES }\n",
      "sha256": "7f4e25aa453748ed9a300426e2ef19517a0a38b2f65a9574881352a02cad5e30"
    },
    {
      "path": "testing/index.ts",
      "contents": "/**\n * @hyper/testing — ergonomic primitives for testing Hyper apps.\n *\n * Philosophy: testing a Hyper route should feel like testing a plain\n * async function. No supertest, no dev-server juggling, no mock server.\n */\n\nexport { assertResponse, type Assertion } from \"./assert.ts\"\nexport { call } from \"./call.ts\"\nexport {\n  captureEvents,\n  type CapturedEvent,\n  type EventCapture,\n} from \"./capture.ts\"\nexport {\n  advanceTime,\n  type Clock,\n  systemClock,\n  testClock,\n  type TestClock,\n  useTestClock,\n} from \"./clock.ts\"\nexport {\n  type KvEntry,\n  type KvStore,\n  type MemoryDb,\n  memoryDb,\n  memoryKv,\n  memoryRateLimiter,\n  type MemoryRateLimiterOptions,\n  type MemoryTable,\n  memoryTable,\n  type RateLimitResult,\n} from \"./memory-stores.ts\"\nexport { mockCtx } from \"./mock-ctx.ts\"\nexport { mockPlugin } from \"./mock-plugin.ts\"\nexport { asUser, fakeRequest, type FakeRequestInit, type FakeUser } from \"./request.ts\"\nexport { type ManifestSnapshot, snapshotManifest } from \"./snapshot.ts\"\n",
      "sha256": "db07b29f69d579f5a05dd64120e28cff18d6e7b2d743d9aa30a5360129874bce"
    },
    {
      "path": "testing/memory-stores.ts",
      "contents": "/**\n * Memory stores — drop-in replacements for the Store shapes that\n * @hyper/cache, @hyper/idempotency, @hyper/rate-limit, @hyper/session\n * accept. Identical surface, zero persistence, deterministic for tests.\n *\n * These shapes intentionally don't import from the consumer packages —\n * they duplicate the tiny interfaces so `@hyper/testing` can serve any\n * of them without cyclic deps.\n */\n\nimport type { Clock } from \"./clock.ts\"\nimport { systemClock } from \"./clock.ts\"\n\n// Generic KV ------------------------------------------------------------\n\nexport interface KvEntry<V> {\n  readonly value: V\n  readonly expiresAt: number | null\n}\n\nexport interface KvStore<V> {\n  get(key: string): Promise<V | undefined>\n  set(key: string, value: V, ttlMs?: number): Promise<void>\n  delete(key: string): Promise<void>\n}\n\nexport function memoryKv<V>(clock: Clock = systemClock): KvStore<V> {\n  const map = new Map<string, KvEntry<V>>()\n  return {\n    async get(key) {\n      const e = map.get(key)\n      if (!e) return undefined\n      if (e.expiresAt !== null && e.expiresAt <= clock.now()) {\n        map.delete(key)\n        return undefined\n      }\n      return e.value\n    },\n    async set(key, value, ttlMs) {\n      map.set(key, { value, expiresAt: ttlMs ? clock.now() + ttlMs : null })\n    },\n    async delete(key) {\n      map.delete(key)\n    },\n  }\n}\n\n// Rate limiter ----------------------------------------------------------\n\nexport interface RateLimitResult {\n  readonly allowed: boolean\n  readonly remaining: number\n  readonly resetMs: number\n}\n\nexport interface MemoryRateLimiterOptions {\n  readonly limit: number\n  readonly windowMs: number\n  readonly clock?: Clock\n}\n\nexport function memoryRateLimiter(opts: MemoryRateLimiterOptions): {\n  check: (key: string) => Promise<RateLimitResult>\n  reset: (key?: string) => void\n} {\n  const clock = opts.clock ?? systemClock\n  const buckets = new Map<string, { tokens: number; resetAt: number }>()\n  return {\n    async check(key: string) {\n      const now = clock.now()\n      let b = buckets.get(key)\n      if (!b || b.resetAt <= now) {\n        b = { tokens: opts.limit, resetAt: now + opts.windowMs }\n        buckets.set(key, b)\n      }\n      if (b.tokens <= 0) {\n        return { allowed: false, remaining: 0, resetMs: b.resetAt - now }\n      }\n      b.tokens -= 1\n      return { allowed: true, remaining: b.tokens, resetMs: b.resetAt - now }\n    },\n    reset(key) {\n      if (key === undefined) buckets.clear()\n      else buckets.delete(key)\n    },\n  }\n}\n\n// Tiny in-memory SQL-ish \"db\" ------------------------------------------\n\nexport interface MemoryTable<Row> {\n  readonly name: string\n  readonly rows: Row[]\n  insert(row: Row): Row\n  find(predicate: (r: Row) => boolean): Row | undefined\n  filter(predicate: (r: Row) => boolean): Row[]\n  update(predicate: (r: Row) => boolean, patch: Partial<Row>): Row | undefined\n  delete(predicate: (r: Row) => boolean): number\n  clear(): void\n}\n\nexport function memoryTable<Row>(name: string): MemoryTable<Row> {\n  const rows: Row[] = []\n  return {\n    name,\n    rows,\n    insert(r) {\n      rows.push(r)\n      return r\n    },\n    find: (p) => rows.find(p),\n    filter: (p) => rows.filter(p),\n    update: (p, patch) => {\n      const i = rows.findIndex(p)\n      if (i < 0) return undefined\n      rows[i] = { ...(rows[i] as object), ...(patch as object) } as Row\n      return rows[i]\n    },\n    delete: (p) => {\n      const before = rows.length\n      for (let i = rows.length - 1; i >= 0; i--) {\n        if (p(rows[i] as Row)) rows.splice(i, 1)\n      }\n      return before - rows.length\n    },\n    clear() {\n      rows.length = 0\n    },\n  }\n}\n\n/**\n * `memoryDb()` — a bag of named tables with helpers. Covers the ~20\n * queries that `apps/examples/todo` exercises. Grows organically.\n */\nexport interface MemoryDb {\n  table<Row>(name: string): MemoryTable<Row>\n  reset(): void\n}\n\nexport function memoryDb(): MemoryDb {\n  const tables = new Map<string, MemoryTable<unknown>>()\n  return {\n    table<Row>(name: string): MemoryTable<Row> {\n      let t = tables.get(name) as MemoryTable<Row> | undefined\n      if (!t) {\n        t = memoryTable<Row>(name)\n        tables.set(name, t as MemoryTable<unknown>)\n      }\n      return t\n    },\n    reset() {\n      for (const t of tables.values()) t.clear()\n    },\n  }\n}\n",
      "sha256": "dce325e9bf13406884d4b541ff734c58448da00cd397aeac524195752e5b5420"
    },
    {
      "path": "testing/mock-ctx.ts",
      "contents": "/**\n * `mockCtx(overrides)` — build a typed AppContext stub for calling a\n * route as a plain async function via `route.callable({ ctx })`.\n *\n * The type-level augmentation on `AppContext` is respected: if your app\n * declares `interface AppContext { db: Db; user?: User }`, then\n * `mockCtx({ db: fakeDb })` returns `AppContext` with those fields set.\n */\n\nimport type { AppContext } from \"@hyper/core\"\n\nexport function mockCtx<T extends Partial<AppContext> = Partial<AppContext>>(\n  overrides: T = {} as T,\n): AppContext {\n  return overrides as unknown as AppContext\n}\n",
      "sha256": "83ac8641be49b823d4247bbd57a4698f124659b51befa858dbf50eb4294b16e2"
    },
    {
      "path": "testing/mock-plugin.ts",
      "contents": "/**\n * `mockPlugin({...})` — one-liner plugin for inserting arbitrary test\n * behavior without writing a full plugin file.\n *\n *   app.test({ plugins: { add: [mockPlugin({\n *     name: \"stub-metrics\",\n *     request: { after: ({ res }) => counts.push(res.status) },\n *   })] } })\n */\n\nimport type { HyperPlugin } from \"@hyper/core\"\n\nexport function mockPlugin(plugin: HyperPlugin): HyperPlugin {\n  return plugin\n}\n",
      "sha256": "86b3febf8af4bf7e1e5720cb9e90da085e20894dee37f2f41f1db4aff19c1b4c"
    },
    {
      "path": "testing/request.ts",
      "contents": "/**\n * Request builders — `fakeRequest` and `asUser`.\n *\n * `fakeRequest` is a thin ergonomic wrapper around `new Request()` that\n * accepts the fields tests actually want (json, form, auth, cookie, ip)\n * without the ceremony of building headers by hand.\n */\n\nimport type { HttpMethod } from \"@hyper/core\"\n\nexport interface FakeRequestInit {\n  readonly query?: Record<string, string | number | boolean>\n  readonly json?: unknown\n  readonly form?: FormData | Record<string, string>\n  readonly text?: string\n  readonly auth?: string\n  readonly cookie?: Record<string, string> | string\n  readonly ip?: string\n  readonly headers?: Record<string, string>\n  readonly origin?: string\n}\n\nconst DEFAULT_ORIGIN = \"http://local\"\n\nexport function fakeRequest(method: HttpMethod, path: string, init: FakeRequestInit = {}): Request {\n  const origin = init.origin ?? DEFAULT_ORIGIN\n  const url = new URL(path.startsWith(\"/\") ? `${origin}${path}` : path)\n  if (init.query) {\n    for (const [k, v] of Object.entries(init.query)) url.searchParams.set(k, String(v))\n  }\n  const headers = new Headers(init.headers ?? {})\n  if (init.auth) headers.set(\"authorization\", init.auth)\n  if (init.cookie !== undefined) {\n    const cookie = typeof init.cookie === \"string\" ? init.cookie : toCookieHeader(init.cookie)\n    headers.set(\"cookie\", cookie)\n  }\n  if (init.ip) headers.set(\"x-forwarded-for\", init.ip)\n\n  let body: BodyInit | undefined\n  if (init.json !== undefined) {\n    body = JSON.stringify(init.json)\n    if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"application/json\")\n  } else if (init.form !== undefined) {\n    body = init.form instanceof FormData ? init.form : toFormData(init.form)\n  } else if (init.text !== undefined) {\n    body = init.text\n    if (!headers.has(\"content-type\")) headers.set(\"content-type\", \"text/plain; charset=utf-8\")\n  }\n\n  return new Request(url, { method, headers, ...(body !== undefined && { body }) })\n}\n\nfunction toCookieHeader(jar: Record<string, string>): string {\n  return Object.entries(jar)\n    .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)\n    .join(\"; \")\n}\n\nfunction toFormData(obj: Record<string, string>): FormData {\n  const fd = new FormData()\n  for (const [k, v] of Object.entries(obj)) fd.set(k, v)\n  return fd\n}\n\n/**\n * Partial-ctx helper — produces a `user`-shaped stub for routes guarded\n * by `@hyper/auth-jwt`. Bypasses JWT verify in tests that don't need a\n * signed token; use `@hyper/testing/auth` for real-signed tokens.\n */\nexport interface FakeUser {\n  readonly id: string\n  readonly roles?: readonly string[]\n  readonly claims?: Record<string, unknown>\n}\n\nexport function asUser(id: string | FakeUser): { readonly user: FakeUser } {\n  const u: FakeUser = typeof id === \"string\" ? { id } : id\n  return { user: u }\n}\n",
      "sha256": "f556e16beb614a03eacb81d17a9d564e01040a1ae8ca61a159d515801629b826"
    },
    {
      "path": "testing/snapshot.ts",
      "contents": "/**\n * `snapshotManifest(app)` — guards the public contract in a single\n * assertion. Snapshots OpenAPI + MCP + client manifests together with a\n * stable structure. A breaking change to any surface fails the snapshot.\n */\n\nimport type { HyperApp } from \"@hyper/core\"\n\nexport interface ManifestSnapshot {\n  readonly openapi: unknown\n  readonly mcp: unknown\n  readonly client: unknown\n}\n\nexport function snapshotManifest(app: HyperApp): ManifestSnapshot {\n  return {\n    openapi: app.toOpenAPI({ title: \"snapshot\", version: \"0.0.0\" }),\n    mcp: app.toMCPManifest(),\n    client: app.toClientManifest(),\n  }\n}\n",
      "sha256": "12133ab8c2c558cf0d0d4da63fb2c21a38b3c492fdc93c6d8fb38d4afd45341d"
    },
    {
      "path": "testing/types.ts",
      "contents": "/**\n * Type-level test helpers. Re-exports `expectTypeOf` from expect-type\n * plus Hyper-shaped narrowing helpers.\n */\n\nimport type { HttpMethod, HyperApp, Route } from \"@hyper/core\"\n\nexport { expectTypeOf } from \"expect-type\"\n\nexport interface RouteAssertion<R> {\n  readonly input: {\n    toEqualTypeOf<T>(): R extends { __input: infer I } ? ([I] extends [T] ? true : never) : never\n  }\n  readonly output: {\n    toEqualTypeOf<T>(): R extends { __output: infer O } ? ([O] extends [T] ? true : never) : never\n  }\n}\n\nexport function expectRoute<R>(_route: R): RouteAssertion<R> {\n  return {\n    input: { toEqualTypeOf: () => true as never },\n    output: { toEqualTypeOf: () => true as never },\n  }\n}\n\n/** Runtime helper used in compile-time-shaped tests — always true. */\nexport function expectApp(app: HyperApp): {\n  hasRoute(entry: `${HttpMethod} ${string}`): boolean\n} {\n  return {\n    hasRoute(entry) {\n      const [method, path] = entry.split(\" \") as [HttpMethod, string]\n      return app.routeList.some((r: Route) => r.method === method && r.path === path)\n    },\n  }\n}\n",
      "sha256": "747f2349a2aadc8c7c44a63008e671283e3748dc0dae7502192d4b3df89bbc7f"
    }
  ],
  "subpaths": {}
}