Skip to content

Commit 70b6781

Browse files
committed
test(journey-client): add type regression tests and remove AAA comments
Add compile-time type assertions to verify JourneyResult union members and ensure undefined never reappears. Enable vitest typecheck in vite.config.ts. Remove redundant Arrange/Act/Assert comments and fix prettier formatting across all modified test files.
1 parent 19c57e4 commit 70b6781

6 files changed

Lines changed: 64 additions & 103 deletions

File tree

packages/journey-client/src/lib/client.store.test.ts

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,10 @@ describe('journey-client', () => {
9999
});
100100

101101
test('journey_WellknownConfig_ReturnsClientWithAllMethods', async () => {
102-
// Arrange
103102
setupMockFetch();
104103

105-
// Act
106104
const client = await journey({ config: mockConfig });
107105

108-
// Assert
109106
expect(client.start).toBeInstanceOf(Function);
110107
expect(client.next).toBeInstanceOf(Function);
111108
expect(client.redirect).toBeInstanceOf(Function);
@@ -114,39 +111,32 @@ describe('journey-client', () => {
114111
});
115112

116113
test('journey_InvalidWellknownUrl_ThrowsError', async () => {
117-
// Arrange
118114
const invalidConfig: JourneyClientConfig = {
119115
serverConfig: {
120116
wellknown: 'not-a-valid-url',
121117
},
122118
};
123119

124-
// Act & Assert
125120
await expect(journey({ config: invalidConfig })).rejects.toThrow('Invalid wellknown URL');
126121
});
127122

128123
test('journey_MissingWellknownPath_ThrowsError', async () => {
129-
// Arrange — valid HTTPS URL but missing /.well-known/openid-configuration
130124
const badPathConfig: JourneyClientConfig = {
131125
serverConfig: {
132126
wellknown: 'https://am.example.com/am/oauth2/alpha',
133127
},
134128
};
135129

136-
// Act & Assert
137130
await expect(journey({ config: badPathConfig })).rejects.toThrow('Invalid wellknown URL');
138131
});
139132

140133
test('start_WellknownConfig_FetchesFirstStep', async () => {
141-
// Arrange
142134
const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] };
143135
setupMockFetch(mockStepResponse);
144136

145-
// Act
146137
const client = await journey({ config: mockConfig });
147138
const step = await client.start();
148139

149-
// Assert
150140
expect(step).toBeDefined();
151141
expect(isGenericError(step)).toBe(false);
152142

@@ -163,7 +153,6 @@ describe('journey-client', () => {
163153
});
164154

165155
test('next_WellknownConfig_SendsStepAndReturnsNext', async () => {
166-
// Arrange
167156
const initialStep = createJourneyStep({
168157
authId: 'test-auth-id',
169158
callbacks: [
@@ -186,11 +175,9 @@ describe('journey-client', () => {
186175
};
187176
setupMockFetch(nextStepPayload);
188177

189-
// Act
190178
const client = await journey({ config: mockConfig });
191179
const nextStep = await client.next(initialStep, {});
192180

193-
// Assert
194181
expect(nextStep).toBeDefined();
195182
expect(isGenericError(nextStep)).toBe(false);
196183

@@ -206,7 +193,6 @@ describe('journey-client', () => {
206193
});
207194

208195
test('redirect_WellknownConfig_StoresStepAndCallsLocationAssign', async () => {
209-
// Arrange
210196
const mockStepPayload: Step = {
211197
callbacks: [
212198
{
@@ -224,11 +210,9 @@ describe('journey-client', () => {
224210
});
225211
setupMockFetch();
226212

227-
// Act
228213
const client = await journey({ config: mockConfig });
229214
await client.redirect(step);
230215

231-
// Assert
232216
expect(mockStorageInstance.set).toHaveBeenCalledWith({ step: step.payload });
233217
expect(assignMock).toHaveBeenCalledWith('https://sso.com/redirect');
234218

@@ -237,20 +221,17 @@ describe('journey-client', () => {
237221

238222
describe('resume()', () => {
239223
test('resume_WithPreviousStepInStorage_CallsNextWithUrlParams', async () => {
240-
// Arrange
241224
const previousStepPayload: Step = {
242225
callbacks: [{ type: callbackType.RedirectCallback, input: [], output: [] }],
243226
};
244227
mockStorageInstance.get.mockResolvedValue({ step: previousStepPayload });
245228
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
246229
setupMockFetch(nextStepPayload);
247230

248-
// Act
249231
const client = await journey({ config: mockConfig });
250232
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
251233
const step = await client.resume(resumeUrl, {});
252234

253-
// Assert
254235
expect(step).toBeDefined();
255236
expect(mockStorageInstance.get).toHaveBeenCalledTimes(1);
256237
expect(mockStorageInstance.remove).toHaveBeenCalledTimes(1);
@@ -269,7 +250,6 @@ describe('journey-client', () => {
269250
});
270251

271252
test('resume_WithPlainStepObjectInStorage_CorrectlyResumes', async () => {
272-
// Arrange
273253
const plainStepPayload: Step = {
274254
callbacks: [
275255
{ type: callbackType.TextOutputCallback, output: [{ name: 'message', value: 'Hello' }] },
@@ -280,12 +260,10 @@ describe('journey-client', () => {
280260
const nextStepPayload: Step = { authId: 'test-auth-id', callbacks: [] };
281261
setupMockFetch(nextStepPayload);
282262

283-
// Act
284263
const client = await journey({ config: mockConfig });
285264
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
286265
const step = await client.resume(resumeUrl, {});
287266

288-
// Assert
289267
expect(step).toBeDefined();
290268
expect(mockStorageInstance.get).toHaveBeenCalledTimes(1);
291269
expect(mockStorageInstance.remove).toHaveBeenCalledTimes(1);
@@ -300,15 +278,12 @@ describe('journey-client', () => {
300278
});
301279

302280
test('resume_PreviousStepRequiredButNotFound_ThrowsError', async () => {
303-
// Arrange
304281
mockStorageInstance.get.mockResolvedValue(undefined);
305282
setupMockFetch();
306283

307-
// Act
308284
const client = await journey({ config: mockConfig });
309285
const resumeUrl = 'https://app.com/callback?code=123&state=abc';
310286

311-
// Assert
312287
await expect(client.resume(resumeUrl)).rejects.toThrow(
313288
'Error: previous step information not found in storage for resume operation.',
314289
);
@@ -317,17 +292,14 @@ describe('journey-client', () => {
317292
});
318293

319294
test('resume_NoPreviousStepRequired_CallsStartWithUrlParams', async () => {
320-
// Arrange
321295
mockStorageInstance.get.mockResolvedValue(undefined);
322296
const mockStepResponse: Step = { authId: 'test-auth-id', callbacks: [] };
323297
setupMockFetch(mockStepResponse);
324298

325-
// Act
326299
const client = await journey({ config: mockConfig });
327300
const resumeUrl = 'https://app.com/callback?foo=bar';
328301
const step = await client.resume(resumeUrl, {});
329302

330-
// Assert
331303
expect(step).toBeDefined();
332304
expect(mockStorageInstance.get).not.toHaveBeenCalled();
333305
expect(mockFetch).toHaveBeenCalledTimes(2); // wellknown + start
@@ -342,14 +314,11 @@ describe('journey-client', () => {
342314
});
343315

344316
test('start_NoDataFromServer_ReturnsGenericError', async () => {
345-
// Arrange: server returns null for the authenticate endpoint
346317
setupMockFetch(null);
347318

348-
// Act
349319
const client = await journey({ config: mockConfig });
350320
const result = await client.start();
351321

352-
// Assert
353322
expect(isGenericError(result)).toBe(true);
354323
if (isGenericError(result)) {
355324
expect(result.error).toBe('no_response_data');
@@ -359,7 +328,6 @@ describe('journey-client', () => {
359328

360329
describe('baseUrl from convertWellknown', () => {
361330
test('journey_LocalhostWellknown_ConstructsCorrectUrls', async () => {
362-
// Arrange
363331
const localhostConfig: JourneyClientConfig = {
364332
serverConfig: {
365333
wellknown: 'http://localhost:9443/am/oauth2/realms/root/.well-known/openid-configuration',
@@ -382,11 +350,9 @@ describe('journey-client', () => {
382350
return Promise.resolve(new Response(JSON.stringify(mockStepResponse)));
383351
});
384352

385-
// Act
386353
const client = await journey({ config: localhostConfig });
387354
await client.start();
388355

389-
// Assert
390356
expect(mockFetch).toHaveBeenCalledTimes(2);
391357
const request = mockFetch.mock.calls[1][0] as Request;
392358
expect(request.url).toBe('http://localhost:9443/am/json/realms/root/authenticate');
@@ -395,7 +361,6 @@ describe('journey-client', () => {
395361

396362
describe('subrealm inference', () => {
397363
test('journey_WellknownWithSubrealm_DerivesCorrectPaths', async () => {
398-
// Arrange
399364
const alphaConfig: JourneyClientConfig = {
400365
serverConfig: {
401366
wellknown:
@@ -420,11 +385,9 @@ describe('journey-client', () => {
420385
return Promise.resolve(new Response(JSON.stringify(mockStepResponse)));
421386
});
422387

423-
// Act
424388
const client = await journey({ config: alphaConfig });
425389
await client.start();
426390

427-
// Assert
428391
const request = mockFetch.mock.calls[1][0] as Request;
429392
expect(request.url).toBe('https://test.com/am/json/realms/root/realms/alpha/authenticate');
430393
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (c) 2025 Ping Identity Corporation. All rights reserved.
3+
*
4+
* This software may be modified and distributed under the terms
5+
* of the MIT license. See the LICENSE file for details.
6+
*/
7+
/* eslint-disable @typescript-eslint/no-unused-vars */
8+
import { describe, it } from 'vitest';
9+
10+
import type { GenericError } from '@forgerock/sdk-types';
11+
12+
import type { JourneyClient } from './client.types.js';
13+
import type { JourneyStep } from './step.utils.js';
14+
import type { JourneyLoginSuccess } from './login-success.utils.js';
15+
import type { JourneyLoginFailure } from './login-failure.utils.js';
16+
17+
/**
18+
* Resolves to `true` if `U` is a member of union `T`, `false` otherwise.
19+
* Uses the distributive-conditional-type trick: when `U` is a union member
20+
* of `T`, `U extends T` distributes and resolves to `true`.
21+
*/
22+
type HasMember<T, U> = U extends T ? true : false;
23+
24+
/** Compile-time assertion: `T` must be exactly `true`. */
25+
type AssertTrue<T extends true> = T;
26+
27+
/** Unwrap Promise<T> → T. */
28+
type Awaited<T> = T extends Promise<infer U> ? U : T;
29+
30+
type StartResult = Awaited<ReturnType<JourneyClient['start']>>;
31+
type NextResult = Awaited<ReturnType<JourneyClient['next']>>;
32+
type ResumeResult = Awaited<ReturnType<JourneyClient['resume']>>;
33+
type TerminateResult = Awaited<ReturnType<JourneyClient['terminate']>>;
34+
35+
describe('JourneyClient return types', () => {
36+
it('start includes all expected members and excludes undefined', () => {
37+
type _hasStep = AssertTrue<HasMember<StartResult, JourneyStep>>;
38+
type _hasSuccess = AssertTrue<HasMember<StartResult, JourneyLoginSuccess>>;
39+
type _hasFailure = AssertTrue<HasMember<StartResult, JourneyLoginFailure>>;
40+
type _hasError = AssertTrue<HasMember<StartResult, GenericError>>;
41+
type _noUndefined = AssertTrue<HasMember<StartResult, undefined> extends false ? true : false>;
42+
});
43+
44+
it('next includes all expected members and excludes undefined', () => {
45+
type _hasStep = AssertTrue<HasMember<NextResult, JourneyStep>>;
46+
type _hasSuccess = AssertTrue<HasMember<NextResult, JourneyLoginSuccess>>;
47+
type _hasFailure = AssertTrue<HasMember<NextResult, JourneyLoginFailure>>;
48+
type _hasError = AssertTrue<HasMember<NextResult, GenericError>>;
49+
type _noUndefined = AssertTrue<HasMember<NextResult, undefined> extends false ? true : false>;
50+
});
51+
52+
it('resume includes all expected members and excludes undefined', () => {
53+
type _hasStep = AssertTrue<HasMember<ResumeResult, JourneyStep>>;
54+
type _hasSuccess = AssertTrue<HasMember<ResumeResult, JourneyLoginSuccess>>;
55+
type _hasFailure = AssertTrue<HasMember<ResumeResult, JourneyLoginFailure>>;
56+
type _hasError = AssertTrue<HasMember<ResumeResult, GenericError>>;
57+
type _noUndefined = AssertTrue<HasMember<ResumeResult, undefined> extends false ? true : false>;
58+
});
59+
60+
it('terminate returns void | GenericError', () => {
61+
type _hasVoid = AssertTrue<HasMember<TerminateResult, void>>;
62+
type _hasError = AssertTrue<HasMember<TerminateResult, GenericError>>;
63+
});
64+
});

packages/journey-client/src/lib/config.slice.test.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@ function createMockWellknown(overrides: Partial<WellknownResponse> = {}): Wellkn
2727
describe('journey-client config.slice', () => {
2828
describe('configSlice_ValidAmWellknown_SetsResolvedServerConfig', () => {
2929
it('should derive baseUrl and paths from a standard AM well-known response', () => {
30-
// Arrange
3130
const payload: ResolvedConfig = {
3231
wellknownResponse: createMockWellknown(),
3332
};
3433

35-
// Act
3634
const state = configSlice.reducer(undefined, configSlice.actions.set(payload));
3735

38-
// Assert
3936
expect(state.serverConfig).toEqual({
4037
baseUrl: 'https://am.example.com',
4138
paths: {
@@ -49,18 +46,15 @@ describe('journey-client config.slice', () => {
4946

5047
describe('configSlice_NonAmIssuer_SetsError', () => {
5148
it('should set a GenericError when the issuer is not a ForgeRock AM issuer', () => {
52-
// Arrange
5349
const payload: ResolvedConfig = {
5450
wellknownResponse: createMockWellknown({
5551
issuer: 'https://auth.pingone.com/env-id/as',
5652
authorization_endpoint: 'https://auth.pingone.com/env-id/as/authorize',
5753
}),
5854
};
5955

60-
// Act
6156
const state = configSlice.reducer(undefined, configSlice.actions.set(payload));
6257

63-
// Assert
6458
expect(state.error).toBeDefined();
6559
expect(state.error?.type).toBe('wellknown_error');
6660
expect(state.error?.message).toContain('ForgeRock AM issuer');
@@ -69,17 +63,14 @@ describe('journey-client config.slice', () => {
6963

7064
describe('configSlice_MissingAuthEndpoint_SetsError', () => {
7165
it('should set a GenericError when authorization_endpoint is empty', () => {
72-
// Arrange
7366
const payload: ResolvedConfig = {
7467
wellknownResponse: createMockWellknown({
7568
authorization_endpoint: '',
7669
}),
7770
};
7871

79-
// Act
8072
const state = configSlice.reducer(undefined, configSlice.actions.set(payload));
8173

82-
// Assert
8374
expect(state.error).toBeDefined();
8475
expect(state.error?.type).toBe('wellknown_error');
8576
expect(state.error?.message).toContain('authorization_endpoint');

0 commit comments

Comments
 (0)