Skip to content

Commit 2bdb7a4

Browse files
odinrCopilot
andcommitted
feat(framework): expose .http, .services, .context and .telemetry on FrameworkMockConfigurator
Pin the four remaining built-in modules whose configure factories don't depend on ref, so their real configurators are reachable the same way .msal and .serviceDiscovery already are. event is deliberately left unpinned since its configure factory depends on ref for parent-scope event bubbling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 00fc1ce commit 2bdb7a4

4 files changed

Lines changed: 119 additions & 8 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
"@equinor/fusion-framework": minor
3+
---
4+
5+
Add `.http`, `.services`, `.context` and `.telemetry` accessors to `FrameworkMockConfigurator`, alongside the existing `.msal` and `.serviceDiscovery`.
6+
7+
These four modules have no test double yet, so a client built through `.http`, or anything read through `.services`, `.context` or `.telemetry`, still performs real I/O — but their configurators are now reachable synchronously the same way `.msal` and `.serviceDiscovery` already are, since none of their `configure` factories depend on `ref`:
8+
9+
```typescript
10+
const configurator = new FrameworkMockConfigurator();
11+
configurator.http.configureClient('my-api', { baseUri: 'http://localhost:6669' });
12+
13+
const fusion = await init(configurator);
14+
```
15+
16+
`event` is intentionally left out: its `configure` factory reads `ref` to wire event bubbling to a parent event provider when `FrameworkMockConfigurator` is hoisted inside a host framework, and pinning it would call `configure()` with `ref` always `undefined` — silently disabling that bubbling.

packages/framework/docs/testing-extending.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,6 @@ class AppMockConfigurator extends FrameworkMockConfigurator<[InvoiceModule]> {
6262

6363
## What is not covered yet
6464

65-
The `http`, `services`, `context` and `telemetry` modules initialize but are not yet backed by test doubles, so anything issuing an actual request still reaches the network. Adding one means creating a `src/mock/` folder in **that module**, then pinning its configurator with `_pin` and exposing it with `_getConfig` on `FrameworkMockConfigurator` — the two lines the built-in mocks already use.
65+
`.http`, `.services`, `.context` and `.telemetry` are already reachable on `FrameworkMockConfigurator` — their `configure` factories take no `ref`, so they were safe to pin the same way `.msal` and `.serviceDiscovery` are. What is missing is a test double behind them: none of the four modules has a `src/mock/` folder yet, so anything issuing an actual request through them still reaches the network. Adding one means creating that folder in **that module**, then pinning its mock configurator with `_pin` and exposing it with `_getConfig` on `FrameworkMockConfigurator`, replacing the real module descriptor pinned there today.
66+
67+
`event` is not pinned at all, deliberately: its `configure` factory reads `ref` to wire event bubbling to a parent event provider when `FrameworkMockConfigurator` is hoisted inside a host framework. Pinning would call `configure()` with `ref` always `undefined`, silently breaking that bubbling — so it is left to build the normal way, from the module system's own configure phase, where `ref` is actually known.

packages/framework/src/__tests__/mock/mock-framework.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,34 @@ describe('FrameworkMockConfigurator', () => {
161161
expect(configurator.addModule(() => undefined)).toBe(configurator);
162162
});
163163

164+
it('exposes the same http configurator the http module is built from', async () => {
165+
const configurator = new FrameworkMockConfigurator();
166+
167+
configurator.http.configureClient('my-api', { baseUri: 'http://localhost:6669' });
168+
169+
const fusion = await init(configurator);
170+
171+
expect(fusion.modules.http.createClient('my-api')).toBeDefined();
172+
});
173+
174+
it('exposes the same services configurator the services module is built from', () => {
175+
const configurator = new FrameworkMockConfigurator();
176+
177+
expect(configurator.services).toBeDefined();
178+
});
179+
180+
it('exposes the same context configurator the context module is built from', () => {
181+
const configurator = new FrameworkMockConfigurator();
182+
183+
expect(configurator.context).toBeDefined();
184+
});
185+
186+
it('exposes the same telemetry configurator the telemetry module is built from', () => {
187+
const configurator = new FrameworkMockConfigurator();
188+
189+
expect(configurator.telemetry).toBeDefined();
190+
});
191+
164192
it('lets a module supplied through TModules get the same kind of accessor as msal and serviceDiscovery', async () => {
165193
// Standing in for an application subclassing FrameworkMockConfigurator to
166194
// expose its own module the same way the built-ins are exposed.

packages/framework/src/mock/FrameworkMockConfigurator.ts

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { AnyModule } from '@equinor/fusion-framework-module';
22

3+
import contextModule, {
4+
type ContextModuleConfigurator,
5+
} from '@equinor/fusion-framework-module-context';
6+
import httpModule, { type IHttpClientConfigurator } from '@equinor/fusion-framework-module-http';
7+
import type { HttpClientMsal } from '@equinor/fusion-framework-module-http/client';
38
import {
49
msalMockModule,
510
type MsalMockConfigurator,
@@ -8,21 +13,36 @@ import {
813
serviceDiscoveryMockModule,
914
type ServiceDiscoveryMockConfigurator,
1015
} from '@equinor/fusion-framework-module-service-discovery/mock';
16+
import servicesModule, { type IApiConfigurator } from '@equinor/fusion-framework-module-services';
17+
import telemetryModule, {
18+
type ITelemetryConfigurator,
19+
} from '@equinor/fusion-framework-module-telemetry';
1120

1221
import { FrameworkConfigurator } from '../FrameworkConfigurator.js';
1322

1423
/**
1524
* The real framework configurator, with every built-in module that reaches
16-
* outside the process backed by a test double.
25+
* outside the process backed by a test double, and every other built-in
26+
* module reachable the same way.
1727
*
1828
* @remarks
1929
* Nothing else changes: the same module set, the same configuration pipeline and
2030
* the same lifecycle are used. Only the boundaries that would need credentials or
2131
* network access are substituted, so a test still exercises module wiring,
2232
* configuration validation and lifecycle hooks.
2333
*
24-
* Each substituted module exposes its own mock configurator as a property, so a
25-
* test reaches it directly instead of registering a callback to receive it.
34+
* Every built-in module exposes its own configurator as a property, so a test
35+
* reaches it directly instead of registering a callback to receive it. `http`,
36+
* `services`, `context` and `telemetry` are not backed by test doubles yet, so
37+
* calls through their configurators still reach the network — but the
38+
* configurators themselves are reachable the same way `.msal` is, since their
39+
* `configure` factories take no `ref` and so lose nothing by being pinned
40+
* early.
41+
*
42+
* `event` is deliberately not pinned: its `configure` factory reads `ref` to
43+
* wire bubbling to a parent event provider when this configurator is hoisted
44+
* inside a host framework, and pinning would freeze that decision before a
45+
* `ref` could ever be known.
2646
*
2747
* Because this *is* a `FrameworkConfigurator`, every `enableX` helper an
2848
* application already uses accepts it unchanged — including the ones an
@@ -54,12 +74,15 @@ export class FrameworkMockConfigurator<
5474
constructor() {
5575
super();
5676

57-
// Pinning the built-in mocks up front — rather than waiting for `.msal` or
58-
// `.serviceDiscovery` to be read — is what replaces the real `auth` and
59-
// `serviceDiscovery` modules registered by `FrameworkConfigurator`'s own
60-
// constructor, whether or not a test ever touches the accessor.
77+
// Pinning up front — rather than waiting for an accessor to be read — is
78+
// what replaces the modules `FrameworkConfigurator`'s own constructor
79+
// already registered, whether or not a test ever touches the accessor.
6180
this._pin(msalMockModule);
6281
this._pin(serviceDiscoveryMockModule);
82+
this._pin(httpModule);
83+
this._pin(servicesModule);
84+
this._pin(contextModule);
85+
this._pin(telemetryModule);
6386
}
6487

6588
/**
@@ -147,6 +170,48 @@ export class FrameworkMockConfigurator<
147170
return this._getConfig<ServiceDiscoveryMockConfigurator>(serviceDiscoveryMockModule.name);
148171
}
149172

173+
/**
174+
* Configures the HTTP module's named clients.
175+
*
176+
* @remarks
177+
* The real configurator — `http` has no test double yet, so this still
178+
* reaches the network unless a test also gives the client in question its
179+
* own `setClient` seam.
180+
*/
181+
public get http(): IHttpClientConfigurator<HttpClientMsal> {
182+
return this._getConfig<IHttpClientConfigurator<HttpClientMsal>>(httpModule.name);
183+
}
184+
185+
/**
186+
* Configures the typed API clients the `services` module builds.
187+
*
188+
* @remarks
189+
* The real configurator — `services` has no test double yet.
190+
*/
191+
public get services(): IApiConfigurator {
192+
return this._getConfig<IApiConfigurator>(servicesModule.name);
193+
}
194+
195+
/**
196+
* Configures context resolution.
197+
*
198+
* @remarks
199+
* The real configurator — `context` has no test double yet.
200+
*/
201+
public get context(): ContextModuleConfigurator {
202+
return this._getConfig<ContextModuleConfigurator>(contextModule.name);
203+
}
204+
205+
/**
206+
* Configures telemetry.
207+
*
208+
* @remarks
209+
* The real configurator — `telemetry` has no test double yet.
210+
*/
211+
public get telemetry(): ITelemetryConfigurator {
212+
return this._getConfig<ITelemetryConfigurator>(telemetryModule.name);
213+
}
214+
150215
/**
151216
* Registers a module through its own enabler.
152217
*

0 commit comments

Comments
 (0)