Skip to content

Commit aa2f6d1

Browse files
committed
test(okta): cover Link-header cursor parsing and group-rule flattening
parseOktaPagination is the load-bearing piece of the pagination retrofit — Okta advertises the opaque after cursor only in a Link header, never in the body — so pin the next/self/absent/malformed cases along with the group-rule mapper's exclude-only conditions and absent-nesting defaults.
1 parent 2d3b68b commit aa2f6d1

1 file changed

Lines changed: 101 additions & 0 deletions

File tree

apps/sim/tools/okta/utils.test.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { mapOktaGroupRule, oktaHeaders, parseOktaPagination } from '@/tools/okta/utils'
6+
7+
/** Obvious non-secret so credential scanners do not flag these fixtures. */
8+
const PLACEHOLDER_TOKEN = 'not-a-real-api-token'
9+
10+
const BASE = 'https://example.okta.com/api/v1/users'
11+
12+
function responseWithLink(link?: string): Response {
13+
return new Response('[]', {
14+
status: 200,
15+
headers: link ? { Link: link } : {},
16+
})
17+
}
18+
19+
describe('oktaHeaders', () => {
20+
it('authenticates with the SSWS scheme rather than Bearer', () => {
21+
expect(oktaHeaders(PLACEHOLDER_TOKEN).Authorization).toBe(`SSWS ${PLACEHOLDER_TOKEN}`)
22+
})
23+
})
24+
25+
describe('parseOktaPagination', () => {
26+
it('extracts the after cursor from a rel="next" link', () => {
27+
const link = `<${BASE}?after=cursor123&limit=200>; rel="next"`
28+
expect(parseOktaPagination(responseWithLink(link))).toEqual({
29+
nextCursor: 'cursor123',
30+
hasMore: true,
31+
})
32+
})
33+
34+
it('reports the last page when only a self link is present', () => {
35+
const link = `<${BASE}?limit=200>; rel="self"`
36+
expect(parseOktaPagination(responseWithLink(link))).toEqual({
37+
nextCursor: null,
38+
hasMore: false,
39+
})
40+
})
41+
42+
it('picks the next link when self is advertised alongside it', () => {
43+
const link = `<${BASE}?limit=200>; rel="self", <${BASE}?after=page2>; rel="next"`
44+
expect(parseOktaPagination(responseWithLink(link))).toEqual({
45+
nextCursor: 'page2',
46+
hasMore: true,
47+
})
48+
})
49+
50+
it('reports the last page when no Link header is sent at all', () => {
51+
expect(parseOktaPagination(responseWithLink())).toEqual({ nextCursor: null, hasMore: false })
52+
})
53+
54+
it('reports more results without a cursor when the next link is malformed', () => {
55+
expect(parseOktaPagination(responseWithLink('<not a url>; rel="next"'))).toEqual({
56+
nextCursor: null,
57+
hasMore: true,
58+
})
59+
})
60+
})
61+
62+
describe('mapOktaGroupRule', () => {
63+
it('lifts the expression, target groups, and exclusions to the top level', () => {
64+
const mapped = mapOktaGroupRule({
65+
id: '0pr1',
66+
name: 'Engineers',
67+
type: 'group_rule',
68+
status: 'ACTIVE',
69+
created: '2026-01-01T00:00:00.000Z',
70+
lastUpdated: '2026-01-02T00:00:00.000Z',
71+
conditions: {
72+
expression: { value: 'user.department == "Eng"', type: 'urn:okta:expression:1.0' },
73+
people: { users: { exclude: ['00u1'] }, groups: { exclude: ['00g1'] } },
74+
},
75+
actions: { assignUserToGroups: { groupIds: ['00g2'] } },
76+
})
77+
78+
expect(mapped).toMatchObject({
79+
id: '0pr1',
80+
expression: 'user.department == "Eng"',
81+
expressionType: 'urn:okta:expression:1.0',
82+
assignUserToGroupIds: ['00g2'],
83+
excludedUserIds: ['00u1'],
84+
excludedGroupIds: ['00g1'],
85+
})
86+
})
87+
88+
it('defaults absent nested conditions instead of throwing', () => {
89+
expect(
90+
mapOktaGroupRule({ id: '0pr2', name: 'Bare', type: 'group_rule', status: 'INACTIVE' })
91+
).toMatchObject({
92+
created: null,
93+
lastUpdated: null,
94+
expression: null,
95+
expressionType: null,
96+
assignUserToGroupIds: [],
97+
excludedUserIds: [],
98+
excludedGroupIds: [],
99+
})
100+
})
101+
})

0 commit comments

Comments
 (0)