Skip to content

Commit 1713c01

Browse files
authored
Merge pull request #416 from ForgeRock/SDKS-4330-sdk-oidc-tests
test(sdk-oidc): add tests to oidc effects package
2 parents 89339f1 + 7ffa428 commit 1713c01

6 files changed

Lines changed: 241 additions & 17 deletions

File tree

.changeset/icy-olives-make.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@forgerock/sdk-oidc': minor
3+
---
4+
5+
- Adds tests for OIDC effects package
6+
- Exposes `getStorageKey` utility
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
1-
export * from './lib//authorize.effects.js';
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+
export * from './lib/authorize.effects.js';
28
export * from './lib/state-pkce.effects.js';
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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+
8+
import type { GenerateAndStoreAuthUrlValues } from '@forgerock/sdk-types';
9+
import { describe, expect, it, beforeEach } from 'vitest';
10+
import { createAuthorizeUrl } from './authorize.effects.js';
11+
import { getStorageKey } from './state-pkce.effects.js';
12+
13+
const mockSessionStorage = (() => {
14+
let store: { [key: string]: string } = {};
15+
return {
16+
getItem: (key: string) => store[key] || null,
17+
setItem: (key: string, value: string) => {
18+
store[key] = value;
19+
},
20+
removeItem: (key: string) => {
21+
delete store[key];
22+
},
23+
clear: () => {
24+
store = {};
25+
},
26+
length: 0,
27+
key: (index: number) => Object.keys(store)[index] || null,
28+
};
29+
})();
30+
31+
describe('createAuthorizeUrl', () => {
32+
beforeEach(() => {
33+
if (typeof sessionStorage === 'undefined') {
34+
global.sessionStorage = mockSessionStorage;
35+
}
36+
sessionStorage.clear();
37+
});
38+
39+
const mockOptions: GenerateAndStoreAuthUrlValues = {
40+
clientId: 'test-client',
41+
redirectUri: 'http://localhost:8080',
42+
scope: 'openid profile',
43+
responseType: 'code',
44+
};
45+
const baseUrl = 'https://auth.example.com/authorize';
46+
47+
it('should create a valid authorization URL with all required parameters', async () => {
48+
const url = await createAuthorizeUrl(baseUrl, mockOptions);
49+
const parsedUrl = new URL(url);
50+
51+
// Check the base URL
52+
expect(parsedUrl.origin + parsedUrl.pathname).toBe(baseUrl);
53+
54+
const params = Object.fromEntries(parsedUrl.searchParams.entries());
55+
expect(params).toMatchObject({
56+
client_id: mockOptions.clientId,
57+
redirect_uri: mockOptions.redirectUri,
58+
response_type: mockOptions.responseType,
59+
scope: mockOptions.scope,
60+
code_challenge_method: 'S256',
61+
});
62+
expect(params.prompt).toBeFalsy();
63+
expect(params.response_mode).toBeFalsy();
64+
65+
// Verify presence of dynamically generated values
66+
expect(params.state).toBeDefined();
67+
expect(params.code_challenge).toBeDefined();
68+
});
69+
70+
it('should include optional parameters when provided', async () => {
71+
const prompt = 'login';
72+
const responseMode = 'pi.flow';
73+
const optionsWithOptionals: GenerateAndStoreAuthUrlValues = {
74+
...mockOptions,
75+
prompt,
76+
responseMode,
77+
};
78+
79+
const url = await createAuthorizeUrl(baseUrl, optionsWithOptionals);
80+
const params = new URL(url).searchParams;
81+
82+
expect(params.get('prompt')).toBe(prompt);
83+
expect(params.get('response_mode')).toBe(responseMode);
84+
});
85+
86+
it('should store the authorize options in session storage', async () => {
87+
await createAuthorizeUrl(baseUrl, mockOptions);
88+
const storageKey = getStorageKey(mockOptions.clientId);
89+
const storedData = sessionStorage.getItem(storageKey);
90+
91+
const parsedOptions = JSON.parse(storedData as string);
92+
const serverUrl = new URL(baseUrl).origin;
93+
94+
expect(storedData).toBeDefined();
95+
expect(parsedOptions).toMatchObject({
96+
...mockOptions,
97+
serverConfig: { baseUrl: serverUrl },
98+
});
99+
expect(parsedOptions).toHaveProperty('state');
100+
expect(parsedOptions).toHaveProperty('verifier');
101+
});
102+
});

packages/sdk-effects/oidc/src/lib/index.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

packages/sdk-effects/oidc/src/lib/state-pkce.effects.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77

88
import { createVerifier, createState } from '@forgerock/sdk-utilities';
99

10-
import type { GetAuthorizationUrlOptions } from '@forgerock/sdk-types';
10+
import type {
11+
GenerateAndStoreAuthUrlValues,
12+
GetAuthorizationUrlOptions,
13+
} from '@forgerock/sdk-types';
1114

12-
function getStorageKey(clientId: string, prefix?: string) {
15+
export function getStorageKey(clientId: string, prefix?: string) {
1316
return `${prefix || 'FR-SDK'}-authflow-${clientId}`;
1417
}
1518

@@ -19,11 +22,6 @@ function getStorageKey(clientId: string, prefix?: string) {
1922
* @param {GenerateAndStoreAuthUrlValues} options - Options for generating PKCE values
2023
* @returns { state: string, verifier: string, GetAuthorizationUrlOptions }
2124
*/
22-
interface GenerateAndStoreAuthUrlValues extends GetAuthorizationUrlOptions {
23-
login?: 'redirect' | 'embedded';
24-
clientId: string;
25-
prefix?: string;
26-
}
2725

2826
export function generateAndStoreAuthUrlValues(
2927
options: GenerateAndStoreAuthUrlValues,
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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+
8+
import { describe, expect, it, beforeEach } from 'vitest';
9+
import {
10+
generateAndStoreAuthUrlValues,
11+
getStorageKey,
12+
getStoredAuthUrlValues,
13+
} from './state-pkce.effects.js';
14+
import type { GenerateAndStoreAuthUrlValues } from '@forgerock/sdk-types';
15+
16+
const mockSessionStorage = (() => {
17+
let store: { [key: string]: string } = {};
18+
return {
19+
getItem: (key: string) => store[key] || null,
20+
setItem: (key: string, value: string) => {
21+
store[key] = value;
22+
},
23+
removeItem: (key: string) => {
24+
delete store[key];
25+
},
26+
clear: () => {
27+
store = {};
28+
},
29+
length: 0,
30+
key: (index: number) => Object.keys(store)[index] || null,
31+
};
32+
})();
33+
34+
describe('PKCE', () => {
35+
beforeEach(() => {
36+
if (typeof sessionStorage === 'undefined') {
37+
global.sessionStorage = mockSessionStorage;
38+
}
39+
40+
sessionStorage.clear();
41+
});
42+
43+
const mockOptions: GenerateAndStoreAuthUrlValues = {
44+
clientId: 'test-client',
45+
redirectUri: 'http://localhost:8080',
46+
scope: 'openid profile',
47+
responseType: 'code',
48+
};
49+
50+
describe('getStorageKey', () => {
51+
const clientId = 'test-client-id';
52+
53+
it('should generate storage key with default prefix', () => {
54+
const key = getStorageKey(clientId);
55+
expect(key).toBe('FR-SDK-authflow-test-client-id');
56+
});
57+
58+
it('should generate storage key with custom prefix', () => {
59+
const customPrefix = 'CUSTOM';
60+
const key = getStorageKey(clientId, customPrefix);
61+
expect(key).toBe('CUSTOM-authflow-test-client-id');
62+
});
63+
});
64+
65+
describe('generateAndStoreAuthUrlValues', () => {
66+
it('should generate PKCE values', () => {
67+
const [options] = generateAndStoreAuthUrlValues(mockOptions);
68+
69+
expect(options).toBeDefined();
70+
expect(options).toHaveProperty('state');
71+
expect(options).toHaveProperty('verifier');
72+
});
73+
74+
it('should store options in sessionStorage when storage function is called', () => {
75+
const [options, storeAuthUrl] = generateAndStoreAuthUrlValues(mockOptions);
76+
storeAuthUrl();
77+
78+
const storageKey = getStorageKey(mockOptions.clientId, mockOptions.prefix);
79+
const storedValue = sessionStorage.getItem(storageKey);
80+
expect(storedValue).toBeDefined();
81+
82+
const parsedValue = JSON.parse(storedValue as string);
83+
expect(parsedValue).toEqual(options);
84+
});
85+
});
86+
87+
describe('getStoredAuthUrlValues', () => {
88+
it('should retrieve and parse stored values', () => {
89+
const [options, storeAuthUrl] = generateAndStoreAuthUrlValues(mockOptions);
90+
storeAuthUrl();
91+
92+
const storedValues = getStoredAuthUrlValues(mockOptions.clientId, mockOptions.prefix);
93+
expect(storedValues).toEqual(options);
94+
});
95+
96+
it('should remove values from storage after retrieval', () => {
97+
const [, storeAuthUrl] = generateAndStoreAuthUrlValues(mockOptions);
98+
storeAuthUrl();
99+
100+
const storageKey = getStorageKey(mockOptions.clientId, mockOptions.prefix);
101+
102+
// Verify value exists before retrieval
103+
expect(sessionStorage.getItem(storageKey)).toBeDefined();
104+
105+
// Retrieve values
106+
getStoredAuthUrlValues(mockOptions.clientId, mockOptions.prefix);
107+
108+
// Verify value was removed
109+
expect(sessionStorage.getItem(storageKey)).toBeNull();
110+
});
111+
112+
it('should throw error when stored values cannot be parsed', () => {
113+
const storageKey = getStorageKey(mockOptions.clientId, mockOptions.prefix);
114+
sessionStorage.setItem(storageKey, 'invalid json');
115+
116+
expect(() => getStoredAuthUrlValues(mockOptions.clientId, mockOptions.prefix)).toThrow(
117+
'Stored values for Auth URL could not be parsed',
118+
);
119+
});
120+
});
121+
});

0 commit comments

Comments
 (0)