|
| 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