@imtbl/auth 2.10.7-alpha.5 → 2.11.1-alpha.0

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.
@@ -0,0 +1,186 @@
1
+ import { Auth } from './Auth';
2
+ import { AuthEvents, User } from './types';
3
+ import { withMetricsAsync } from './utils/metrics';
4
+ import jwt_decode from 'jwt-decode';
5
+
6
+ const trackFlowMock = jest.fn();
7
+ const trackErrorMock = jest.fn();
8
+ const identifyMock = jest.fn();
9
+ const trackMock = jest.fn();
10
+ const getDetailMock = jest.fn();
11
+
12
+ jest.mock('@imtbl/metrics', () => ({
13
+ Detail: { RUNTIME_ID: 'runtime-id' },
14
+ trackFlow: (...args: any[]) => trackFlowMock(...args),
15
+ trackError: (...args: any[]) => trackErrorMock(...args),
16
+ identify: (...args: any[]) => identifyMock(...args),
17
+ track: (...args: any[]) => trackMock(...args),
18
+ getDetail: (...args: any[]) => getDetailMock(...args),
19
+ }));
20
+
21
+ jest.mock('jwt-decode', () => jest.fn());
22
+
23
+ beforeEach(() => {
24
+ trackFlowMock.mockReset();
25
+ trackErrorMock.mockReset();
26
+ identifyMock.mockReset();
27
+ trackMock.mockReset();
28
+ getDetailMock.mockReset();
29
+ (jwt_decode as jest.Mock).mockReset();
30
+ });
31
+
32
+ describe('withMetricsAsync', () => {
33
+ it('resolves with function result and tracks flow', async () => {
34
+ const flow = {
35
+ addEvent: jest.fn(),
36
+ details: { flowId: 'flow-id' },
37
+ };
38
+ trackFlowMock.mockReturnValue(flow);
39
+
40
+ const result = await withMetricsAsync(async () => 'done', 'login');
41
+
42
+ expect(result).toEqual('done');
43
+ expect(trackFlowMock).toHaveBeenCalledWith('passport', 'login', true);
44
+ expect(flow.addEvent).toHaveBeenCalledWith('End');
45
+ });
46
+
47
+ it('tracks error when function throws', async () => {
48
+ const flow = {
49
+ addEvent: jest.fn(),
50
+ details: { flowId: 'flow-id' },
51
+ };
52
+ trackFlowMock.mockReturnValue(flow);
53
+ const error = new Error('boom');
54
+
55
+ await expect(withMetricsAsync(async () => {
56
+ throw error;
57
+ }, 'login')).rejects.toThrow(error);
58
+
59
+ expect(trackErrorMock).toHaveBeenCalledWith('passport', 'login', error, { flowId: 'flow-id' });
60
+ expect(flow.addEvent).toHaveBeenCalledWith('End');
61
+ });
62
+
63
+ it('does not fail when non-error is thrown', async () => {
64
+ const flow = {
65
+ addEvent: jest.fn(),
66
+ details: { flowId: 'flow-id' },
67
+ };
68
+ trackFlowMock.mockReturnValue(flow);
69
+
70
+ const nonError = { message: 'failure' };
71
+ await expect(withMetricsAsync(async () => {
72
+ throw nonError as unknown as Error;
73
+ }, 'login')).rejects.toBe(nonError);
74
+
75
+ expect(flow.addEvent).toHaveBeenCalledWith('errored');
76
+ });
77
+ });
78
+
79
+ describe('Auth', () => {
80
+ describe('getUserOrLogin', () => {
81
+ const createMockUser = (): User => ({
82
+ accessToken: 'access',
83
+ idToken: 'id',
84
+ refreshToken: 'refresh',
85
+ expired: false,
86
+ profile: {
87
+ sub: 'user-123',
88
+ email: 'test@example.com',
89
+ nickname: 'tester',
90
+ },
91
+ });
92
+
93
+ it('emits LOGGED_IN event and identifies user when login is required', async () => {
94
+ const auth = Object.create(Auth.prototype) as Auth;
95
+ const loginWithPopup = jest.fn().mockResolvedValue(createMockUser());
96
+
97
+ (auth as any).eventEmitter = { emit: jest.fn() };
98
+ (auth as any).getUserInternal = jest.fn().mockResolvedValue(null);
99
+ (auth as any).loginWithPopup = loginWithPopup;
100
+
101
+ const user = await auth.getUserOrLogin();
102
+
103
+ expect(loginWithPopup).toHaveBeenCalledTimes(1);
104
+ expect((auth as any).eventEmitter.emit).toHaveBeenCalledWith(AuthEvents.LOGGED_IN, user);
105
+ expect(identifyMock).toHaveBeenCalledWith({ passportId: user.profile.sub });
106
+ });
107
+
108
+ it('returns cached user without triggering login', async () => {
109
+ const auth = Object.create(Auth.prototype) as Auth;
110
+ const cachedUser = createMockUser();
111
+
112
+ (auth as any).eventEmitter = { emit: jest.fn() };
113
+ (auth as any).getUserInternal = jest.fn().mockResolvedValue(cachedUser);
114
+ (auth as any).loginWithPopup = jest.fn();
115
+
116
+ const user = await auth.getUserOrLogin();
117
+
118
+ expect(user).toBe(cachedUser);
119
+ expect((auth as any).loginWithPopup).not.toHaveBeenCalled();
120
+ expect((auth as any).eventEmitter.emit).not.toHaveBeenCalled();
121
+ expect(identifyMock).not.toHaveBeenCalled();
122
+ });
123
+ });
124
+
125
+ describe('buildExtraQueryParams', () => {
126
+ it('omits third_party_a_id when no anonymous id is provided', () => {
127
+ const auth = Object.create(Auth.prototype) as Auth;
128
+ (auth as any).userManager = { settings: { extraQueryParams: {} } };
129
+ getDetailMock.mockReturnValue('runtime-id-value');
130
+
131
+ const params = (auth as any).buildExtraQueryParams();
132
+
133
+ expect(params.third_party_a_id).toBeUndefined();
134
+ expect(params.rid).toEqual('runtime-id-value');
135
+ });
136
+ });
137
+
138
+ describe('username extraction', () => {
139
+ it('extracts username from id token when present', () => {
140
+ const mockOidcUser = {
141
+ id_token: 'token',
142
+ access_token: 'access',
143
+ refresh_token: 'refresh',
144
+ expired: false,
145
+ profile: { sub: 'user-123', email: 'test@example.com', nickname: 'tester' },
146
+ };
147
+
148
+ (jwt_decode as jest.Mock).mockReturnValue({
149
+ username: 'username123',
150
+ passport: undefined,
151
+ });
152
+
153
+ const result = (Auth as any).mapOidcUserToDomainModel(mockOidcUser);
154
+
155
+ expect(jwt_decode).toHaveBeenCalledWith('token');
156
+ expect(result.profile.username).toEqual('username123');
157
+ });
158
+
159
+ it('maps username when creating OIDC user from device tokens', () => {
160
+ const tokenResponse = {
161
+ id_token: 'token',
162
+ access_token: 'access',
163
+ refresh_token: 'refresh',
164
+ token_type: 'Bearer',
165
+ expires_in: 3600,
166
+ };
167
+
168
+ (jwt_decode as jest.Mock).mockReturnValue({
169
+ sub: 'user-123',
170
+ iss: 'issuer',
171
+ aud: 'audience',
172
+ exp: 1,
173
+ iat: 0,
174
+ email: 'test@example.com',
175
+ nickname: 'tester',
176
+ username: 'username123',
177
+ passport: undefined,
178
+ });
179
+
180
+ const oidcUser = (Auth as any).mapDeviceTokenResponseToOidcUser(tokenResponse);
181
+
182
+ expect(jwt_decode).toHaveBeenCalledWith('token');
183
+ expect(oidcUser.profile.username).toEqual('username123');
184
+ });
185
+ });
186
+ });