npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@geonosis/testbed

v3.8.1

Published

The fixtures law as code, one adapter contract for an integration harness, and the conformance suite an adapter has to pass. Extracted from a Medusa storefront's Medusa runner and a Workers + D1 app's workerd cell.

Readme

@geonosis/testbed

Through the front door: geonosis testbed — the metapackage pins this and every other kit tool at ONE version, and passes the exit code through unchanged.

An integration harness is not its engine. It is the arrangement around one engine call: where fixtures live and that nothing outside them is readable, what a database is called and how long it lives, what the network may reach, and who boots it. Two shipping repos wrote that arrangement separately — a Medusa storefront around medusaIntegrationTestRunner, a Workers + D1 app around workerd and a Postgres cell — and neither one's arrangement needs its engine in order to be expressed. This package is the arrangement; the engines stay peer dependencies of the repo that runs them.

What the two harnesses really do, measured file by file, is in docs/testbed-inventory-2026-08-30.md.

pnpm add -D @geonosis/testbed

The fixtures law

import { fixturesOnly } from '@geonosis/testbed'

const fixture = fixturesOnly(new URL('./__fixtures__', import.meta.url).pathname)

fixture('invoices', 'march.yaml') // -> /…/__fixtures__/invoices/march.yaml
fixture('..', 'Purchase Orders & Specs') // throws

A Workers + D1 app's §4.3 — "a test reads and writes ONLY apps/web/__tests__/__fixtures__/" — exists because the directory next door in that repo holds real customers' documents. Its own guard compares strings, which three shapes get past, and all three are refused here:

| shape | what a string-prefix guard does | |---|---| | fixture('/etc/passwd') | resolve reads /etc/passwd; join quietly reads <root>/etc/passwd | | a sibling named <root>-production | resolved.startsWith(root) is true, and it is a different directory | | a symlink inside the root pointing out | every character of the path is inside the root |

The first is refused for naming an absolute path rather than reinterpreted; the second by comparing against root + path.sep; the third by resolving through realpath. A path that does not exist yet — a fixture about to be written — is judged as if it had been.

Defining a testbed

import { createHash } from 'node:crypto'
import { defineTestbed } from '@geonosis/testbed'

export const testbed = defineTestbed({
  adapter: myAdapter,
  database: {
    name: (suite) => createHash('sha1').update(suite).digest('hex').slice(0, 12),
    per: 'suite',
  },
  fixtures: new URL('./__fixtures__', import.meta.url).pathname,
  network: 'none',
})

per is 'suite' (one database per describe), 'file', or 'run' (one cell for the whole run, emptied per test). Both are idioms a consumer already runs.

Everything it can refuse, it refuses when it is defined, not at the first boot:

  • an adapter with no id — every diagnostic names it
  • an adapter declaring snapshot without restore, or the reverse: half of per-test isolation is not isolation
  • a per it does not know — a config read from JSON has whatever spelling someone typed
  • network: { allow: [] } — an allow list of nothing is 'none' spelt so it reads as permission
  • no fixtures directory

And when a database name is derived:

  • an empty name
  • a name over 63 bytes — Postgres truncates there, and the truncation is a NOTICE nobody reads
  • a name a different key already took. sha1(description).slice(0, 12) is 48 bits; two suites that collide share a database, and the second one's reset empties the first one's rows with both green. Neither source repo notices this; this refuses it.

The adapter contract

type TestbedAdapter<Db> = {
  id: string
  boot: (ctx: BootContext) => Promise<{ db: Db; teardown: () => Promise<void> }>
  snapshot?: () => Promise<void>
  restore?: () => Promise<void>
  waitFor?: (kind: string, predicate: () => boolean | Promise<boolean>) => Promise<void>
}

boot is all that is required. The rest is what a particular engine can do: Medusa snapshots to a Postgres TEMPLATE database and restores from it before every test; a Workers + D1 app's cell cannot, and truncates instead. The BootContext is frozen, including its allow-list array — an adapter that wrote its own state onto it would carry it into the next boot.

The conformance suite

import { runAdapterConformance } from '@geonosis/testbed'

it('conforms', async () => {
  const report = await runAdapterConformance(myAdapter, {
    probe: { read: (db) => db.rows(), write: (db) => db.insert(…) },
  })

  expect(report.passed, JSON.stringify(report.checks, null, 2)).toBe(true)
})

Exported because consumers write adapters this repo never sees, and an adapter that has not been through this is a claim. Each check is a property one of the two harnesses really depends on:

| check | why | |---|---| | boot returns a handle | a boot that resolves with db: undefined fails later, somewhere else | | restore before a snapshot is refused | Medusa spends a databaseTemplateReady flag on exactly this; restoring a template nobody took returns whatever the database happened to hold | | restore returns the snapshot | a no-op restore is invisible without writing a row — see the probe below | | waitFor rejects when it never comes true | a waitFor that resolves on timeout is worse than none | | waitFor resolves when it comes true | and one that never resolves is a hung suite | | teardown is idempotent | a Medusa storefront wires cleanup to afterAll and to SIGTERM/SIGINT; on a cancelled run both fire | | boots again after teardown | a run-lifetime adapter has to survive re-entry |

It never throws for the adapter's sake — a broken adapter is a failed check carrying its own message in detail, because a conformance run that dies tells the reader less than one that reports.

The probe. This package cannot write a row through someone else's handle, and without writing one a no-op restore() is indistinguishable from a real one. So restore returns the snapshot is skipped by name when no probe is given — report.skipped is a separate list from report.checks, because a check that did not run is not a check that passed. Adapters with no snapshot/restore and no waitFor skip those checks and still pass; they are optional, not absent.

@geonosis/testbed/medusa-built

A Medusa storefront's runIntegrationTest — 26 lines that 106 integration suites go through — generalised, and carrying the refusals its own comments describe but its code does not make.

import { medusaIntegrationTestRunner } from '@medusajs/test-utils'
import { createMedusaIntegrationTest } from '@geonosis/testbed/medusa-built'
import { describe } from 'vitest'

export const { adapterFor, runIntegrationTest } = createMedusaIntegrationTest({
  describe,
  root: resolve(import.meta.dirname, '../..'),
  runner: medusaIntegrationTestRunner,
})

The engine is injected, never imported. @medusajs/test-utils is a peer of the repo that runs it — a package that reached for it could not be installed by a repo on another runtime, and could not be tested without a database. MedusaSuiteLike is a structural type over what the runner really hands a suite.

| option | default | why | |---|---|---| | root | required | the app directory | | builtDir | .medusa/server | Medusa's own output; the loaders and migrations must run as in production | | databaseName | sha1(description).slice(0, 12) | a Medusa storefront's derivation, kept exactly — the engine appends -integration-<worker> and lowercases | | connectionEnv | ['DB_HOST', 'DB_PORT', 'DB_USERNAME'] | Medusa's own getDatabaseURL keys; [] to take its defaults deliberately | | nodeEnv | 'test' | false when the repo checks it itself | | requireBuilt | true | | | env, fixtures, waitForMs | — | fixtures is optional: a Medusa storefront's suites build their world through the booted app |

What it refuses, and why each one is worth a refusal

  • an unbuilt server. ".medusa/server must be rebuilt before integration tests" is a law in a Medusa storefront's docs enforced by nothing. Unbuilt, the run dies inside a Medusa loader after the database has been created.
  • NODE_ENV that is not test. It selects the in-memory cache, event bus and workflow engine, and it is read while the app loads — setting it afterwards is too late.
  • a connection variable that is unset. This is the sharp one. Medusa defaults every one of DB_HOST/DB_PORT/DB_USERNAME, so an unset variable is not an error: the suite quietly connects to whatever is on the default host and port. A Medusa storefront's Postgres moved from 5433 to 5434 because another project's embedded cell took 5433; with the defaults, a stale environment reaches the other project's database and the suite goes green against it.
  • two suites that derive one database name. 48 bits of sha1. They share a database, and each one's reset empties the other's rows, with both green.

The adapter

adapterFor(suiteOptions) wraps what the runner hands a suite. It attaches rather than boots — the runner owns the application's lifecycle, creating the database, migrating, snapshotting to a Postgres TEMPLATE and restoring from it before every test. Two things it adds:

  • restore() refuses when this adapter took no snapshot(). The runner keeps a databaseTemplateReady flag for its own beforeEach; the dbUtils object it hands the suite carries no such guard, so a direct restore() returns whichever template was last written.
  • waitFor('workflows', predicate) drains utils.waitWorkflowExecutions() and then reads the predicate, on a deadline, rejecting when it never comes true. The engine calls waitWorkflowExecutions in every afterEach; a test that asserts on a workflow's side effect needs it before the assertion. A Medusa storefront never names it — grep over its tree returns nothing.

The real-engine smoke is the consumer's step

This package is proven against a stub of medusaIntegrationTestRunner, because the contract is what it owns. Booting a Medusa app needs a Postgres, a built server and about two minutes, and none of those belong in a kit's suite. The consumer's adoption step is: point one existing integration suite at createMedusaIntegrationTest, run pnpm test:integration, and confirm the same pass line and the same database name as before.

@geonosis/testbed/workerd

A Workers + D1 app's harness, in three pieces. Each one is a mechanism its repo built after a specific failure, and each is proven here against stubs of the engine — the real workerd pool and a real Postgres are the consumer's.

1. The runner whose exit code lies

import { runStrict, verdictOf } from '@geonosis/testbed/workerd'

const { code, verdict } = await runStrict({ args: ['vitest', 'run', ...process.argv.slice(2)] })
if (!verdict.ok) {
  process.stderr.write(`\n${verdict.why}\n`)
  process.exit(1)
}
process.exit(code)

@cloudflare/vitest-pool-workers exits 0 with failing tests. Every workerd green in the repo this comes from was hollow until something read the report instead of the exit code. verdictOf refuses three things:

| report | verdict | |---|---| | absent | a crash before the reporter is not a pass | | { success: false, numFailedTests: 0 } | the run did not finish; checked first, because anything counting failures reads this as a pass | | { success: true, numFailedTests: n > 0 } | n test(s) failed |

When the report names no failing test, or none exists, the refusal quotes the last lines the runner wrote to stderr: a runner that died in a native load says so there and nowhere else (#333).

A clean report does not launder a non-zero exit: code is returned unchanged and the caller exits with it.

This judgement is deliberately not shared with @geonosis/ratchet's testFailures counter, which reads the same field. They answer different questions — "should this process fail" versus "how much debt is there" — and they are installed for different reasons; coupling them would put a ratchet in every repo that only wanted a harness. The contract is written down here, and each package holds it independently. The proof is a real spawned process that lies in each of the three ways above, not a mocked one.

2. The config that has no remote binding to call

import { deriveLocalOnlyConfig } from '@geonosis/testbed/workerd'

const configPath = deriveLocalOnlyConfig({
  dropVars: ['LOCAL_AI_URL'],
  source: new URL('./wrangler.jsonc', import.meta.url).pathname,
})

An ai binding is always remote: the pool resolves the account against api.cloudflare.com the moment it sees one, before a single test runs — a network call, a credential and a bill in the middle of the suite. So the test worker gets the real config with those bindings removed.

  • Derived, not duplicated. Read from the real wrangler.jsonc on every start, so a binding added there is in the suites the same day. This is what makes "tests never call a model" a property of the runtime rather than a promise about the code: in the test worker there is no env.AI to call.
  • Every named environment too. They inherit nothing per binding, so each repeats it, and a block missed is a network call nobody expected.
  • Written BESIDE the source, and a target containing a path separator is refused: every relative path in a wrangler config (main, migrations_dir) resolves against the directory the config sits in, so a copy one level deeper resolves src/index.ts to a file that is not there.
  • The source is read by jsonc-parser: line and block comments and trailing commas are read, and a comment between two tokens (1/* why */0) is refused naming its line, as wrangler refuses it. The copy is written as plain JSON.

remoteOnly defaults to ['ai'] — a platform fact, not a repo's — and is overridable.

3. One cell for the run

import EmbeddedPostgres from 'embedded-postgres'
import { createCellServer, workerdAdapter } from '@geonosis/testbed/workerd'

const cell = await createCellServer({
  cluster: ({ databaseDir, port }) =>
    new EmbeddedPostgres({ databaseDir, password: 'postgres', persistent: false, port, user: 'postgres' }),
  prepare: async (connectionString) => {
    await migrateCell(connectionString)
    return appConnectionStringOf(connectionString, 'app-password')
  },
})

embedded-postgres is a peer, injected as a factory. CELL_CONNECTION_STRING (rename it with connectionStringEnv) takes an externally-owned cell instead — which is how the same suite runs against every Postgres major a deployment might use — and that one is prepared and never stopped: in CI the container is thrown away, and a managed instance is not the suite's to stop. Otherwise a throwaway cluster starts on a port found by binding :0, in a fresh temp directory.

Two things this adds to the shape it came from: stop() is idempotent, and a cluster whose prepare throws is stopped rather than leaked — a failed migration in that repo leaves a Postgres and its data directory up for the rest of the session.

workerdAdapter({ cell, execute, tables, before }) is the adapter. There is no template database, so restore() is a TRUNCATE, and empty is the only state a truncate can return to: snapshot() therefore does not record the current rows, it empties the tables and marks that point. That is exactly the split the source repo already has between its two setup files — the truncate-per-test suites are the unseeded ones. A suite that seeds re-seeds after each restore.

truncateAllStatement(tables) is one statement for any number of tables, because a DELETE per table inside a transaction cost twenty-eight round trips before every test. It refuses an empty table list (a reset that resets nothing reads as isolation) and any name that is not a plain identifier, since the list is usually derived from a schema object.

No waitFor. The source repo has none — its raised timeouts carry the waiting — so this adapter declares none, and the conformance suite reports those checks as skipped rather than passed.

postgresForSuite is those three pieces as one call — boot the cell, migrate it from zero as its owner, empty the tenant tables between files:

import EmbeddedPostgres from 'embedded-postgres'
import { postgresForSuite } from '@geonosis/testbed/workerd'

const suite = await postgresForSuite({
  cluster: ({ databaseDir, port }) => new EmbeddedPostgres({ databaseDir, password: 'postgres', persistent: false, port, user: 'postgres' }),
  connect: async (connectionString) => openOwnerConnection(connectionString),
  migrate: async (connectionString) => {
    await migrateCell(connectionString)
    return appConnectionStringOf(connectionString, 'app-password')
  },
  tables: ['documents', 'document_revisions'],
})

beforeEach(() => suite.reset())
afterAll(() => suite.stop())

connect is the owner's connection, handed in because this package imports no driver; reset() is one truncate … restart identity cascade over tables through it. The table list is judged before a cluster boots, a failed migrate or connect stops the cluster it started, and stop() closes the connection and then the cluster, once. It runs against a real embedded Postgres in src/workerd/postgres-suite.exam.test.ts.

What a workerd suite costs

The floor is per spec FILE, and it is workerd instantiating the worker under test — not your setup, not your fixtures.

| measured | figure | |---|---| | a worker-under-test spec file, @cloudflare/vitest-pool-workers on a wrangler main worker (2026-09-02, a consumer's Workers repo) | ~4.5 s per file; setup hooks 0.2 s of it | | this repo's own single workerd spec file (2026-09-03, macOS, node 24.16) | 4.61 s wall, of which 1.09 s is the tests and 0.19 s collection — the remaining ~3.3 s is the pool |

So a suite of twelve spec files costs a minute before a single assertion runs, and splitting a file in two costs another 4.5 s. Fewer spec files, or a targeted run, is the only lever that moves that number. Cutting setup work moves 0.2 s of it.

singleWorker is a @cloudflare/vitest-pool-workers option that @cloudflare/vitest-plugin 1.3.6 does not have: on vitest 4 the same thing is spelled --no-isolate --max-workers=1. Measured on this repo's own workerd suite (foundations/workflows: three spec files, 60 tests, macOS, vitest 4.1.11, 2026-10-08), three runs each:

| spelling | wall | what vitest reports | |---|---|---| | default — files in parallel, each isolated | 2.7–2.8 s | 2.2–2.3 s | | --no-isolate --max-workers=1 | 3.8–3.9 s | 3.3–3.4 s | | --max-workers=1 (serial, isolated) | 4.7–4.9 s | 4.1–4.3 s |

One shared worker is slower than the default here, because the default runs the three files at once and the tests inside them cost 2.4 s of the total either way. It saves nothing a suite of a few files can spend; the 53-file suite below is the case it was asked about, and it was not run from here (that repo is read-only to this one).

isolate: false and singleWorker share one module graph across files. Measured on the same day: they changed the worker-under-test suite's time by nothing at all and hung it once, while cutting a plain database package from 77 s to 12.5 s. The rule that follows:

  • a library suite — no worker under test, no bindings, code that would run anywhere — may share a module graph, and that is where the 77 s → 12.5 s came from;
  • never for a worker-under-test suite: the cost there is instantiating the worker, which is the thing the isolation was paying for, and sharing it buys nothing and can hang the run.

@geonosis/testbed/e2e

A Playwright suite that can reach only the stack it was pointed at, and can mail nobody. It exists because three failures read as a pass: a URL with a fallback tests whichever stack the fallback names, a local app driven against a remote API acts on that API's data, and a test that signs up a real address sends real mail.

// e2e/test.ts, the one file that imports Playwright
import { test as base } from '@playwright/test'
import { hermeticTest, readStack, requireEnv } from '@geonosis/testbed/e2e'

export const stack = readStack({ api: 'E2E_API_URL', app: 'E2E_APP_URL' })

export const password = requireEnv('E2E_PASSWORD')

export const test = hermeticTest(base, {
  allow: [requireEnv('E2E_LOGIN')],
  apiUrl: stack.apiUrl,
  domain: 'example.test'
})

export { expect } from '@playwright/test'

The engine is injected, never imported, as in medusa-built: playwright is an optional peer, and hermeticTest calls extend on the test it is given, so the suite never carries a second copy of Playwright. Any test works, including one the repo has already extended.

| export | what it does | |---|---| | requireEnv(name, env?) | The value. Unset, empty or blank throws naming the variable. | | requireUrl(name, env?) | An http or https URL with no trailing slash. A value that is not one throws naming the variable, and never prints the value. | | readStack({ api, app, env?, stackOf? }) | { apiUrl, appUrl, urlOf }. Throws when app is on another stack than api. urlOf(name) reads a third URL when a test asks for it, held to the same stack, so a run that never opens it does not need it set. | | stackOfUrl(url) | The default reading: local for a loopback address, the address itself for any other IP, and otherwise the hostname less its first label, so app.staging.example.com and api.staging.example.com are one stack and api.example.com is another. | | hermeticTest(base, options) | base with a context fixture that intercepts before a test can use it, so a page the test opens itself is covered too. | | interceptEmails(context, options) | The same on a context you made yourself. | | rewriteEmails(body, { allow?, domain? }) | Every address in a body moved to domain, in JSON, form-encoded and multipart text alike. |

options are apiUrl, domain and allow. apiUrl limits interception to that host and, when it has one, that path: routing every request costs a round trip per asset, so an app's own traffic is left alone. An API on the app's host therefore takes its path, https://example.test/api. domain defaults to example.test, and a domain that is not reserved is refused when the wrapper is defined, since a real inbox could answer it. An address already on a reserved domain is left alone. allow lists the login identities that must reach the API as written; a rewritten login fails as a 401.

stackOfUrl cannot know a public suffix, so example.co.uk and api.example.co.uk read as two stacks, and two hosts on one domain that differ by a word (app-staging, api) read as one. Pass stackOf for either: it takes a URL and answers a name.

Interception sees what the browser sends. A test's own request.post and page.request go past it, so a call that can send mail goes through the page, or through rewriteEmails by hand.

Ban the import that walks around it. @playwright/test imported anywhere else is a test with no interception, and it looks the same as one that has it:

// .oxlintrc.json
{
  "rules": {
    "no-restricted-imports": ["error", {
      "paths": [{
        "name": "@playwright/test",
        "message": "Import test and expect from e2e/test.ts, which intercepts outgoing email."
      }]
    }]
  },
  "overrides": [{
    "files": ["e2e/test.ts", "playwright.config.ts"],
    "rules": { "no-restricted-imports": "off" }
  }]
}

The database matrix, and geonosis-testbed

// geonosis.json
{
  "testbed": {
    "matrix": {
      "command": "bun run --cwd packages/db test",
      "connectionEnv": "CELL_CONNECTION_STRING",
      "connectionString": "postgres://postgres:[email protected]:5432/postgres",
      "majors": ["17", "18"],
      "name": "cells",
      "setup": ["bun install --frozen-lockfile"]
    }
  }
}
geonosis-testbed matrix --out .github/workflows/cells.yml
geonosis-testbed strict -- packages/db      # judge the report, not the exit code

The rendered workflow is templates/ci/db-matrix.yml, which is checked in as the renderer's own output for the documented example and tested to still equal it — a template committed beside a generator drifts from it the first time either is touched, and then the thing people copy is not the thing the code produces.

Two jobs, not one, and that is the point. The source repo runs its data layer against Postgres 17 and 18 in a matrix, and against an embedded cluster in every other job — because the connection variable is unset there. The embedded leg is real, it is tested, and it is invisible: nothing in that workflow says the suite has a second backend. Here it is <name>-embedded, with a name.

What the renderer refuses: a matrix with no legs (embedded: false and no majors), no command, a job name that is not a name, and — the one that matters — majors with an empty connection string, which would run the embedded cell under a container's name and go green twice against the same database while claiming to have tested two.

fail-fast: false and the pg_isready health check are not decoration: one major failing must not hide whether the other passes, and without the health check the first connection races the container and the suite fails for a reason that is not in the code.

The rule: no-mock-db-in-integration

Preset testbed, in @geonosis/oxlint-plugin-biological-architecture.

"biological-architecture/no-mock-db-in-integration": ["error", {
  "dbModules": ["@repo/db"],
  "integrationDirs": ["__tests__/integration"],
  "mockNames": []
}]

An integration suite that mocks its database proves that a procedure calls what it says it calls, and nothing about what comes back — which is what an integration test exists to check.

Both consumers of this kit pass it today, measured: a Medusa storefront's 106 integration files contain zero vi.mock calls, and a Workers + D1 app's whole tree contains zero. The landmine is a third repo's, where 35 of 43 test files share one context helper carrying db: mockDb, and they are the gate. That is why integrationDirs and dbModules are required with no defaults — a default drawn from two repos that do not have the pattern would match nothing and report a clean tree it never looked at.

mockNames is for the shape no mock call can be seen in: a handle built in a setup file and threaded through a helper. It is empty by default; a repo that has one names it.

Adoption status (D-027)

Read on 2026-10-17, both consumers depend on this package. A Medusa storefront's integration runner is createMedusaIntegrationTest, and its adapter runs runAdapterConformance. A Workers + D1 app runs three packages' tests through geonosis-testbed strict and boots its cell with createCellServer; its local-only config and its reset are still its own. What follows is each migration in full, written from what each repo had when it was measured — see docs/testbed-inventory-2026-08-30.md for the numbers.

A Medusa backend

Replace the one integration runner every suite imports with:

import { medusaIntegrationTestRunner } from '@medusajs/test-utils'
import { createMedusaIntegrationTest } from '@geonosis/testbed/medusa-built'
import { describe } from 'vitest'

export const { adapterFor, runIntegrationTest } = createMedusaIntegrationTest({
  describe,
  root: resolve(import.meta.dirname, '../..'),
  runner: medusaIntegrationTestRunner,
})

Nothing else changes: the default builtDir is .medusa/server, the default derivation is the sha1(description).slice(0, 12) it already uses, and the default connectionEnv is the DB_* set its setup.ts already fills in. The same 106 suites must produce the same database names, which is the equivalence check — moduleName is printed by the engine's own Creating database … line.

What it gains on the day it lands: a refusal instead of a loader crash when .medusa/server is stale, a refusal instead of a silent connection to port 5432 when the DB_* environment is not set — the failure that moving Postgres from 5433 to 5434 made possible, and which lands on another project's database — and a refusal instead of a shared database when two suite descriptions collide in 48 bits of sha1.

Then .github/workflows/: a Medusa storefront's CI is 26 lines and has no integration job at all. 106 suites exist, none runs on a push. geonosis-testbed matrix renders one; majors for a Medusa backend is the Postgres versions it supports, and the embedded leg does not apply, so embedded: false.

no-mock-db-in-integration costs nothing to enable — 0 findings today, proven not silent in proofs/022-W9-consumers/.

a Workers + D1 app

Four replacements, none of which changes what runs:

| today | becomes | |---|---| | scripts/vitest-strict.ts (57 lines, the test script of 3 packages) | geonosis-testbed strict, or runStrict/verdictOf | | apps/api/testing/local-only-config.ts (98 lines) | deriveLocalOnlyConfig({ dropVars: ['LOCAL_AI_URL'], source }) | | packages/db/testing/cell-server.ts (72 lines) | createCellServer({ cluster, prepare }) with EmbeddedPostgres injected | | packages/db/testing/reset.ts | workerdAdapter({ before: ['drop trigger …'], tables }), or truncateAllStatement |

Its .github/workflows/ci.yml cells job becomes geonosis-testbed matrix output, with one difference that is the reason to do it: the embedded leg becomes a named job. Today it is real and tested and invisible, running in every job where CELL_CONNECTION_STRING happens to be unset.

Two behaviour changes it gets for free: stop() becomes idempotent, and a cluster whose prepare throws is stopped instead of left running with its data directory for the rest of the session.

Nothing its CLAUDE.md names is proposed for deletion here, per the 2026-08-30 boundary: §4.3's fixtures law stays its law — fixturesOnly is that law with three more refusals in it, not a replacement for the sentence.

Consumable from CommonJS

A Medusa backend is module: Node16 CommonJS by upstream requirement, not by preference. This package ships both builds and a require condition carrying its own .d.cts, so a static import type-checks and require() works:

import { defineTestbed } from '@geonosis/testbed'      // ESM, module: Node16 or ESNext
const { defineTestbed } = require('@geonosis/testbed') // CommonJS

An ESM-only package is a wall for such a backend, and no amount of parity gets over it: a Medusa storefront measured @geonosis/testbed at 15/15 identical and still could not adopt it, because a static import is TS1479 and await import() cannot serve the synchronous describe() registration its 107 suites are built on.