feat(app): give @equinor/fusion-framework-app its own /mock entry point - #5268
Conversation
…n the real configurator Closes equinor/fusion-core-tasks#1706 Replaces enableHttpMock/HttpMockConfigurator/HttpMockRouter with addMiddleware on the real HttpClientConfigurator, via a new HttpMiddlewareHandler that wraps _performFetch instead of swapping the configurator out. A middleware calling next(uri, init) falls through to the real call (or the next middleware) unchanged, so a test exercises the exact same client and configuration a real app registers. createRouterMiddleware and createOpenApiMockMiddleware replace the old router and its Express-style adapters, covering the same handler/OpenAPI-fake cases without depending on a real routing library.
Adds the http module's docs/testing.md via the same @include pattern used by client-configuration.md and server-sent-events.md, with a matching sidebar entry.
…ientConfigurator FrameworkMockConfigurator.http returns the real IHttpClientConfigurator now that module-http's mock support is addMiddleware on that real configurator, not a separate mock-specific one. Fake a response with .http.addMiddleware(...) instead of swapping the module out. Updates testing.md/testing-api.md/testing-extending.md to match.
enableHttpMock/http.get(...) no longer exist; rebuilds the fixture with configureHttp + createRouterMiddleware against configurator.addConfig, matching the addMiddleware-based mocking module-http now exposes. Updates the README's mention of the HTTP-mock alternative to match.
Part of equinor/fusion-core-tasks#1706 Adds MockAppClient, exported from a new ./mock subpath, so a test can serve one app's own manifest and config locally instead of contacting the app service: - getAppManifest resolves locally only for manifest.appKey with no tag. - getAppConfig resolves locally for manifest.appKey when tag is absent or equal to manifest.build?.version. - Every other request still goes through the real AppClient it wraps. Also exports AppConfig as a value from the package root (previously type-only), so a test can construct one directly.
Closes equinor/fusion-core-tasks#1706 Adds a ./mock entry point: mockAppModules runs an application's real module pipeline — the real event/http/msal modules, the real AppConfigurator configuration pipeline and real lifecycle — against a mocked parent Fusion instance, so a test exercises the wiring an application actually depends on instead of a reimplementation of it. AppMockConfigurator extends the real AppConfigurator, pinning http and msal to the same test doubles FrameworkMockConfigurator uses so .http/.msal are reachable synchronously; event is deliberately left unpinned since its configure factory needs a real ref. Its own addConfig override redirects a pinned module's later registrations at the pinned descriptor. The base AppConfigurator constructor's own call into _configureHttpClientsFromAppConfig is deferred (via a no-op override + an explicit super call after pinning) so it never runs before this class's own fields exist. enableAppManifestMock registers the app module on a parent mockFramework configurator, serving one app's manifest/config locally while delegating everything else to service discovery. mockAppModules uses it to build its zero-configuration default parent. Restructures README.md into an entry point pointing at docs/http-clients.md, docs/bookmarks.md and docs/testing.md, matching the convention already used by module-http and module-msal.
🦋 Changeset detectedLatest commit: 5c3b533 The changes in this PR will be included in the next version bump. This PR includes changesets to release 20 packages
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 |
initialize-app-modules.ts superseded this file when initializeAppModules gained the TConfigurator type parameter, but the old file was left in place -- nothing imported it, so it was dead code that still compiled and still linted (fusion-lint's require-tsdoc flagged it for the missing @template tags the newer file already has).
Long import lists wrapped onto multiple lines, a ternary split across lines, and a single-quoted string containing an apostrophe rewritten with double quotes -- no behavior change.
…nings mock/ only ever had one barrel (index.ts) plus an adapters/ subfolder holding the 3 files it re-exported -- two barrels doing the same job for exactly one category of file. Moved create-open-api-mock-middleware.ts, create-router-middleware.ts and resolve-open-api-mock-response.ts up into mock/ directly and dropped the adapters/ barrel, fixing relative import depths and OpenApiMockLike's re-export path along the way. Also fixes the fusion-lint warnings the files carried before the move: renamed each file to match its exported symbol (filename-convention), added the missing intent comments on create-router-middleware.ts's if-blocks and .map() (require-intent-comment), and replaced a comma-operator expression in compilePath with an explicit if/return (noCommaOperator).
There was a problem hiding this comment.
Pull request overview
Adds mock entry points for application pipelines and replaces the HTTP mock configurator with composable middleware.
Changes:
- Adds HTTP middleware, router, and OpenAPI mock adapters.
- Adds
MockAppClient,AppMockConfigurator, andmockAppModules. - Updates tests, documentation, exports, and changesets.
Reviewed changes
Copilot reviewed 61 out of 62 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
vue-press/src/modules/http/docs/testing.md |
Adds HTTP testing docs page. |
vue-press/src/.vuepress/sidebar.ts |
Links the testing guide. |
pnpm-lock.yaml |
Updates workspace test dependencies. |
packages/modules/http/tests/mock/router-middleware.test.ts |
Tests router middleware. |
packages/modules/http/tests/mock/module.test.ts |
Removes legacy module mock tests. |
packages/modules/http/tests/mock/HttpMockRouter.test.ts |
Removes legacy router tests. |
packages/modules/http/tests/mock/HttpMockConfigurator.test.ts |
Removes legacy configurator tests. |
packages/modules/http/tests/mock/adapters.test.ts |
Tests the OpenAPI middleware. |
packages/modules/http/tests/HttpMiddlewareHandler.test.ts |
Tests middleware composition. |
packages/modules/http/tests/HttpClient.test.ts |
Tests client middleware integration. |
packages/modules/http/src/provider.ts |
Propagates middleware into clients. |
packages/modules/http/src/mock/resolve-open-api-mock-response.ts |
Resolves OpenAPI mock responses. |
packages/modules/http/src/mock/module.ts |
Removes the legacy mock module. |
packages/modules/http/src/mock/index.ts |
Exports new mock utilities. |
packages/modules/http/src/mock/HttpMockRouter.ts |
Removes the old router. |
packages/modules/http/src/mock/HttpMockConfigurator.ts |
Removes the old configurator. |
packages/modules/http/src/mock/create-router-middleware.ts |
Adds lightweight route middleware. |
packages/modules/http/src/mock/create-open-api-mock-middleware.ts |
Adds OpenAPI middleware adapter. |
packages/modules/http/src/mock/create-http-client-mock-ctor.ts |
Removes the mock client constructor. |
packages/modules/http/src/mock/adapters/MockExpressResponse.ts |
Removes Express response adapter. |
packages/modules/http/src/mock/adapters/index.ts |
Removes legacy adapter exports. |
packages/modules/http/src/mock/adapters/from-open-api-mock.ts |
Removes the old OpenAPI adapter. |
packages/modules/http/src/mock/adapters/from-express-style-handler.ts |
Removes the Express adapter. |
packages/modules/http/src/lib/operators/types.ts |
Defines middleware contracts. |
packages/modules/http/src/lib/operators/index.ts |
Exports the middleware handler. |
packages/modules/http/src/lib/operators/HttpMiddlewareHandler.ts |
Implements middleware composition. |
packages/modules/http/src/lib/client/client.ts |
Runs requests through middleware. |
packages/modules/http/src/configurator.ts |
Adds middleware configuration APIs. |
packages/modules/http/README.md |
Updates HTTP testing guidance. |
packages/modules/http/docs/testing.md |
Documents middleware-based testing. |
packages/modules/context/src/__tests__/ContextModuleConfigurator.test.ts |
Migrates context tests to middleware. |
packages/modules/context/README.md |
Updates context mocking guidance. |
packages/modules/app/vitest.config.ts |
Configures module-app tests. |
packages/modules/app/tsconfig.json |
Adds the framework project reference. |
packages/modules/app/src/mock/MockAppClient.ts |
Adds local manifest/config responses. |
packages/modules/app/src/mock/index.ts |
Exports MockAppClient. |
packages/modules/app/src/index.ts |
Exports AppConfig as a value. |
packages/modules/app/src/__tests__/AppModuleProvider.test.ts |
Tests app provider HTTP integration. |
packages/modules/app/README.md |
Documents the app-module mock client. |
packages/modules/app/package.json |
Publishes the mock subpath and tests. |
packages/framework/src/mock/FrameworkMockConfigurator.ts |
Uses the real HTTP configurator. |
packages/framework/docs/testing.md |
Updates framework mock documentation. |
packages/framework/docs/testing-extending.md |
Documents middleware registration. |
packages/framework/docs/testing-api.md |
Updates the testing API reference. |
packages/app/src/mock/mock-app-modules.ts |
Adds app pipeline initialization helper. |
packages/app/src/mock/index.ts |
Publishes app mock exports. |
packages/app/src/mock/enable-app-manifest-mock.ts |
Wires local manifest/config responses. |
packages/app/src/mock/AppMockConfigurator.ts |
Adds pinned app mock configuration. |
packages/app/src/initialize-app-modules.ts |
Extracts the shared initialization pipeline. |
packages/app/src/configure-modules.ts |
Delegates to shared initialization. |
packages/app/src/__tests__/mock/mock-app.test.ts |
Tests app mock initialization. |
packages/app/src/__tests__/mock/AppMockConfigurator.test.ts |
Tests pinned HTTP and MSAL configuration. |
packages/app/README.md |
Documents the app mock entry point. |
packages/app/package.json |
Publishes the mock subpath and test script. |
packages/app/docs/testing.md |
Adds app testing guidance. |
packages/app/docs/http-clients.md |
Extracts HTTP client documentation. |
packages/app/docs/bookmarks.md |
Extracts bookmark documentation. |
.changeset/module-http_mock-entry-point.md |
Removes the superseded changeset. |
.changeset/module-http_addmiddleware.md |
Describes the HTTP middleware migration. |
.changeset/module-app_mock-client.md |
Describes MockAppClient. |
.changeset/framework_http-mock-real-configurator.md |
Describes the framework HTTP change. |
.changeset/app_mock-entry-point.md |
Describes the app mock entry point. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (1)
packages/modules/app/src/mock/MockAppClient.ts:70
- This also treats an explicit empty-string tag as absent, so the local config is returned even though the contract only permits an absent tag or the manifest build version. Use an
undefinedcheck so every other supplied tag delegates to the wrapped client.
const isOwnTag = !args.tag || args.tag === this.#manifest.build?.version;
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
getAppManifest's !args.tag and getAppConfig's isOwnTag both treated an
explicit empty-string tag the same as an absent one, so getAppManifest({
appKey, tag: '' }) and getAppConfig({ appKey, tag: '' }) answered locally
instead of delegating, contradicting the documented 'tag is absent' rule.
Compare against undefined explicitly instead of a falsy check.
Adds MockAppClient.test.ts covering both methods' local-vs-delegate
decision, including the explicit-empty-string-tag case.
The new vue-press page and sidebar entry is a consumer-facing docs change to the published @equinor/fusion-framework-docs package, which needed a changeset like every other vue-press page addition in this repo.
enableHttpMock, HttpMockConfigurator, and HttpMockRouter were removed from @equinor/fusion-framework-module-http's /mock entry point, and FrameworkMockConfigurator.http's return type changed from a mock-specific configurator to the real IHttpClientConfigurator -- both are breaking changes for existing consumers and need a major bump, not minor.
The http module registered by AppMockConfigurator is the real HttpClientConfigurator -- addMiddleware only short-circuits requests a middleware matches with a response, so an unmatched client or a middleware that calls next still reaches the real network. msal is the only module genuinely backed by a test double. Reworded the doc comment to describe what is actually substituted instead of promising no network access.
…bort() next(...) resolves through a Promise (HttpMiddlewareHandler's toPromise), so a middleware calling it creates a subscription to _performFetch that sits outside the tree takeUntil(this._abort$) tears down -- the outer request settled on abort(), but the underlying fetch kept running. abort() now also aborts a per-request AbortSignal combined into the request init, so _performFetch (fromFetch by default) is cancelled directly regardless of whether middleware severed the RxJS teardown chain. Adds a regression test with a pass-through middleware asserting the underlying fetch's signal is aborted.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 64 out of 65 changed files in this pull request and generated no new comments.
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
Suppressed comments (6)
packages/framework/src/mock/FrameworkMockConfigurator.ts:92
- Pinning the real HTTP module means an unhandled request can now reach the network, contradicting this class's “network access boundaries are substituted” documentation (lines 34–35) and
mock-framework.ts:19-25. Either retain fail-closed network isolation by default or update the public contract and migration notes to explicitly describe this behavioral break.
this._pin(httpModule);
packages/modules/http/src/lib/client/client.ts:382
- The abort controller and
_abort$subscription are created whenfetch$is called rather than when its cold observable is subscribed. An unsubscribed observable therefore leaks this subscription and can be pre-aborted by an earlierclient.abort(), while re-subscribing to the same observable after its first completion reuses a controller whose abort subscription was already removed; create this per subscription (for example withdefer) soabort()only targets requests that are actually in flight.
packages/framework/src/mock/FrameworkMockConfigurator.ts:92 _pin(httpModule)registers a cloned descriptor, but inheritedconfigureHttp/configureHttpClientlater calladdConfigwith the original descriptor;ModulesConfigurator.addConfigreplaces the clone, while.httpstill returns the now-detached pinned configurator. Middleware added through.httpcan then be absent from initialized clients and requests reach the network; redirect HTTP registrations to the pinned descriptor asAppMockConfigurator.addConfigdoes.
This issue also appears on line 92 of the same file.
this._pin(httpModule);
packages/modules/http/src/mock/create-router-middleware.ts:137
- This prefix check treats sibling paths such as
/v10as being under a/v1base and slices them as routed paths. Require either an exact base-path match or a/segment boundary so the router cannot answer requests outside its configured base URI.
packages/app/docs/testing.md:7 - This still promises that network access is substituted, but
mockAppModulesuses the real HTTP configurator and explicitly allows unmatched middleware to reach_performFetch. Update the guide to warn that this helper is not a network sandbox so tests do not accidentally contact live services.
configuration pipeline, and real lifecycle — while only the boundaries that reach
outside the process (network access, credentials, a running parent portal) are
substituted with deterministic fakes. This entry point has no dependency on
packages/modules/http/src/lib/operators/types.ts:161
- This describes the registration direction backwards: the implementation's
reduceRightmakes the first registered middleware outermost, so a newly registered middleware is wrapped by earlier middleware rather than wrapping it. Reword this to match the actual onion order and the surrounding API documentation.
Why is this change needed?
@equinor/fusion-framework-apphad no mock support at all — nomock/folder, no./mockexport.AppConfiguratorregisters its own realevent/http/msalmodules independently of whatever parentfusioninstance it's attached to, so passing a mockedfusionintoconfigureModulesonly mocked the parent framework's modules, not the app's own.What is the current behavior?
A test exercising an app's
configureModules(cb)had no way to run the app's own module pipeline end-to-end without a real network —http,msal, and anything acbconfigures throughuseFrameworkServiceClient/configureHttpClientall hit real endpoints.What is the new behavior?
@equinor/fusion-framework-module-http's mock support is redesigned:enableHttpMock/HttpMockConfigurator/HttpMockRouterare replaced withaddMiddlewareon the realHttpClientConfigurator, so a test wraps_performFetchinstead of swapping the configurator out.createRouterMiddlewareandcreateOpenApiMockMiddlewarecover what the old router and Express-style adapters did.FrameworkMockConfigurator.httpnow returns the realIHttpClientConfigurator, updated to match.@equinor/fusion-framework-module-appgets aMockAppClient(./mocksubpath) that serves one app's own manifest/config locally and falls through to the realAppClientfor everything else.@equinor/fusion-framework-appgets a./mockentry point:mockAppModulesruns an app's real module pipeline — realevent/http/msal, realAppConfiguratorpipeline, real lifecycle — against a mocked parent Fusion instance.AppMockConfiguratorpinshttp/msalto the same test doublesFrameworkMockConfiguratoruses.enableAppManifestMockregisters theappmodule on a mocked parent, serving one app's manifest/config while delegating everything else to service discovery.packages/modules/context's test fixture and README are updated off the removedenableHttpMockAPI.What is the intended behavior or invariant?
addMiddlewarethat callsnext(uri, init)falls through to the real call (or the next middleware) unchanged — only the boundary that would reach the network is ever short-circuited.AppMockConfigurator'saddConfigoverride only redirects a pinned module's registration at its pinned descriptor; the baseAppConfiguratorconstructor's own call into_configureHttpClientsFromAppConfigis deferred (no-op override + explicitsupercall after pinning) so it never runs beforeAppMockConfigurator's own fields exist — this was a real, reproduced crash (TypeError: Cannot read private member #pinnedModules from an object whose class did not declare it) before the fix.MockAppClient.getAppManifestresolves locally only whentagis absent entirely;getAppConfigresolves locally whentagis absent or equal tomanifest.build?.version— these two are not symmetric, and docs/changesets now say so correctly.Does this PR introduce a breaking change?
Yes, for
@equinor/fusion-framework-module-http's./mocksubpath:enableHttpMock,HttpMockConfigurator, andHttpMockRouterare removed in favor ofaddMiddleware+createRouterMiddleware/createOpenApiMockMiddleware.FrameworkMockConfigurator.http's return type changes from a mock-specific configurator to the realIHttpClientConfigurator. Both are covered by minor-bump changesets with migration examples;@equinor/fusion-framework-module-appand@equinor/fusion-framework-appchanges are additive.Impact assessment:
./mocksubpaths above (see changesets for migration).@equinor/fusion-framework-module-http,@equinor/fusion-framework,@equinor/fusion-framework-module-app,@equinor/fusion-framework-app(all via changesets).enableHttpMock/HttpMockConfigurator/HttpMockRouterAPI needs to migrate toaddMiddleware+ the new adapters —packages/modules/context's own test is a worked example of the migration.packages/modules/contextneeded a follow-up commit for exactly this reason; no other in-repo consumers of the old http mock API were found.Review guidance:
AppMockConfiguratorconstructor-ordering fix (packages/app/src/mock/AppMockConfigurator.ts) is the most subtle piece — worth reading the_configureHttpClientsFromAppConfigno-op override alongside the constructor together.pnpm -w testand fullbiome/fusion-lint/tsc -b --forcepassed for all touched packages before these commits were made.Additional context
Two independent LLM subagent reviews (different models) were used to critique this work before it was committed; one raised a hallucinated false-positive (disproven via direct filesystem/build checks), the other caught the real
AppMockConfiguratorconstruction-order bug fixed here, plus doc-accuracy issues (non-compiling manifest examples, an inaccurategetAppManifest/getAppConfigtag-matching description) that are also fixed in this PR.Related issues
closes: equinor/fusion-core-tasks#1706
Checklist