Skip to content

feat: H3 — reusable auth System + the testing seam (stubLoad, bootstrapService) - #39

Merged
wmadden-electric merged 10 commits into
mainfrom
claude/h3-reusable-system-testing
Jul 11, 2026
Merged

feat: H3 — reusable auth System + the testing seam (stubLoad, bootstrapService)#39
wmadden-electric merged 10 commits into
mainfrom
claude/h3-reusable-system-testing

Conversation

@wmadden-electric

Copy link
Copy Markdown
Contributor

The last slice of the system-composition project (H3): a reusable System and the testing seam that makes apps built on the framework testable, both proven.

What lands

  • Auth is now a reusable System that owns its database. system('auth', { expose: { rpc } }, …) provisions its own Postgres and the auth service internally and exposes only the RPC contract; @prisma/* are peer dependencies (publish-shaped). The app root composes it and wires auth.rpc into storefront — it no longer touches Postgres.
  • @prisma/app/testing → stubLoad — the unit seam. Mocks service.load() to return doubles typed against the service's own deps (a wrong-shaped double is a compile error). Test any code that pulls deps through load() — a page, an action — with no server and no environment.
  • bootstrapService — the integration seam. The in-process counterpart of the deploy bootstrap (runForTest(config, boot) reuses the same stash writer): boots the real entry with a config you choose and hands back a driveable server.

Both rest on one fact — every dependency flows through service.load() — documented in testing.md.

Proof

  • Unit — renders storefront's page.tsx with load() stubbed to a fake auth (vitest).
  • Integration — boots the real Next.js standalone storefront in-process against an in-process fake auth on a loopback port, and drives the round trip over HTTP (bun test). No cloud.
  • Live e2e — the existing "Deploy, verify, destroy" job now deploys the composed auth System + storefront to real Prisma Cloud. This is the reusable-System-deployed-for-real gate; it must be green to merge.

All repo gates green (typecheck, test, lint, build, casts delta 0).

…spec

design: docs/design/10-domains/testing.md — the two-altitude testing model.
Every dependency flows through one seam, service.load(); stubLoad replaces its
output (unit), bootstrapService feeds its input (integration), both without
touching the code under test.

drive: the H3 slice spec — reusable auth System (owns its db), the two testing
utilities (stubLoad in core, bootstrapService via a runForTest runnable seam),
the fake shipped from the auth package, and the three proofs (unit, integration,
live e2e). Two open items flagged for an operator ruling before dispatch.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Operator rulings: teardown via bun-test process isolation (no close(), server.ts
untouched); both the unit (stubLoad) and integration (bootstrapService) paths are
deliverables, integration boots the full Next storefront against a loopback fake
auth with a minimal-RPC-consumer fallback.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
auth's default export is now a System (src/system.ts) that provisions its
own Postgres and the compute service, wiring db in and exposing only the
rpc contract — no db input on the boundary. The root system.ts no longer
provisions the database; it provisions the auth System and wires its
exposed rpc into storefront. The package declares @prisma/* as peer
dependencies, standing in for how a published reusable System would ship.

Part of the H3 slice (reusable-system-testing).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@prisma/app/testing exports stubLoad(service, overrides): a service node
whose load() returns overrides merged with the service's own param
defaults, typed against the service's declared deps so a double of the
wrong shape fails to compile. Target-agnostic, no module mocking of its
own — that stays in the test. New ./testing tsdown entry regenerates the
package manifest.

Part of the H3 slice (reusable-system-testing).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
compute()'s runnable node gets runForTest(config, boot): stash the given
Config address-free (the same writer run() uses, minus the address ->
Config deserialize step) and call boot(). @prisma/app/testing wraps it
generically as bootstrapService(service, config): dynamically imports the
service's real entry (build.entry resolved against build.module, exactly
as the deploy bootstrap does) and hands back { url, fetch }. No close() —
teardown rides bun-test's per-file process isolation. server.ts is
untouched.

Part of the H3 slice (reusable-system-testing).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Auth ships a /fake export: an in-memory verify with no Postgres, sharing
the real authContract so the handler map is typed against the same
contract the real service exposes.

Storefront gets both proof tests: a unit test renders page.tsx with
load() stubbed to the fake via stubLoad (vitest, no server, no
environment), and an integration test drives the real Next.js standalone
entry through bootstrapService against the fake served on a loopback
port (bun test — needs Bun.serve, and the resolved no-close() teardown
decision rests on bun-test's per-file process isolation).

bootstrapService gains an optional third `boot` argument: its default
derivation (build.entry resolved against build.module) fits a `node`
build adapter, but nextjs's entry is a bare filename inside the Next
standalone output, not module-relative, so the integration test supplies
its own boot thunk built from @prisma/app-nextjs/control's
nextStandaloneDir — the same path math the deploy assembly step uses.
This makes the integration test boot the *real* Next.js storefront
in-process rather than falling back to a minimal RPC consumer.

Part of the H3 slice (reusable-system-testing).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@pkg-pr-new

pkg-pr-new Bot commented Jul 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma/alchemy

npm i https://pkg.pr.new/@prisma/alchemy@39

@prisma/app

npm i https://pkg.pr.new/@prisma/app@39

@prisma/app-assemble

npm i https://pkg.pr.new/@prisma/app-assemble@39

@prisma/app-cli

npm i https://pkg.pr.new/@prisma/app-cli@39

@prisma/app-cloud

npm i https://pkg.pr.new/@prisma/app-cloud@39

@prisma/app-nextjs

npm i https://pkg.pr.new/@prisma/app-nextjs@39

@prisma/app-node

npm i https://pkg.pr.new/@prisma/app-node@39

@prisma/app-rpc

npm i https://pkg.pr.new/@prisma/app-rpc@39

prisma-app

npm i https://pkg.pr.new/prisma-app@39

commit: 0f84d56

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to clarify the purpose of this file. Its content is so unrealistic it's impossible to infer what it's for, which IIUC is: use this fake auth service as a dummy service to test a system which depends on it. Is that right?

It probably doesn't belong in package src/; you wouldn't want it to reach production code. It ought to be a separate entrypoint under, eg, test.

vi.mock('../src/service.ts', async () => {
const actual = await vi.importActual<{ default: typeof Service }>('../src/service.ts');
return {
default: stubLoad(actual.default, {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stubLoad() is spectacularly hard to read. How about mockService(service, overrides)?

Comment thread packages/app-cloud/src/compute.ts Outdated
blindCast<
RunnableServiceNode<D, typeof computeParams, E>,
"the spread copies node's own enumerable data (including the Symbol.for brand) and adds run/load — exactly RunnableServiceNode's shape"
RunnableServiceNode<D, typeof computeParams, E> & Testable,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wtf is this? Why does our production code have testing code in it????

Comment thread packages/app-cloud/src/compute.ts Outdated
Comment on lines +64 to +67
async runForTest<T>(config: Config, boot: () => Promise<T>): Promise<T> {
stash(configOf(node), config);
return boot();
},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REJECTED

…oduction, mockService, doc truth

Your review (CHANGES_REQUESTED) + the Opus pass:

- Move the integration seam OUT of production code. `compute()` no longer carries
  runForTest/Testable — it ships only run/load. `bootstrapService` moves to a new
  target-specific `@prisma/app-cloud/testing`, doing stash+boot directly via the
  internal `stash` + `configOf`. `mockService` (the unit seam, renamed from the
  hard-to-read `stubLoad`) stays in core `@prisma/app/testing`.
- The fake moves out of production `src/` to a test-only entrypoint
  (`auth/testing/fake.ts`, still exported as `./fake`) with its purpose spelled
  out; storefronts production `next build` no longer type-checks test files.
- CodeQL: drop the regex comment-strip in the integration test; assert the exact
  render instead.
- Opus: rewrite `testing.md`s `bootstrapService` example to the shipped API
  (real Config shape, no `close()`, an explicit Next boot thunk, target-specific);
  single-source the Next standalone entry path (`standaloneEntryPath`, shared by
  `assemble` + the test); wire `test:types` into CI.

Gates green (typecheck, test, test:types, lint, build, casts delta 0); both proof
tests run live (unit + Next-standalone integration).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…0.22 bumps

Integrates main: dep bumps (next 15->16, tsdown 0.15->0.22, @types/node 25),
ADR-0018/0019/0020 (params/cron), the forcing-function-apps project.

Conflicts resolved:
- storefront tsconfig: main include (+.next/dev/types) with our test-file
  excludes (production next build must not type-check tests).
- storefront package.json: main @types/node/@types/react bumps, keeping our
  @types/bun (the bun integration test needs it).

Re-verified on the merged tree: gates green (typecheck, test, test:types, lint,
build, casts delta 0); both proof tests run live — the integration test boots
the real Next 16 standalone server against the fake auth. (A stale local
rolldown binary from the incremental install broke the build; a clean reinstall
fixed it — CI installs fresh.)

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The merge auto-merged a broken lockfile (missing the vitest peer-hash entry
after the @types/node bump); it was committed before the clean reinstall
regenerated it. Regenerate so --frozen-lockfile passes in CI.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…isma-apps skill

- Rewrite docs/design/10-domains/testing.md: lead with the decision, open with a
  worked example, build the topic up plainly, alternatives-considered at the
  end. Drops the contrived "replace/feed the seam" phrasing.
- Add skills/testing-prisma-apps: a user-facing how-to for mockService (unit)
  and bootstrapService (integration).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric
wmadden-electric force-pushed the claude/h3-reusable-system-testing branch from 67b1cee to 0f84d56 Compare July 11, 2026 17:12
@wmadden-electric
wmadden-electric merged commit 3368bb6 into main Jul 11, 2026
13 checks passed
wmadden-electric added a commit that referenced this pull request Jul 11, 2026
Reconcile H3's testing seam (#39: mockService, bootstrapService) with S1's
config()/load() split and schema-typed params:

- mockService now sets load() (deps) and config() (params) separately,
  splitting its flat override by dependency vs param key; drops the removed
  Loaded type.
- bootstrapService calls stash(node, config) (the node-based signature),
  not the old shape argument.
- testing.test.ts / testing.test-d.ts migrate off { type: 'string' } to
  string()/number() and read params through config().
- Commit the ./testing export both packages' tsdown config already emits.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants