@zenera/faker
v1.1.23
Published
Mock HTTP server for swagger/OpenAPI documents, with response bodies generated by a model.
Maintainers
Readme
@zenera/faker
A mock HTTP server for swagger/OpenAPI documents. Point it at a spec and it serves it - the response bodies are written, once, by a model.
Part of ZeneraNeo. It ships no binary of its own: installing it adds a
fakersubcommand tozen, which is also where the credentials already are.
Install
Node.js 24+ and podman. Install it alongside the CLI:
npm i -g @zenera/cli @zenera/faker
zen key add openai # the keyring `zen` already usesThe first start builds one image; every start after that reuses it. zen --help
lists faker whether or not it is installed, and says what to run if not.
Use
zen faker serve api/openapi.yaml --port 8787
curl -s localhost:8787/users/12324
# { "user_id": 12324, "email": "[email protected]", ... }More than one document at a time is fine, and the same request answers the same way every time when you pin a seed:
zen faker serve specs/*.yaml --seed 42Warm it up before a demo or a test run, so no request pays for a model turn:
zen faker build api/openapi.yaml # write every generator now and exit
zen faker cache ls # what has been generated
zen faker cache clear # throw it awayzen mock is the same command under a shorter noun.
A path key with a query string on it — /x/{id}?action=retry, which VMware NSX
and others write when they need two posts under one path — is read for what
it means rather than taken verbatim: the path is the part before the ?, and
each pair after it becomes a required query value that tells those operations
apart. Call one without it and the 404 says which queries the path answers to.
How a body is produced
The first time an operation is called, the faker asks a model to write a
Python generator for it - one file, taking a JSON input path and a JSON
output path. That file is then run against sample requests it makes up, and
judged twice: against the operation's response schema, and against the echo
rule, which says that where a path or query parameter shares a name with a
property in the response, the response has to carry the value that was asked
for. GET /users/12324 answering with somebody else's id validates perfectly
and is still wrong.
If it fails, the diagnostics go back to the model and it tries again, up to
--attempts. If it passes, the generator is kept in this machine's shared cache
under ~/.zenera/neo/cache/faker-generator/, keyed by the operation's shape, and
every later request is just podman exec python3 gen.py in.json out.json - no
model, no tokens. The store is the machine's, so the same document served from
another directory costs nothing the second time.
Generators run in a container with no network, on an image baked once with
faker, exrex, jsonschema and python-dateutil.
Pages that end
A list endpoint is the one place a mock can hang a real client. Given
?cursor=abc, the honest-looking answer is a body that validates, echoes
nothing it shouldn't, and hands back abc again - so the client asks for the
same page forever.
The faker reads the document for this. Where an operation has a paging
parameter (cursor, page, offset, page_token, …) in query or request body
and a response property that carries the next one (next, next_cursor, has_more, …),
three things happen, all in the operation's own names:
- the model is told to fabricate between 1 and 10 pages in total, to build the token
out of the paging parameter rather than the seed, and to end the list - null,
absent, or
has_more: false(and empty items) where the schema leaves no other room; - the generator is then walked: the faker calls it with no cursor, follows the token it gets back, and rejects the file if the token repeats, cycles, or never runs out. The diagnostics say which, and the model gets another go;
- at request time, pagination is tracked and bounded: a token identical to the one just sent is cut (nulled or dropped), cycles are stopped, and streams are capped to random 1-10 pages, returning empty results if a client loops past the end. The request line narrates each cut. Nothing is invented in its place; a generator written before this rule existed is still cached, and a cache is not rebuilt because a rule changed.
Only paginated operations are affected. Their cache keys changed once, so they
are written again on first use; everything else keeps the key it had.
GET /__faker/routes reports the shape that was recognised, per operation.
Plenty of documents describe the envelope and never write down the parameter
that reads it back. The first two steps cannot help there - nothing static can
see a parameter that is not declared - but the cut still applies: it takes the
paging parameter from query parameters or request body, since a client only sends
cursor=X because a body handed it X.
Commands
zen faker serve <spec...> Serve them. Generators are written on demand.
zen faker build <spec...> Write every generator now and exit.
zen faker cache ls | clear What has been generated, or throw it away.
zen faker model [ref] Show or set the model it uses.The model is looked for in this order: --model, ZENERA_FAKER_MODEL, what
zen faker model <provider>:<id> stored, then the best one your keys can buy.
The stored one lives beside the keyring in ~/.zenera/neo/faker.json, so it is
remembered between runs and across projects; zen faker model --clear forgets
it.
Useful options: --port, --host (reachable only from this machine by
default), --model, --seed (same request, same answer), --rebuild,
--attempts, --concurrency, --timeout, --cache <dir> (the container's
workspace), --quiet. zen help faker prints the full table.
GET / is a contents page: every operation the mock is serving, grouped by the
document it came from, with its parameters and the shape of its answer. Each
one has a Run button, which opens a form — a box per parameter, required
ones marked, a body prefilled with something the mock will accept — and shows
the status, the time and the answer. GET /__faker/routes is the same thing as
JSON; GET /__faker/health is a health check. A document that declares /
itself keeps it.
Credentials
The keyring is zen's, so there is nothing new to configure:
zen key add openai
zen key ls --checkEnvironment variables still win over the keyring, exactly as they do for zen.
The rest of the family
| Package | What it is |
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| @zenera/cli | zen - agent projects on the command line |
| @zenera/neo | the runtime - agents, models, tools, skills, memory |
| @zenera/rag | zen rag - an API description as a searchable graph |
License
MIT.
