Backend Testing Tooling Plan
Goal
- Give backend module authors a first-class way to run route + contract tests from the CLI, including request helpers that understand the module manifest emitted by
@webstir-io/webstir-backend. - Provide a standardized harness that can spin up the compiled backend server (default scaffold or Fastify) for integration tests and dev loops without manual scripts.
- Fit into the existing
webstir test/webstir watchworkflows so backend coverage runs automatically in CI and duringwebstir watch.
Current State
webstir testcompiles TypeScript, builds the backend viaModuleBuildExecutor, then shells out towebstir-testing(webstir-dotnet/Engine/Workflows/TestWorkflow.cs:21-60). The runner simply executes compiled.test.jsfiles inside a Node VM (webstir-testing/src/runtime.ts), so backend tests cannot hit a real server or share bootstrapped context.- The manifest metadata and route definitions exposed by
backendProvider(webstir-backend/src/provider.ts) are only logged in dev/watch (webstir-dotnet/Engine/Services/DevService.cs:24-121). There is no automated harness to assert that handlers satisfy their contract or that exported routes respond correctly. - Developers that need integration coverage must manually start
node build/backend/index.js(or Fastify) in one terminal and run ad-hoc scripts in another, which does not exercise orchestrator plumbing, env toggles, or module events.
In Scope
- Extend the CLI surface so backend-only tests can be invoked directly (headless) or alongside
webstir watch. - Introduce a backend-aware testing provider that can start the compiled server, expose HTTP helpers, and hydrate route metadata/contracts for assertions.
- Wire readiness + teardown into
ProcessHandleso test workflows can block on a healthy/api/healthwithout bespoke scripts. - Document the harness (APIs, env vars, defaults) inside
webstir-portaland surface the workflow under the “Backend Design” plan.
Out of Scope
- Shipping new backend server scaffolds (Fastify already exists under
templates/backend/server/fastify.ts). - Replacing the existing
webstir-testingmanifest discovery logic. - Full E2E orchestration across frontend + backend + database (follow-up once backend harness lands).
Functional Requirements
- CLI entry points
webstir backend test [--watch] [--filter <glob>] [--dev-server <embedded|external>]routes to a dedicated workflow so backend modules do not need to scan frontend specs.webstir backend dev [--tests] [--port <number>]starts the backend watch pipeline (startBackendWatchfrom@webstir-io/webstir-backend/src/watch.ts) and optionally triggers backend tests on rebuild.- Existing
webstir test/webstir watchcommands should delegate to the backend harness when backend tests are present so CI does not need a new entry point.
- Harness behavior
- Each backend test run must build the backend in
ModuleBuildMode.Testand capture manifest routes before starting tests. - The harness should start the compiled backend entry (
build/backend/index.jsor Fastify) once, wait for the readiness log (API server running), expose the port + base URL to tests, then reuse the process across files unless a test requests isolation. - Provide a TypeScript helper (e.g.,
import { backendTest } from '@webstir-io/webstir-backend/testing') that wraps the generictest()API, injects a supertest-likerequest()helper, and preloads module routes + schema metadata.
- Each backend test run must build the backend in
- Contract assertions
- Helpers should be able to load the hydrated module manifest (routes, schemas) returned from the provider to ensure tests can compare responses against the declared contract (status codes, schema refs).
- Failures should emit
WEBSTIR_MODULE_EVENTlines so orchestrator logs mirror the existing test runner output.
- Dev server for integration
webstir backend dev --testsshould combine the esbuild watch (startBackendWatch) with the new harness: rebuild on change, restart the backend server, rerun backend specs, surface hot output via the Dev Service console.
Non-Functional Requirements
- Runs must work in air-gapped CI (no npm install during test time). All helpers must live in first-party packages (
@webstir-io/webstir-backend,@webstir-io/webstir-testing). - Server startup/teardown must respect the shared
ProcessHandletimeout + cancellation semantics (webstir-dotnet/Utilities/ProcessRunner/ProcessHandle.cs), so hung tests do not wedge the CLI. - The harness cannot assume a specific framework. It should treat the compiled backend entry as a black box that emits readiness logs and honors
PORT/NODE_ENV/API_BASE_URL. - Watch mode should debounce restarts (≥250 ms) to avoid thrashing when esbuild emits multiple files.
Proposed Architecture
1. CLI & Workflow updates (C#)
- Add
BackendTestWorkflowandBackendDevWorkflowinsidewebstir-dotnet/Engine/Workflows/.BackendTestWorkflowshares the existingTestWorkflowsteps (build, tsc, provider build) but injects a newBackendTestHarnessright afterCompileBackendAsync().BackendDevWorkflowwrapsstartBackendWatch(spawned vianode dist/watch.js) usingProcessRunner. It listens for the readiness log before marking the Dev Service “ready” and can optionally trigger backend tests after each successful build.
- Extend
webstir-dotnet/CLI/Help.cs+ DI wiring sowebstir backend test/devare discoverable. The legacywebstir testcommand will internally invokeBackendTestWorkflowwhen backend tests exist to keep defaults working.
2. Node-side harness (@webstir-io/webstir-backend/testing)
- New export:
createBackendTestHarness()that starts the compiled backend entry (defaults tobuild/backend/index.js, with Fastify fallback) and returns:type BackendTestContext = {
request: (init?: RequestInit & { path?: string }) => Promise<Response>;
manifest: ModuleManifest | null;
routes: readonly RouteSpec[];
url: URL;
env: Record<string, string>;
}; - Provide
backendTest(name, handler, options?)helper that mirrorstest()but injects the shared context. Under the hood it calls the existingtest()API provided by@webstir-io/webstir-testing. - Respect env contracts:
WEBSTIR_BACKEND_TEST_PORT(default 4100; auto-increment if busy).WEBSTIR_BACKEND_TEST_ENTRYto override the server entry (Fastify vs default).WEBSTIR_BACKEND_TEST_ONCEto restart per test file when isolation is required.
3. Testing provider integration (@webstir-io/webstir-testing)
- Introduce a backend-aware provider that satisfies the current
TestProviderinterface (webstir-testing/src/providers.ts). It wraps the VM runtime but augments the global withbackendTest. - Provider responsibilities:
- Before executing a file, launch the backend harness (if not already running) and share the context via a global symbol.
- After the file completes, stop or recycle the server based on
WEBSTIR_BACKEND_TEST_ONCE. - Emit module-level log events so the C# runner can show backend-specific failures distinctly.
- Backend tests live under
src/backend/tests/**/*.test.ts(current default), so no manifest changes are required.
4. Dev server plumbing
ProcessHandlealready exposesWaitForReadyAsync()with a readiness token. UpdateNodeServer(used byDevService) to watch for the backend readiness message sowebstir backend devcan block until the server is usable.- Watch mode flow:
- Spawn
node dist/watch.jsfrom@webstir-io/webstir-backend(honoringWEBSTIR_BACKEND_TYPECHECKtoggles). - On rebuild success, restart the runtime server (
node build/backend/index.js) and run backend tests (if--testswas passed). - Stream diagnostics to the console the same way
startBackendWatchcurrently prints esbuild messages.
- Spawn
Validation Strategy
- Unit: Add focused tests for
BackendTestHarness(mock server, verify retries) and CLI argument parsing. - Integration: Extend
Tester/Workflows/Test/TestWorkflowTests.cswith scenarios that include backend tests hitting a fake route (compile sample project, assert HTTP 200 using the harness). - Manual: Run
webstir backend dev --testsinside the sample workspace to verify hot rebuilds restart the server and re-run tests without hanging.
Rollout / Steps
- Ship the design doc (this file) and link it from
PLAN.md/TODO.md. - Implement Node harness + testing provider updates (
@webstir-io/webstir-backend+@webstir-io/webstir-testing). - Add new CLI workflows + help text in
webstir-dotnet. - Update docs (
webstir-portal/docs/how-to/test.md, CLI reference) with usage examples. - Enable backend tests inside
webstir watchonce stability is proven.
Open Questions
- Should the harness auto-provision database/auth adapters, or do we rely on user-provided test doubles initially?
- Do we need per-test isolation (restart server + reset module state) or is file-level isolation sufficient?
- How should we surface contract schema mismatches? (Option A: fail tests immediately; Option B: emit warnings and let assertions decide.)
- Where should we persist integration test fixtures (inline in tests vs
tests/fixtures/*with helper loaders)?