@equinor/fusion-framework-module-msal 11.0.0 → 11.0.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.
Files changed (60) hide show
  1. package/dist/esm/version.js +1 -1
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/dist/types/version.d.ts +1 -1
  4. package/package.json +8 -5
  5. package/CHANGELOG.md +0 -1212
  6. package/docs/api-reference.md +0 -85
  7. package/docs/auth-code-flow.md +0 -86
  8. package/docs/migration-v2-to-v4.md +0 -115
  9. package/docs/testing.md +0 -191
  10. package/docs/troubleshooting.md +0 -17
  11. package/docs/version-management.md +0 -67
  12. package/src/MsalClient.interface.ts +0 -139
  13. package/src/MsalClient.ts +0 -326
  14. package/src/MsalConfigurator.ts +0 -486
  15. package/src/MsalProvider.interface.ts +0 -179
  16. package/src/MsalProvider.ts +0 -776
  17. package/src/MsalProxyProvider.interface.ts +0 -72
  18. package/src/__tests__/MsalConfigurator.test.ts +0 -222
  19. package/src/__tests__/MsalProvider.test.ts +0 -74
  20. package/src/__tests__/create-proxy-provider.test.ts +0 -77
  21. package/src/__tests__/mock/create-mock-user-from-token.test.ts +0 -46
  22. package/src/__tests__/mock/msal-mock.test.ts +0 -613
  23. package/src/__tests__/versioning/resolve-version.test.ts +0 -161
  24. package/src/create-client-log-callback.ts +0 -102
  25. package/src/create-proxy-provider.ts +0 -97
  26. package/src/index.ts +0 -48
  27. package/src/mock/MsalMockClient.ts +0 -618
  28. package/src/mock/MsalMockConfigurator.ts +0 -305
  29. package/src/mock/create-mock-token.ts +0 -92
  30. package/src/mock/create-mock-user-from-token.ts +0 -46
  31. package/src/mock/create-msal-mock-client.ts +0 -25
  32. package/src/mock/decode-jwt-segment.ts +0 -22
  33. package/src/mock/index.ts +0 -30
  34. package/src/mock/module.ts +0 -54
  35. package/src/module.ts +0 -142
  36. package/src/msal-config-schema.ts +0 -81
  37. package/src/static.ts +0 -38
  38. package/src/telemetry-config-schema.ts +0 -25
  39. package/src/types.ts +0 -16
  40. package/src/util/compare-origin.ts +0 -18
  41. package/src/util/normalize-uri.ts +0 -24
  42. package/src/util/redirect.ts +0 -19
  43. package/src/v2/IAuthClient.interface.ts +0 -114
  44. package/src/v2/Logger.ts +0 -204
  45. package/src/v2/MsalProvider.interface.ts +0 -102
  46. package/src/v2/create-proxy-client.ts +0 -195
  47. package/src/v2/create-proxy-provider.ts +0 -177
  48. package/src/v2/map-account-info.ts +0 -23
  49. package/src/v2/map-authentication-result.ts +0 -28
  50. package/src/v2/types.ts +0 -674
  51. package/src/v4/create-proxy-provider.ts +0 -75
  52. package/src/v4/index.ts +0 -13
  53. package/src/v4/types.ts +0 -727
  54. package/src/version.ts +0 -2
  55. package/src/versioning/VersionError.ts +0 -64
  56. package/src/versioning/index.ts +0 -29
  57. package/src/versioning/resolve-version.ts +0 -154
  58. package/src/versioning/types.ts +0 -60
  59. package/tsconfig.json +0 -18
  60. package/vitest.config.ts +0 -11
@@ -1,613 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import {
3
- ModulesConfigurator,
4
- type ConfigBuilderCallbackArgs,
5
- } from '@equinor/fusion-framework-module';
6
- import telemetryModule from '@equinor/fusion-framework-module-telemetry';
7
- import type { AccountInfo, AuthenticationResult } from '@azure/msal-browser';
8
-
9
- import { MsalConfigurator, type MsalConfig } from '../../MsalConfigurator';
10
- import { MsalProvider } from '../../MsalProvider';
11
- import type { IMsalProvider } from '../../MsalProvider.interface';
12
- import { enableMSAL, module as realModule } from '../../module';
13
- import {
14
- MsalMockClient,
15
- createMockToken,
16
- createMsalMockClient,
17
- enableMsalMock,
18
- msalMockModule,
19
- MsalMockConfigurator,
20
- type MsalMockUser,
21
- } from '../../mock';
22
-
23
- /**
24
- * Initializes the mock module through the real module system.
25
- *
26
- * @remarks
27
- * Deliberately avoids hand-building initialization arguments. Faking the module
28
- * system is the very cost this work removes, and a hand-rolled `init` would test
29
- * the fake rather than the module. The telemetry module is registered because the
30
- * MSAL schema requires a telemetry provider to be present.
31
- *
32
- * @param options - The user the mock client represents.
33
- * @returns The provider the module produced.
34
- */
35
- const initializeMockWith = async (
36
- configure?: (builder: MsalMockConfigurator) => void,
37
- ): Promise<MsalProvider> => {
38
- const configurator = new ModulesConfigurator([telemetryModule]);
39
- enableMsalMock(configurator, configure);
40
- const instances = await configurator.initialize();
41
- return (instances as unknown as { auth: MsalProvider }).auth;
42
- };
43
-
44
- /**
45
- * Initializes the mock module for a given account.
46
- *
47
- * @param options - The user the mock client represents.
48
- * @returns The provider the module produced.
49
- */
50
- const initializeMock = (user?: MsalMockUser): Promise<MsalProvider> =>
51
- initializeMockWith(user && ((builder) => builder.setAccount(user)));
52
-
53
- /** The client configuration a test would declare, identical for real and mock. */
54
- const clientConfig = (clientId = 'fusion-mock-client', tenantId = 'fusion-mock-tenant') => ({
55
- auth: { clientId, tenantId },
56
- });
57
-
58
- describe('msalMockModule', () => {
59
- it('changes nothing but the configurator', () => {
60
- expect(msalMockModule.name).toBe(realModule.name);
61
- expect(msalMockModule.version).toBe(realModule.version);
62
- // The production initializer, untouched — the mock has no lifecycle of its own
63
- expect(msalMockModule.initialize).toBe(realModule.initialize);
64
- });
65
-
66
- it('builds a real MsalConfigurator, so the whole builder stays available', () => {
67
- const configurator = msalMockModule.configure?.();
68
-
69
- expect(configurator).toBeInstanceOf(MsalConfigurator);
70
- expect(configurator).toBeInstanceOf(MsalMockConfigurator);
71
- });
72
- });
73
-
74
- describe('enableMsalMock', () => {
75
- it('produces the real MsalProvider, not a stand-in', async () => {
76
- const provider = await initializeMock();
77
-
78
- // The point of substituting the client rather than the provider: everything
79
- // above the network boundary is production code
80
- expect(provider).toBeInstanceOf(MsalProvider);
81
- });
82
-
83
- it('signs in a user without any client configuration', async () => {
84
- const provider = await initializeMock({ name: 'Ada Lovelace' });
85
-
86
- expect(provider.account?.name).toBe('Ada Lovelace');
87
- });
88
-
89
- it('replaces an auth module that is already registered', async () => {
90
- // A FrameworkConfigurator pre-registers the real auth module, so the mock is
91
- // only useful if registering it afterwards wins
92
- const configurator = new ModulesConfigurator([telemetryModule, realModule]);
93
- enableMsalMock(configurator);
94
-
95
- const instances = await configurator.initialize();
96
-
97
- expect((instances as unknown as { auth: MsalProvider }).auth.account?.name).toBe('Test User');
98
- });
99
-
100
- it('declares the account without constructing a client', () => {
101
- const configurator = new MsalMockConfigurator();
102
-
103
- configurator.setAccount({ username: 'ada@equinor.com' });
104
-
105
- // Nothing is built until the module assembles its config
106
- expect(configurator.getClient()).toBeUndefined();
107
- expect(configurator.getClientConfig()).toBeUndefined();
108
- });
109
-
110
- it('resolves an account callback with the ordinary builder arguments', async () => {
111
- // An ordinary config-builder callback, so a test can reach the modules in
112
- // scope rather than being handed a bespoke signature
113
- const provider = await initializeMockWith((builder) =>
114
- builder.setAccount(async ({ hasModule }) => ({
115
- name: hasModule('telemetry') ? 'Ada Lovelace' : 'Nobody',
116
- })),
117
- );
118
-
119
- expect(provider.account?.name).toBe('Ada Lovelace');
120
- });
121
-
122
- it('signs the user in before the provider initializes', async () => {
123
- // The provider's own start-up path must see the declared state, or a test
124
- // could not observe what the framework does with it
125
- const provider = await initializeMockWith((builder) => {
126
- builder.setAccount({ signedOut: true });
127
- builder.setRequiresAuth(true);
128
- });
129
-
130
- // `requiresAuth` made the real provider log in during initialize
131
- expect(provider.account?.username).toBe('test.user@equinor.com');
132
- });
133
-
134
- it('signs the declared user in on a client that was set explicitly', async () => {
135
- // The rule is uniform: the user goes on whichever client the module
136
- // authenticates through, wherever that client came from
137
- const own = new MsalMockClient(clientConfig());
138
- own.setUser({ name: 'Grace Hopper' });
139
-
140
- const provider = await initializeMockWith((builder) => {
141
- builder.setClient(own);
142
- builder.setAccount({ name: 'Ada Lovelace' });
143
- });
144
-
145
- expect(provider.client).toBe(own);
146
- expect(provider.account?.name).toBe('Ada Lovelace');
147
- });
148
-
149
- it('leaves a client that was set explicitly alone when no user is declared', async () => {
150
- const own = new MsalMockClient(clientConfig());
151
- own.setUser({ name: 'Grace Hopper' });
152
-
153
- const provider = await initializeMockWith((builder) => builder.setClient(own));
154
-
155
- expect(provider.account?.name).toBe('Grace Hopper');
156
- });
157
-
158
- it('refuses to declare a user on a client that cannot represent one', async () => {
159
- // Failing quietly is the whole failure mode this exists to prevent
160
- const configurator = new ModulesConfigurator([telemetryModule]);
161
- enableMsalMock(configurator, (builder) => {
162
- builder.setClient({} as unknown as MsalMockClient);
163
- builder.setAccount({ name: 'Ada Lovelace' });
164
- });
165
-
166
- await expect(configurator.initialize()).rejects.toThrow(
167
- /does not authenticate through a mock client/,
168
- );
169
- });
170
-
171
- it('carries the user on the configuration, resolved by the builder', async () => {
172
- // The user travels the ordinary pipeline as `mock.account` rather than
173
- // living on the builder, so any code with the raw configuration can read it
174
- const configurator = new MsalMockConfigurator();
175
- configurator.setAccount(async () => ({ name: 'Ada Lovelace' }));
176
-
177
- let rawConfig: MsalConfig | undefined;
178
- vi.spyOn(configurator, '_processConfig').mockImplementation(async (config) => {
179
- rawConfig = config;
180
- return config as MsalConfig;
181
- });
182
-
183
- await configurator.createConfigAsync({
184
- requireInstance: async () => undefined,
185
- hasModule: () => false,
186
- config: {},
187
- } as unknown as ConfigBuilderCallbackArgs);
188
-
189
- expect(rawConfig?.mock?.account).toEqual({ name: 'Ada Lovelace' });
190
- });
191
-
192
- it('applies the account as it ends up, not as it started', async () => {
193
- const provider = await initializeMockWith((builder) => {
194
- builder.setAccount({ name: 'Ada Lovelace' });
195
- builder.setAccount({ name: 'Grace Hopper' });
196
- });
197
-
198
- expect(provider.client).toBeInstanceOf(MsalMockClient);
199
- expect(provider.account?.name).toBe('Grace Hopper');
200
- });
201
-
202
- it('builds its client from setClientConfig, exactly as the real module does', async () => {
203
- const provider = await initializeMockWith((builder) =>
204
- builder.setClientConfig(clientConfig('my-app', 'my-tenant')),
205
- );
206
-
207
- expect(provider.client).toBeInstanceOf(MsalMockClient);
208
- expect(provider.client.clientId).toBe('my-app');
209
- expect(provider.client.tenantId).toBe('my-tenant');
210
- });
211
-
212
- it('exposes the client built for a signed-out account', async () => {
213
- const provider = await initializeMockWith((builder) => builder.setAccount({ signedOut: true }));
214
- const client = provider.client;
215
-
216
- expect(client.hasValidClaims).toBe(false);
217
-
218
- client.setActiveAccount({
219
- homeAccountId: 'id.tenant',
220
- localAccountId: 'id',
221
- environment: 'login.microsoftonline.com',
222
- tenantId: 'tenant',
223
- username: 'user@equinor.com',
224
- name: 'User',
225
- });
226
-
227
- expect(client.hasValidClaims).toBe(true);
228
- });
229
-
230
- it('starts with no account when signed out', async () => {
231
- const provider = await initializeMock({ signedOut: true });
232
-
233
- expect(provider.account).toBeNull();
234
- });
235
-
236
- it('runs the real sign-in flow through the provider', async () => {
237
- const provider = await initializeMock({ signedOut: true });
238
- expect(provider.account).toBeNull();
239
-
240
- await provider.login({ request: { scopes: ['User.Read'] } });
241
-
242
- expect(provider.account?.username).toBe('test.user@equinor.com');
243
- });
244
-
245
- it('runs the real sign-out flow through the provider', async () => {
246
- const provider = await initializeMock();
247
- expect(provider.account).not.toBeNull();
248
-
249
- await provider.logout();
250
-
251
- expect(provider.account).toBeNull();
252
- });
253
-
254
- it('lets the real provider resolve default scopes', async () => {
255
- const provider = await initializeMockWith((builder) =>
256
- builder.setClientConfig(clientConfig('my-app')),
257
- );
258
-
259
- const token = await provider.acquireAccessToken();
260
- const claims = JSON.parse(atob(token?.split('.')[1] ?? ''));
261
-
262
- // MsalProvider — not the mock — turns "no scopes requested" into `${clientId}/.default`
263
- expect(claims.scp).toBe('my-app/.default');
264
- });
265
- });
266
-
267
- describe('enableMsalMock when hoisted onto a host', () => {
268
- /**
269
- * Initializes the mock module inside a host application.
270
- *
271
- * @remarks
272
- * `ModulesConfigurator.initialize` forwards its argument as the module `ref`,
273
- * which is exactly how a portal hands its modules to an app it loads. Going
274
- * through the real module system keeps the proxy-provider path under test.
275
- *
276
- * @param configure - Configuration applied to the hosted (inner) module.
277
- * @returns The provider the hosted module produced.
278
- */
279
- const initializeHosted = async (
280
- configure?: (builder: MsalMockConfigurator) => void,
281
- ): Promise<{ host: MsalProvider; hosted: IMsalProvider }> => {
282
- const host = await initializeMockWith((builder) => builder.setAccount({ name: 'Host User' }));
283
-
284
- const configurator = new ModulesConfigurator([telemetryModule]);
285
- enableMsalMock(configurator, configure);
286
- const instances = await configurator.initialize({ auth: host });
287
-
288
- return { host, hosted: (instances as unknown as { auth: IMsalProvider }).auth };
289
- };
290
-
291
- it('authenticates through the host instead of building a client of its own', async () => {
292
- const { host, hosted } = await initializeHosted();
293
-
294
- expect(hosted).not.toBe(host);
295
- expect(hosted.account?.name).toBe('Host User');
296
- });
297
-
298
- it('signs the declared user in on the host client, since none is built here', async () => {
299
- // The client belongs to the host, built in a scope this builder never sees.
300
- // Reaching it is the only way a declaration made here can take effect at
301
- // all — otherwise `setAccount` silently does nothing exactly when an
302
- // application is being tested inside a portal
303
- const { host, hosted } = await initializeHosted((builder) =>
304
- builder.setAccount({ name: 'Ada Lovelace' }),
305
- );
306
-
307
- expect(hosted.account?.name).toBe('Ada Lovelace');
308
- // The session is shared, as it is in production
309
- expect(host.account?.name).toBe('Ada Lovelace');
310
- });
311
-
312
- it('leaves the host user alone when the app declares none', async () => {
313
- const { host, hosted } = await initializeHosted();
314
-
315
- expect(hosted.account?.name).toBe('Host User');
316
- expect(host.account?.name).toBe('Host User');
317
- });
318
-
319
- it('refuses to declare a user the host cannot honour', async () => {
320
- // Failing quietly here is the bug this path exists to prevent
321
- const host = await initializeMockWith();
322
- const realHost = { ...host, client: {} } as unknown as IMsalProvider;
323
-
324
- const configurator = new ModulesConfigurator([telemetryModule]);
325
- enableMsalMock(configurator, (builder) => builder.setAccount({ name: 'Ada Lovelace' }));
326
-
327
- await expect(configurator.initialize({ auth: realHost })).rejects.toThrow(
328
- /does not authenticate through a mock client/,
329
- );
330
- });
331
-
332
- it('declares no client configuration at all, so nothing stands in for the host', async () => {
333
- // The stand-in configuration exists only to build a client from; a hoisted
334
- // module builds none, so it must not look configured either
335
- let hosted: MsalMockConfigurator | undefined;
336
- const host = await initializeMockWith((builder) => builder.setAccount({ name: 'Host User' }));
337
-
338
- const configurator = new ModulesConfigurator([telemetryModule]);
339
- enableMsalMock(configurator, (builder) => {
340
- hosted = builder;
341
- });
342
- await configurator.initialize({ auth: host });
343
-
344
- expect(hosted?.getClientConfig()).toBeUndefined();
345
- expect(hosted?.getClient()).toBeUndefined();
346
- });
347
- });
348
-
349
- describe('createMsalMockClient with the real module', () => {
350
- it('needs no mock module — the client is enough', async () => {
351
- const configurator = new ModulesConfigurator([telemetryModule]);
352
- enableMSAL(configurator, (builder) => {
353
- builder.setClient(createMsalMockClient(clientConfig(), { name: 'Ada Lovelace' }));
354
- });
355
-
356
- const instances = await configurator.initialize();
357
-
358
- expect((instances as unknown as { auth: MsalProvider }).auth.account?.name).toBe(
359
- 'Ada Lovelace',
360
- );
361
- });
362
- });
363
-
364
- describe('MsalMockClient', () => {
365
- it('takes the same argument as the real client', () => {
366
- const client = new MsalMockClient(clientConfig('my-app', 'my-tenant'));
367
-
368
- expect(client.clientId).toBe('my-app');
369
- expect(client.tenantId).toBe('my-tenant');
370
- });
371
-
372
- it('reads the tenant out of the authority when none was given', () => {
373
- // A production config often carries only `authority`, and the tenant still
374
- // has to reach the tokens the client mints
375
- const client = new MsalMockClient({
376
- auth: {
377
- clientId: 'my-app',
378
- authority: 'https://login.microsoftonline.com/authority-tenant',
379
- },
380
- });
381
-
382
- expect(client.tenantId).toBe('authority-tenant');
383
- });
384
-
385
- it('is not mistaken for a promise', () => {
386
- const client = new MsalMockClient(clientConfig());
387
-
388
- expect((client as unknown as { then?: unknown }).then).toBeUndefined();
389
- });
390
-
391
- it('fails silent sign-in without an account, as MSAL does', async () => {
392
- const client = new MsalMockClient(clientConfig());
393
- client.setUser({ signedOut: true });
394
-
395
- await expect(client.ssoSilent({ scopes: ['X'] })).rejects.toThrow(/no cached account/);
396
- });
397
-
398
- it('issues identical tokens across instances', async () => {
399
- const first = new MsalMockClient(clientConfig());
400
- const second = new MsalMockClient(clientConfig());
401
-
402
- const a = await first.acquireToken({ request: { scopes: ['X'] } });
403
- const b = await second.acquireToken({ request: { scopes: ['X'] } });
404
-
405
- expect(a?.accessToken).toBe(b?.accessToken);
406
- });
407
-
408
- it('lets a test runner spy on a single method', async () => {
409
- const client = new MsalMockClient(clientConfig());
410
-
411
- vi.spyOn(client, 'acquireToken').mockResolvedValue({
412
- account: client.getActiveAccount(),
413
- accessToken: 'mock-token',
414
- idToken: 'mock-token',
415
- scopes: ['mock'],
416
- tokenType: 'Bearer',
417
- expiresOn: new Date('2033-11-14T22:13:20.000Z'),
418
- authority: 'https://login.microsoftonline.com/mock',
419
- uniqueId: 'mock-user',
420
- tenantId: 'mock-tenant',
421
- fromCache: false,
422
- correlationId: 'fusion-mock-correlation',
423
- } as unknown as AuthenticationResult);
424
-
425
- const result = await client.acquireToken({ request: { scopes: ['X'] } });
426
-
427
- expect(result?.accessToken).toBe('mock-token');
428
- });
429
-
430
- it('returns a token set directly on the client verbatim', async () => {
431
- const client = new MsalMockClient(clientConfig());
432
- const token = createMockToken({ name: 'Direct Token' });
433
-
434
- client.setToken(token);
435
- const result = await client.acquireToken({ request: { scopes: ['X'] } });
436
-
437
- expect(result?.accessToken).toBe(token);
438
- expect(result?.idToken).toBe(token);
439
- });
440
-
441
- it('resumes generating tokens once setToken(null) clears the override', async () => {
442
- const client = new MsalMockClient(clientConfig());
443
- const token = createMockToken({ name: 'Direct Token' });
444
- client.setToken(token);
445
-
446
- client.setToken(null);
447
- const result = await client.acquireToken({ request: { scopes: ['X'] } });
448
-
449
- expect(result?.accessToken).not.toBe(token);
450
- });
451
- });
452
-
453
- describe('MsalMockConfigurator.setToken', () => {
454
- it('returns the exact token instead of one generated from the account', async () => {
455
- const token = createMockToken({ name: 'Token User' });
456
- const provider = await initializeMockWith((builder) => builder.setToken(token));
457
-
458
- const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
459
-
460
- expect(result?.accessToken).toBe(token);
461
- expect(result?.idToken).toBe(token);
462
- });
463
-
464
- it('signs in the account named by the token claims by default', async () => {
465
- const token = createMockToken({
466
- name: 'Token User',
467
- preferred_username: 'token.user@equinor.com',
468
- });
469
- const provider = await initializeMockWith((builder) => builder.setToken(token));
470
-
471
- expect(provider.account?.name).toBe('Token User');
472
- expect(provider.account?.username).toBe('token.user@equinor.com');
473
- });
474
-
475
- it('keeps a separately declared account when skipResolve is true', async () => {
476
- // skipResolve overrides only the returned token, leaving setAccount's declaration alone
477
- const token = createMockToken({ name: 'Token User' });
478
- const provider = await initializeMockWith((builder) => {
479
- builder.setAccount({ name: 'Ada Lovelace' });
480
- builder.setToken(token, true);
481
- });
482
-
483
- expect(provider.account?.name).toBe('Ada Lovelace');
484
- const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
485
- expect(result?.accessToken).toBe(token);
486
- });
487
-
488
- it('resumes generating tokens once setToken(null) is called on the client', async () => {
489
- const token = createMockToken({ name: 'Token User' });
490
- const provider = await initializeMockWith((builder) => builder.setToken(token));
491
-
492
- (provider.client as MsalMockClient).setToken(null);
493
- const result = await provider.client.acquireToken({ request: { scopes: ['X'] } });
494
-
495
- expect(result?.accessToken).not.toBe(token);
496
- });
497
- });
498
-
499
- describe('setAccount(null)', () => {
500
- it('starts with nobody signed in', async () => {
501
- const provider = await initializeMockWith((builder) => builder.setAccount(null));
502
-
503
- expect(provider.account).toBeNull();
504
- expect(provider.client.hasValidClaims).toBe(false);
505
- });
506
-
507
- it('is a declaration, not the absence of one', async () => {
508
- // Saying "nobody" has to beat the default signed-in user, so it cannot be
509
- // treated the same as saying nothing at all
510
- const provider = await initializeMockWith((builder) => {
511
- builder.setAccount({ name: 'Ada Lovelace' });
512
- builder.setAccount(null);
513
- });
514
-
515
- expect(provider.account).toBeNull();
516
- });
517
-
518
- it('forgets the identity, unlike signedOut which keeps it', async () => {
519
- const forgotten = await initializeMockWith((builder) => {
520
- builder.setAccount({ name: 'Ada Lovelace' });
521
- builder.setAccount(null);
522
- });
523
- const remembered = await initializeMockWith((builder) =>
524
- builder.setAccount({ name: 'Ada Lovelace', signedOut: true }),
525
- );
526
-
527
- await forgotten.client.login({ request: { scopes: [] } });
528
- await remembered.client.login({ request: { scopes: [] } });
529
-
530
- expect(forgotten.account?.name).toBe('Test User');
531
- expect(remembered.account?.name).toBe('Ada Lovelace');
532
- });
533
-
534
- it('resolves null from a callback too', async () => {
535
- const provider = await initializeMockWith((builder) => builder.setAccount(async () => null));
536
-
537
- expect(provider.account).toBeNull();
538
- });
539
- });
540
-
541
- describe('MsalMockClient account cache', () => {
542
- it('swaps the signed-in user on a live framework, as the docs describe', async () => {
543
- const provider = await initializeMock();
544
- const account = {
545
- ...(provider.account as AccountInfo),
546
- homeAccountId: 'ada',
547
- name: 'Ada Lovelace',
548
- };
549
-
550
- provider.client.setActiveAccount(account);
551
-
552
- expect(provider.account?.name).toBe('Ada Lovelace');
553
- });
554
-
555
- it('holds the signed-in user in the account cache', () => {
556
- const client = new MsalMockClient(clientConfig());
557
-
558
- expect(client.getAllAccounts()).toEqual([client.getActiveAccount()]);
559
- });
560
-
561
- it('empties the cache when the user signs out', async () => {
562
- const client = new MsalMockClient(clientConfig());
563
-
564
- await client.logout();
565
-
566
- expect(client.getActiveAccount()).toBeNull();
567
- expect(client.getAllAccounts()).toEqual([]);
568
- });
569
-
570
- it('matches a cached account on the filter it is given', () => {
571
- const client = new MsalMockClient(clientConfig());
572
- const account = client.getActiveAccount();
573
-
574
- expect(client.getAccount({ username: account?.username })).toBe(account);
575
- expect(client.getAccount({ homeAccountId: account?.homeAccountId })).toBe(account);
576
- expect(client.getAccount({ username: 'someone.else@equinor.com' })).toBeNull();
577
- });
578
-
579
- it('caches an account a test makes active', () => {
580
- // Tests swap the user per run rather than reconstructing the framework, so
581
- // an account MSAL never issued still has to end up in the cache
582
- const client = new MsalMockClient(clientConfig());
583
- const account = {
584
- ...(client.getActiveAccount() as AccountInfo),
585
- homeAccountId: 'grace.hopper',
586
- username: 'grace.hopper@equinor.com',
587
- name: 'Grace Hopper',
588
- };
589
-
590
- client.setActiveAccount(account);
591
-
592
- expect(client.getActiveAccount()?.name).toBe('Grace Hopper');
593
- expect(client.getAccount({ username: 'grace.hopper@equinor.com' })).toBe(account);
594
- });
595
-
596
- it('replaces the session when a new user is declared', () => {
597
- const client = new MsalMockClient(clientConfig());
598
-
599
- client.setUser({ name: 'Ada Lovelace', userId: 'ada' });
600
-
601
- expect(client.getAllAccounts()).toHaveLength(1);
602
- expect(client.getActiveAccount()?.name).toBe('Ada Lovelace');
603
- });
604
-
605
- it('leaves no account behind when signed out', () => {
606
- const client = new MsalMockClient(clientConfig());
607
-
608
- client.setUser({ signedOut: true });
609
-
610
- expect(client.getAllAccounts()).toEqual([]);
611
- expect(client.hasValidClaims).toBe(false);
612
- });
613
- });