feat(openapi-mock): add package for generating OpenAPI-shaped fake responses - #5204
Conversation
🦋 Changeset detectedLatest commit: 1f7b278 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…sponses
Adds @equinor/fusion-openapi-mock, a framework-agnostic utility that fakes
OpenAPI 3 responses straight from a parsed spec document. Every operation
with an operationId is faked from its declared success response schema,
with overrides/register for edge cases, seed for repeatable output, and
a FieldFakerMap for realistic per-field values via @faker-js/faker.
No dependency on any HTTP or routing framework: resolve({ method, path,
query }) returns a plain { status, mock }, so it composes with
@equinor/fusion-framework-module-http's mock router or any other server.
Related: equinor/fusion-core-tasks#1660
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
e7abda6 to
ce7ea98
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new published utility package, @equinor/fusion-openapi-mock, for generating OpenAPI-shaped fake responses (with overrides, seeding, and field-level faker mapping), plus accompanying tests, docs, and a changeset for the initial release.
Changes:
- Introduce
@equinor/fusion-openapi-mockpublic API (createOpenApiMock,fetchOpenApiDocument,loadFakerMap) and supporting internals for$refdereferencing and schema-based mock generation. - Add Vitest coverage for dereferencing, document fetching/parsing, sidecar faker maps, and resolve/override behavior.
- Add package metadata/docs + changeset, and update CODEMAP/lockfile to include the new package and dependencies.
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds the new package importer and locks new deps (json-schema-faker, yaml, etc.). |
| packages/utils/openapi-mock/vitest.config.ts | Vitest project config for the new package. |
| packages/utils/openapi-mock/tsconfig.json | TS build configuration for the new package (project references, output dirs). |
| packages/utils/openapi-mock/package.json | New published package manifest, scripts, and dependencies. |
| packages/utils/openapi-mock/README.md | Consumer-facing docs and usage examples for the new mock generator. |
| packages/utils/openapi-mock/src/index.ts | Public export surface for the package. |
| packages/utils/openapi-mock/src/types.ts | Public types for documents, mocks, overrides, and field faker mappings. |
| packages/utils/openapi-mock/src/load-faker-map.ts | Sidecar loader for FieldFakerMap (JSON/YAML/script via importConfig). |
| packages/utils/openapi-mock/src/fetch-open-api-document.ts | Fetch + parse helper for JSON/YAML OpenAPI docs. |
| packages/utils/openapi-mock/src/dereference-schema.ts | $ref inlining helper with cycle protection. |
| packages/utils/openapi-mock/src/generate-mock-from-schema.ts | Schema-to-value generation using json-schema-faker + @faker-js/faker. |
| packages/utils/openapi-mock/src/create-open-api-mock.ts | Core routing + operation indexing + override/resolve implementation. |
| packages/utils/openapi-mock/src/apply-field-fakers.ts | Field-level faker-map application by annotating schemas before generation. |
| packages/utils/openapi-mock/tests/load-faker-map.test.ts | Tests for sidecar parsing/loading behavior. |
| packages/utils/openapi-mock/tests/fetch-open-api-document.test.ts | Tests for JSON/YAML parsing and non-OK fetch handling. |
| packages/utils/openapi-mock/tests/dereference-schema.test.ts | Tests for $ref resolution, arrays, and cycle breaking. |
| packages/utils/openapi-mock/tests/create-open-api-mock.test.ts | Tests for routing, schema-based mocking, overrides, and seeding. |
| packages/utils/openapi-mock/tests/fixtures/fields-yaml.faker.yaml | YAML fixture for loadFakerMap. |
| packages/utils/openapi-mock/tests/fixtures/fields-ts.faker.ts | TS sidecar fixture (default export with real function). |
| packages/utils/openapi-mock/tests/fixtures/fields-json.faker.json | JSON fixture for loadFakerMap. |
| CODEMAP.md | Updates published package count and adds the new package to the map. |
| .changeset/openapi-mock_initial-release.md | Changeset for initial minor release of the new package. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
…e fallback, and validation - sort operations by specificity so literal routes win over templated ones - fall back to lowest declared numeric status when no 2xx/default response exists - validate parsed YAML faker maps and fetched documents are plain objects - use an isolated faker instance per generation instead of mutating the global singleton - refactor createOpenApiMock and apply-field-fakers into smaller, focused functions
- replace && shorthand and let-based branches with explicit if/early-return - extract resolveResponseSchema, parseDocumentText helpers - rewrite resolvePointer's pointer walk with Array#reduce instead of a mutable loop variable
Coverage Report
File Coverage
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (4)
packages/utils/openapi-mock/src/create-open-api-mock.ts:256
options.overridesentries are never validated against the operations in the document, so a misspelledoperationIdinoverridesis silently ignored (contradicting the@throwsdoc and the intent of failing fast on typos). Validate the keys eagerly and throw the same error message used elsewhere.
const operations = buildOperations(document);
const overrides = new Map<string, OpenApiMockOverride>(Object.entries(options.overrides ?? {}));
packages/utils/openapi-mock/src/index.ts:14
- The package-level TSDoc says
resolve()returns{ status, mock }, but the actual return type/value includesoperationIdandparamsas well. This mismatch can mislead consumers reading the generated docs.
* This package has no opinion on HTTP or routing frameworks: {@link OpenApiMock.resolve}
* takes a plain `{ method, path, query }` and returns a plain
* `{ status, mock }`, so it drops into `@equinor/fusion-framework-module-http`'s
.changeset/openapi-mock_initial-release.md:24
- The changeset claims
resolve({ method, path, query })returns{ status, mock }, but the public API actually returns{ status, mock, operationId, params }. Aligning this text avoids publishing misleading release notes for consumers.
- No dependency on any HTTP or routing framework: `resolve({ method, path, query })` returns a plain `{ status, mock }`, so it drops into `@equinor/fusion-framework-module-http`'s mock router, `openapi-backend`, Express, or a hand-rolled server equally easily.
packages/utils/openapi-mock/src/load-faker-map.ts:92
loadFakerMapvalidates YAML sidecars, but.json(and.ts/.js) sidecars loaded viaimportConfigare returned without runtime shape validation. A malformed.jsonmap (non-string values) will fail later in less obvious ways; validate the resolved config immediately and throw an error that names the resolved file.
// .json, .ts, .js, .mjs (or no extension) resolve through `importConfig`'s own
// extension probing, so a sidecar can be authored in whichever of those formats
// (real functions require .ts/.js/.mjs, since JSON can't hold one)
const basename = source.replace(/\.(json|ts|mjs|js)$/i, '');
const { config } = await importConfig<FieldFakerMap>(basename, { baseDir: options.baseDir });
return config;
Why is this change needed?
Extracted from the
poc/framework-testabilitybranch cleanup.@equinor/fusion-openapi-mockis a standalone, framework-agnostic utility and deserves its own history/PR rather than being entangled with the HTTP-mock-module and MSAL/service-discovery mock work. It also has its own tracked user story: equinor/fusion-core-tasks#1660.What is the current behavior?
No package exists for generating fake responses from an OpenAPI document. Testing an API-shaped client currently requires hand-written fixtures for every endpoint.
What is the new behavior?
Adds
@equinor/fusion-openapi-mock:createOpenApiMock(document, options?)fakes every operation with anoperationIdfrom its declared success response schema ($refs resolved against the document).overrides(at construction) and.register(operationId, handler)(afterwards) replace faked output for specific operations.seedmakes faked output repeatable across runs.fetchOpenApiDocument(url, options?)fetches/parses a JSON or YAML spec from a URL.fields(aFieldFakerMap) fakes specific fields with a@faker-js/fakerpath string or function;loadFakerMap(source)loads one from a sidecar file.resolve({ method, path, query })returns a plain{ status, mock }.What is the intended behavior or invariant?
resolve()returnsundefinedfor any request that matches no operation in the document, so callers (e.g.@equinor/fusion-framework-module-http's mock router viafromOpenApiMock) can compose it with other registrations covering endpoints outside the spec. The package has zero runtime dependency on any HTTP module — consumers duck-type againstOpenApiMockLike.Does this PR introduce a breaking change?
No — this is a new package.
Impact assessment:
@equinor/fusion-framework-module-http'sfromOpenApiMockadapter is duck-typed against this package's shape but has no hard dependencyReview guidance:
Focus on the public API surface (
createOpenApiMock,fetchOpenApiDocument,loadFakerMap) and TSDoc. Tests cover schema dereferencing, field-faker application, and mock generation from schema.Additional context
Extracted cleanly from
poc/framework-testability— verifiedpnpm --filter @equinor/fusion-openapi-mock test run,build,biome check, andfusion-lint lint packages/utils/openapi-mockall pass with no issues.Related issues
closes: equinor/fusion-core-tasks#1660
ref: equinor/fusion-core-tasks#1654
Checklist