@equinor/fusion-framework 8.1.0 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +12 -9
- package/CHANGELOG.md +0 -1961
- package/docs/testing-api.md +0 -25
- package/docs/testing-choosing-a-layer.md +0 -90
- package/docs/testing-design.md +0 -63
- package/docs/testing-extending.md +0 -102
- package/docs/testing.md +0 -178
- package/src/FrameworkConfigurator.ts +0 -273
- package/src/__tests__/FrameworkConfigurator.test.ts +0 -35
- package/src/__tests__/mock/application-module.test.ts +0 -87
- package/src/__tests__/mock/documented-usage.test.ts +0 -98
- package/src/__tests__/mock/mock-framework.test.ts +0 -278
- package/src/index.ts +0 -44
- package/src/init.ts +0 -61
- package/src/mock/FrameworkMockConfigurator.ts +0 -270
- package/src/mock/index.ts +0 -62
- package/src/mock/mock-framework.ts +0 -72
- package/src/types.ts +0 -82
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -36
- package/vitest.config.ts +0 -10
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { type AnyModule, ModulesConfigurator } from '@equinor/fusion-framework-module';
|
|
2
|
-
|
|
3
|
-
import event from '@equinor/fusion-framework-module-event';
|
|
4
|
-
|
|
5
|
-
import http, {
|
|
6
|
-
configureHttpClient,
|
|
7
|
-
configureHttp,
|
|
8
|
-
type HttpClientOptions,
|
|
9
|
-
} from '@equinor/fusion-framework-module-http';
|
|
10
|
-
import type { HttpClientMsal } from '@equinor/fusion-framework-module-http/client';
|
|
11
|
-
|
|
12
|
-
import auth, { type AuthConfigFn } from '@equinor/fusion-framework-module-msal';
|
|
13
|
-
|
|
14
|
-
import context from '@equinor/fusion-framework-module-context';
|
|
15
|
-
|
|
16
|
-
import disco from '@equinor/fusion-framework-module-service-discovery';
|
|
17
|
-
import services from '@equinor/fusion-framework-module-services';
|
|
18
|
-
import telemetry, { enableTelemetry } from '@equinor/fusion-framework-module-telemetry';
|
|
19
|
-
|
|
20
|
-
import type { FusionModules } from './types.js';
|
|
21
|
-
import type { MsalClientConfig } from '@equinor/fusion-framework-module-msal';
|
|
22
|
-
import { version } from './version.js';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Configurator that registers and wires the core Fusion Framework modules.
|
|
26
|
-
*
|
|
27
|
-
* Extend `FrameworkConfigurator` to declare which modules (authentication,
|
|
28
|
-
* HTTP, service-discovery, telemetry, context, …) the framework instance
|
|
29
|
-
* should contain and how each module is configured before the framework
|
|
30
|
-
* starts.
|
|
31
|
-
*
|
|
32
|
-
* Typical workflow:
|
|
33
|
-
* 1. Create a `FrameworkConfigurator` instance.
|
|
34
|
-
* 2. Call configuration helpers such as {@link configureMsal},
|
|
35
|
-
* {@link configureHttp}, {@link configureHttpClient}, and
|
|
36
|
-
* {@link configureServiceDiscovery}.
|
|
37
|
-
* 3. Pass the configurator to {@link init} to bootstrap the framework.
|
|
38
|
-
*
|
|
39
|
-
* @template TModules - Additional module descriptors to merge with the
|
|
40
|
-
* built-in Fusion modules.
|
|
41
|
-
* @template TRef - Optional reference object forwarded to module
|
|
42
|
-
* initialization (e.g. a parent framework instance).
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```typescript
|
|
46
|
-
* import { FrameworkConfigurator, init } from '@equinor/fusion-framework';
|
|
47
|
-
*
|
|
48
|
-
* const configurator = new FrameworkConfigurator();
|
|
49
|
-
* configurator.configureMsal({ clientId: 'my-client-id', authority: '…' });
|
|
50
|
-
* configurator.configureServiceDiscovery({
|
|
51
|
-
* client: { baseUri: 'https://service-registry.example.com' },
|
|
52
|
-
* });
|
|
53
|
-
*
|
|
54
|
-
* const fusion = await init(configurator);
|
|
55
|
-
* ```
|
|
56
|
-
*/
|
|
57
|
-
export class FrameworkConfigurator<
|
|
58
|
-
TModules extends Array<AnyModule> = [],
|
|
59
|
-
// biome-ignore lint/suspicious/noExplicitAny: default must be bivariant `any`, not `unknown` \u2014 `unknown` breaks assignability when a concrete `FrameworkConfigurator<TModules, TRef>` is used where the default-typed class is expected
|
|
60
|
-
TRef = any,
|
|
61
|
-
> extends ModulesConfigurator<FusionModules<TModules>, TRef> {
|
|
62
|
-
/**
|
|
63
|
-
* The class name used for event naming. This static property ensures
|
|
64
|
-
* the name is preserved through compilation and minification.
|
|
65
|
-
*/
|
|
66
|
-
static readonly className: string = 'FrameworkConfigurator';
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Creates a new FrameworkConfigurator instance with default telemetry configuration.
|
|
70
|
-
*
|
|
71
|
-
* Initializes the framework with core modules (event, auth, http, service discovery,
|
|
72
|
-
* services, context, and telemetry) and sets up default telemetry that includes
|
|
73
|
-
* framework version metadata and 'framework' scope for all telemetry events.
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* ```typescript
|
|
77
|
-
* const configurator = new FrameworkConfigurator();
|
|
78
|
-
* // Now ready to configure additional modules
|
|
79
|
-
* ```
|
|
80
|
-
*/
|
|
81
|
-
constructor() {
|
|
82
|
-
super([event, auth, http, disco, services, context, telemetry]);
|
|
83
|
-
|
|
84
|
-
// default configuration
|
|
85
|
-
enableTelemetry(this, {
|
|
86
|
-
configure: (builder) => {
|
|
87
|
-
builder.setMetadata({
|
|
88
|
-
fusion: {
|
|
89
|
-
type: 'framework-telemetry',
|
|
90
|
-
framework: {
|
|
91
|
-
version,
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
builder.setDefaultScope(['framework']);
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Configures the global HTTP module settings such as base URLs, default headers,
|
|
102
|
-
* request/response interceptors, and timeout settings.
|
|
103
|
-
*
|
|
104
|
-
* This affects all HTTP requests made through the framework's HTTP module.
|
|
105
|
-
* Use this for application-wide HTTP configuration.
|
|
106
|
-
*
|
|
107
|
-
* @param args - HTTP module configuration arguments (same as configureHttp function)
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* ```typescript
|
|
111
|
-
* configurator.configureHttp({
|
|
112
|
-
* baseUri: 'https://api.example.com',
|
|
113
|
-
* defaultHeaders: { 'X-App-Version': '1.0.0' }
|
|
114
|
-
* });
|
|
115
|
-
* ```
|
|
116
|
-
*/
|
|
117
|
-
public configureHttp(...args: Parameters<typeof configureHttp>) {
|
|
118
|
-
this.addConfig(configureHttp(...args));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Configures a named HTTP client instance with specific settings.
|
|
123
|
-
*
|
|
124
|
-
* Unlike configureHttp which sets global HTTP settings, this creates a named client
|
|
125
|
-
* that can have its own base URL, headers, interceptors, and other HTTP-specific
|
|
126
|
-
* configuration. Useful for connecting to different APIs or services with different requirements.
|
|
127
|
-
*
|
|
128
|
-
* @param args - HTTP client configuration arguments: [name, clientOptions]
|
|
129
|
-
* where name is a string identifier and clientOptions contains HTTP settings
|
|
130
|
-
*
|
|
131
|
-
* @example
|
|
132
|
-
* ```typescript
|
|
133
|
-
* // Configure a client for external API
|
|
134
|
-
* configurator.configureHttpClient('external-api', {
|
|
135
|
-
* baseUri: 'https://external-api.com',
|
|
136
|
-
* defaultHeaders: { 'Authorization': 'Bearer token' }
|
|
137
|
-
* });
|
|
138
|
-
*
|
|
139
|
-
* // Configure a client for internal services
|
|
140
|
-
* configurator.configureHttpClient('internal', {
|
|
141
|
-
* baseUri: 'https://internal.company.com',
|
|
142
|
-
* timeout: 10000
|
|
143
|
-
* });
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
public configureHttpClient(...args: Parameters<typeof configureHttpClient>) {
|
|
147
|
-
this.addConfig(configureHttpClient(...args));
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Configures Microsoft Authentication Library (MSAL) authentication for the framework.
|
|
152
|
-
*
|
|
153
|
-
* This sets up OAuth 2.0 / OpenID Connect authentication using Azure AD or other
|
|
154
|
-
* MSAL-compatible identity providers. The authentication module will handle token
|
|
155
|
-
* acquisition, refresh, and user session management.
|
|
156
|
-
*
|
|
157
|
-
* @param cb_or_config - Authentication configuration. Can be either:
|
|
158
|
-
* - A callback function that receives an auth builder for advanced configuration
|
|
159
|
-
* - A client configuration object with MSAL settings
|
|
160
|
-
* @param requiresAuth - Whether the application requires authentication to function.
|
|
161
|
-
* When true (default), unauthenticated users cannot access the app.
|
|
162
|
-
* When false, authentication is optional but available when needed.
|
|
163
|
-
*
|
|
164
|
-
* @example
|
|
165
|
-
* ```typescript
|
|
166
|
-
* // Simple configuration with client config object
|
|
167
|
-
* configurator.configureMsal({
|
|
168
|
-
* clientId: 'your-client-id',
|
|
169
|
-
* authority: 'https://login.microsoftonline.com/your-tenant-id'
|
|
170
|
-
* });
|
|
171
|
-
*
|
|
172
|
-
* // Advanced configuration with callback
|
|
173
|
-
* configurator.configureMsal((builder) => {
|
|
174
|
-
* builder.setClientConfig({
|
|
175
|
-
* clientId: 'your-client-id',
|
|
176
|
-
* authority: 'https://login.microsoftonline.com/your-tenant-id'
|
|
177
|
-
* });
|
|
178
|
-
* builder.setScopes(['User.Read', 'api://your-api/scope']);
|
|
179
|
-
* builder.setRequiresAuth(true);
|
|
180
|
-
* });
|
|
181
|
-
*
|
|
182
|
-
* // Optional authentication
|
|
183
|
-
* configurator.configureMsal(config, false);
|
|
184
|
-
* ```
|
|
185
|
-
*/
|
|
186
|
-
public configureMsal(cb_or_config: AuthConfigFn | MsalClientConfig, requiresAuth = true) {
|
|
187
|
-
this.addConfig({
|
|
188
|
-
module: auth,
|
|
189
|
-
configure: (builder) => {
|
|
190
|
-
// Only override the default requiresAuth flag when the caller explicitly set one
|
|
191
|
-
if (requiresAuth !== undefined) {
|
|
192
|
-
builder.setRequiresAuth(!!requiresAuth);
|
|
193
|
-
}
|
|
194
|
-
// A function argument configures the builder directly
|
|
195
|
-
if (typeof cb_or_config === 'function') {
|
|
196
|
-
cb_or_config(builder);
|
|
197
|
-
}
|
|
198
|
-
// A plain object argument is applied as the MSAL client config
|
|
199
|
-
if (typeof cb_or_config === 'object') {
|
|
200
|
-
builder.setClientConfig(cb_or_config);
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Configures the service discovery module to automatically find and connect to services.
|
|
208
|
-
*
|
|
209
|
-
* Service discovery allows the framework to dynamically locate microservices and APIs
|
|
210
|
-
* at runtime rather than hardcoding their URLs. This is essential in distributed systems
|
|
211
|
-
* where services may be deployed across multiple environments or scaled dynamically.
|
|
212
|
-
*
|
|
213
|
-
* @param args - Configuration object for service discovery
|
|
214
|
-
* @param args.client - HTTP client configuration used to communicate with the
|
|
215
|
-
* service discovery registry. Typically includes base URL,
|
|
216
|
-
* authentication, and timeout settings.
|
|
217
|
-
*
|
|
218
|
-
* @example
|
|
219
|
-
* ```typescript
|
|
220
|
-
* configurator.configureServiceDiscovery({
|
|
221
|
-
* client: {
|
|
222
|
-
* baseUri: 'https://service-registry.company.com',
|
|
223
|
-
* defaultHeaders: { 'Authorization': 'Bearer token' },
|
|
224
|
-
* timeout: 5000
|
|
225
|
-
* }
|
|
226
|
-
* });
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
public configureServiceDiscovery(args: { client: HttpClientOptions<HttpClientMsal> }) {
|
|
230
|
-
this.configureHttpClient('service_discovery', args.client);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Configures application telemetry and observability settings.
|
|
235
|
-
*
|
|
236
|
-
* Telemetry enables tracking of application usage, performance metrics, errors,
|
|
237
|
-
* and custom events. This helps with monitoring, debugging, and understanding
|
|
238
|
-
* how your application is being used. The framework comes with default telemetry
|
|
239
|
-
* that includes framework version and scope information.
|
|
240
|
-
*
|
|
241
|
-
* @param cb - Configuration callback function that receives a telemetry builder.
|
|
242
|
-
* Use this to customize telemetry settings like event scopes, metadata,
|
|
243
|
-
* sampling rates, and custom instrumentation.
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* ```typescript
|
|
247
|
-
* configurator.configureTelemetry((builder) => {
|
|
248
|
-
* // Add custom metadata to all telemetry events
|
|
249
|
-
* builder.setMetadata({
|
|
250
|
-
* application: 'my-app',
|
|
251
|
-
* environment: 'production',
|
|
252
|
-
* version: '1.2.3'
|
|
253
|
-
* });
|
|
254
|
-
*
|
|
255
|
-
* // Set custom scopes for filtering events
|
|
256
|
-
* builder.setDefaultScope(['app', 'performance']);
|
|
257
|
-
*
|
|
258
|
-
* // Configure sampling (only send 10% of events)
|
|
259
|
-
* builder.setSamplingRate(0.1);
|
|
260
|
-
*
|
|
261
|
-
* // Add custom instrumentation
|
|
262
|
-
* builder.addInstrumentation('custom-operation', (context) => {
|
|
263
|
-
* // Track custom metrics
|
|
264
|
-
* });
|
|
265
|
-
* });
|
|
266
|
-
* ```
|
|
267
|
-
*/
|
|
268
|
-
public configureTelemetry(cb: Parameters<typeof enableTelemetry>[1]): void {
|
|
269
|
-
enableTelemetry(this, cb);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export default FrameworkConfigurator;
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { firstValueFrom, take } from 'rxjs';
|
|
3
|
-
import { FrameworkConfigurator } from '../FrameworkConfigurator.js';
|
|
4
|
-
import type { AnyModule } from '@equinor/fusion-framework-module';
|
|
5
|
-
import { SemanticVersion } from '@equinor/fusion-framework-module';
|
|
6
|
-
|
|
7
|
-
describe('FrameworkConfigurator', () => {
|
|
8
|
-
let configurator: FrameworkConfigurator;
|
|
9
|
-
|
|
10
|
-
// Create a mock module for testing
|
|
11
|
-
const createMockModule = (name: string, version = '1.0.0'): AnyModule => ({
|
|
12
|
-
name,
|
|
13
|
-
version: new SemanticVersion(version),
|
|
14
|
-
initialize: () => ({ mockInstance: true }),
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
configurator = new FrameworkConfigurator();
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
describe('Event Name Prefixing', () => {
|
|
22
|
-
it('should prefix event names with "FrameworkConfigurator::"', async () => {
|
|
23
|
-
// Trigger event by adding a config
|
|
24
|
-
configurator.addConfig({
|
|
25
|
-
module: createMockModule('test', '1.0.0'),
|
|
26
|
-
configure: () => {},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// Wait for the first event to be emitted
|
|
30
|
-
const event = await firstValueFrom(configurator.event$.pipe(take(1)));
|
|
31
|
-
|
|
32
|
-
expect(event.name).toMatch(/^FrameworkConfigurator::/);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { IModulesConfigurator, Module } from '@equinor/fusion-framework-module';
|
|
4
|
-
|
|
5
|
-
import { mockFramework } from '../../mock/index.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* A module an application team owns, which the framework knows nothing about.
|
|
9
|
-
*
|
|
10
|
-
* @remarks
|
|
11
|
-
* The point of these tests is that nothing below required support from
|
|
12
|
-
* `@equinor/fusion-framework/mock`. If an application team can do this, the
|
|
13
|
-
* extension pattern holds.
|
|
14
|
-
*/
|
|
15
|
-
interface InvoiceClient {
|
|
16
|
-
getInvoice(id: string): Promise<{ id: string; total: number }>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
class InvoiceConfigurator {
|
|
20
|
-
#client?: InvoiceClient;
|
|
21
|
-
|
|
22
|
-
public setClient(client: InvoiceClient): void {
|
|
23
|
-
this.#client = client;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
public createClient(): InvoiceClient {
|
|
27
|
-
if (!this.#client) {
|
|
28
|
-
throw new Error('An invoice client is required');
|
|
29
|
-
}
|
|
30
|
-
return this.#client;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
type InvoiceModule = Module<'invoices', InvoiceClient, InvoiceConfigurator>;
|
|
35
|
-
|
|
36
|
-
const invoiceModule: InvoiceModule = {
|
|
37
|
-
name: 'invoices',
|
|
38
|
-
configure: () => new InvoiceConfigurator(),
|
|
39
|
-
initialize: ({ config }) => config.createClient(),
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/** The team's own mock, following the pattern the built-in modules use. */
|
|
43
|
-
const enableInvoicesMock = (
|
|
44
|
-
// biome-ignore lint/suspicious/noExplicitAny: mirrors every enableX helper
|
|
45
|
-
configurator: IModulesConfigurator<any, any>,
|
|
46
|
-
options: { total?: number } = {},
|
|
47
|
-
): void => {
|
|
48
|
-
configurator.addConfig({
|
|
49
|
-
module: invoiceModule,
|
|
50
|
-
configure: (builder: InvoiceConfigurator) =>
|
|
51
|
-
builder.setClient({
|
|
52
|
-
getInvoice: async (id) => ({ id, total: options.total ?? 0 }),
|
|
53
|
-
}),
|
|
54
|
-
} as { module: InvoiceModule });
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
describe('application modules', () => {
|
|
58
|
-
it('fails to start without its mock, so the seam is real and not a no-op', async () => {
|
|
59
|
-
await expect(
|
|
60
|
-
mockFramework<[InvoiceModule]>((configurator) => {
|
|
61
|
-
configurator.addConfig({ module: invoiceModule } as { module: InvoiceModule });
|
|
62
|
-
}),
|
|
63
|
-
).rejects.toThrow(/invoice client is required/i);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('composes with the built-in mocks and is typed without a cast', async () => {
|
|
67
|
-
const fusion = await mockFramework<[InvoiceModule]>((configurator) => {
|
|
68
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
69
|
-
enableInvoicesMock(configurator, { total: 42 });
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// No cast: `TModules` must flow through to `fusion.modules`.
|
|
73
|
-
const invoice = await fusion.modules.invoices.getInvoice('inv-1');
|
|
74
|
-
|
|
75
|
-
expect(invoice).toEqual({ id: 'inv-1', total: 42 });
|
|
76
|
-
expect(fusion.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('is registered exactly as it is in production', async () => {
|
|
80
|
-
// The same helper, against a real FrameworkConfigurator, would behave identically.
|
|
81
|
-
const fusion = await mockFramework<[InvoiceModule]>((configurator) =>
|
|
82
|
-
enableInvoicesMock(configurator, { total: 7 }),
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
await expect(fusion.modules.invoices.getInvoice('inv-2')).resolves.toMatchObject({ total: 7 });
|
|
86
|
-
});
|
|
87
|
-
});
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { decodeJwtSegment } from '@equinor/fusion-framework-module-msal/mock';
|
|
4
|
-
|
|
5
|
-
import { init } from '../../init.js';
|
|
6
|
-
import { createMockService, FrameworkMockConfigurator, mockFramework } from '../../mock/index.js';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Pins the snippets in `docs/testing.md` and the package README to real
|
|
10
|
-
* behaviour, so documentation cannot drift away from the API it describes.
|
|
11
|
-
*/
|
|
12
|
-
describe('documented usage', () => {
|
|
13
|
-
afterEach(() => vi.restoreAllMocks());
|
|
14
|
-
|
|
15
|
-
it('composes the service registry on the builder', async () => {
|
|
16
|
-
const fusion = await mockFramework((configurator) => {
|
|
17
|
-
configurator.serviceDiscovery.setBaseUri('http://localhost:6669');
|
|
18
|
-
configurator.serviceDiscovery.addService({ key: 'my-api' });
|
|
19
|
-
configurator.serviceDiscovery.removeService('bookmarks');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('my-api')).resolves.toMatchObject({
|
|
23
|
-
key: 'my-api',
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('replaces the baseline registry with setServices', async () => {
|
|
28
|
-
const fusion = await mockFramework((configurator) => {
|
|
29
|
-
configurator.serviceDiscovery.setServices([{ key: 'apps', uri: 'http://localhost:3000' }]);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('apps')).resolves.toMatchObject({
|
|
33
|
-
uri: 'http://localhost:3000',
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('keeps startup working when the registry is replaced, because unknown services synthesise', async () => {
|
|
38
|
-
// The context module resolves `context` while initializing, so a replaced
|
|
39
|
-
// registry that omits it would break start-up if synthesis were disabled.
|
|
40
|
-
const fusion = await mockFramework((configurator) => {
|
|
41
|
-
configurator.serviceDiscovery.setServices([{ key: 'apps', uri: 'http://localhost:3000' }]);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('people')).resolves.toBeDefined();
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('lets the test runner spy on the discovery client', async () => {
|
|
48
|
-
const fusion = await mockFramework();
|
|
49
|
-
|
|
50
|
-
vi.spyOn(fusion.modules.serviceDiscovery.client, 'resolveService').mockResolvedValue(
|
|
51
|
-
createMockService({ key: 'apps', uri: 'http://spied' }),
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
await expect(fusion.modules.serviceDiscovery.resolveService('apps')).resolves.toMatchObject({
|
|
55
|
-
uri: 'http://spied',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('lets the test runner spy on the auth client', async () => {
|
|
60
|
-
const fusion = await mockFramework();
|
|
61
|
-
|
|
62
|
-
const spy = vi.spyOn(fusion.modules.auth.client, 'acquireToken');
|
|
63
|
-
|
|
64
|
-
await fusion.modules.auth.acquireAccessToken({ request: { scopes: ['Files.Read'] } });
|
|
65
|
-
|
|
66
|
-
expect(spy).toHaveBeenCalled();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('resolves the real scope through the real provider, not the test double', async () => {
|
|
70
|
-
const fusion = await mockFramework((configurator) => {
|
|
71
|
-
// The client is configured with what it talks to, exactly as in production
|
|
72
|
-
configurator.msal.setClientConfig({ auth: { clientId: 'my-app', tenantId: 'my-tenant' } });
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const token = await fusion.modules.auth.acquireAccessToken();
|
|
76
|
-
const claims = JSON.parse(decodeJwtSegment(token?.split('.')[1] ?? ''));
|
|
77
|
-
|
|
78
|
-
// MsalProvider — not the mock — turns "no scopes requested" into `${clientId}/.default`
|
|
79
|
-
expect(claims.scp).toBe('my-app/.default');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it('signs nobody in when the account is signed out', async () => {
|
|
83
|
-
const fusion = await mockFramework((configurator) => {
|
|
84
|
-
configurator.msal.setAccount({ signedOut: true });
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
expect(fusion.modules.auth.account).toBeFalsy();
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('can be constructed directly and initialized with init', async () => {
|
|
91
|
-
const configurator = new FrameworkMockConfigurator();
|
|
92
|
-
configurator.msal.setAccount({ name: 'Ada Lovelace' });
|
|
93
|
-
|
|
94
|
-
const fusion = await init(configurator);
|
|
95
|
-
|
|
96
|
-
expect(fusion.modules.auth.account?.name).toBe('Ada Lovelace');
|
|
97
|
-
});
|
|
98
|
-
});
|