Skip to content

Commit 34b0e4c

Browse files
added tests
1 parent 1c98680 commit 34b0e4c

10 files changed

Lines changed: 1082 additions & 0 deletions
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (c) 2026-present, Okta, Inc. and/or its affiliates. All rights reserved.
3+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4+
*
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
*
10+
* See the License for the specific language governing permissions and limitations under the License.
11+
*/
12+
13+
import { IDX_STEP } from 'src/constants';
14+
import { getStubFormBag, getStubTransactionWithNextStep } from 'src/mocks/utils/utils';
15+
import {
16+
DescriptionElement,
17+
LinkElement,
18+
OpenOktaVerifyFPButtonElement,
19+
TitleElement,
20+
WidgetProps,
21+
} from 'src/types';
22+
23+
import * as idxUtils from '../../util/idxUtils';
24+
import { transformNfcPinDeviceChallenge } from './transformNfcPinDeviceChallenge';
25+
26+
describe('transformNfcPinDeviceChallenge', () => {
27+
const transaction = getStubTransactionWithNextStep();
28+
const formBag = getStubFormBag();
29+
const widgetProps: WidgetProps = {};
30+
31+
beforeEach(() => {
32+
formBag.uischema.elements = [];
33+
transaction.nextStep = {
34+
name: 'challenge-poll',
35+
relatesTo: {
36+
value: {
37+
// @ts-expect-error contextualData not fully typed
38+
contextualData: {
39+
challenge: {
40+
value: {
41+
challengeMethod: 'CUSTOM_URI',
42+
href: 'com-okta-authenticator:/deviceChallenge?challengeRequest=mock-jwt',
43+
downloadHref: 'https://apps.apple.com/us/app/okta-verify/id490179405',
44+
},
45+
},
46+
},
47+
type: 'proximity',
48+
key: 'nfc_pin',
49+
id: 'npc123',
50+
displayName: 'NFC',
51+
methods: [{ type: 'nfc_pin' }],
52+
},
53+
},
54+
};
55+
transaction.availableSteps = [{ name: IDX_STEP.SELECT_AUTHENTICATOR_AUTHENTICATE }];
56+
jest.spyOn(idxUtils, 'hasMinAuthenticatorOptions').mockReturnValue(true);
57+
});
58+
59+
afterEach(() => {
60+
jest.restoreAllMocks();
61+
});
62+
63+
it('should show NFC-specific title "Verify with NFC"', () => {
64+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
65+
66+
const titleEl = result.uischema.elements[0] as TitleElement;
67+
expect(titleEl.type).toBe('Title');
68+
expect(titleEl.options.content).toBe('oie.nfc_pin.challenge.verify.title');
69+
});
70+
71+
it('should show NFC-specific description', () => {
72+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
73+
74+
const descEl = result.uischema.elements[1] as DescriptionElement;
75+
expect(descEl.type).toBe('Description');
76+
expect(descEl.options.content).toBe('oie.nfc_pin.challenge.verify.description');
77+
});
78+
79+
it('should include OpenOktaVerifyFPButton with correct href', () => {
80+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
81+
82+
const buttonEl = result.uischema.elements[2] as OpenOktaVerifyFPButtonElement;
83+
expect(buttonEl.type).toBe('OpenOktaVerifyFPButton');
84+
expect(buttonEl.options.href).toBe('com-okta-authenticator:/deviceChallenge?challengeRequest=mock-jwt');
85+
expect(buttonEl.options.challengeMethod).toBe('CUSTOM_URI');
86+
});
87+
88+
it('should include "Verify with something else" link when multiple authenticators available', () => {
89+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
90+
91+
const selectLink = result.uischema.elements[3] as LinkElement;
92+
expect(selectLink.type).toBe('Link');
93+
expect(selectLink.options.label).toBe('oie.verification.switch.authenticator');
94+
});
95+
96+
it('should NOT include "Verify with something else" when only one authenticator', () => {
97+
jest.spyOn(idxUtils, 'hasMinAuthenticatorOptions').mockReturnValue(false);
98+
99+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
100+
101+
// Without switch link: [Title, Description, OpenOktaVerifyFPButton, CancelLink]
102+
expect(result.uischema.elements.length).toBe(4);
103+
const lastEl = result.uischema.elements[3] as LinkElement;
104+
expect(lastEl.options.label).toBe('goback');
105+
});
106+
107+
it('should include "Back to sign in" cancel link', () => {
108+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
109+
110+
const lastEl = result.uischema.elements[result.uischema.elements.length - 1] as LinkElement;
111+
expect(lastEl.type).toBe('Link');
112+
expect(lastEl.options.step).toBe('cancel');
113+
expect(lastEl.options.label).toBe('goback');
114+
});
115+
116+
it('should NOT include download link (not relevant for NFC)', () => {
117+
const result = transformNfcPinDeviceChallenge({ transaction, formBag, widgetProps });
118+
119+
// With switch link: [Title, Description, OpenOktaVerifyFPButton, SwitchAuthLink, CancelLink]
120+
expect(result.uischema.elements.length).toBe(5);
121+
// No element should have a download href
122+
const hasDownloadLink = result.uischema.elements.some(
123+
(el) => (el as LinkElement).options?.href?.includes('apple.com'),
124+
);
125+
expect(hasDownloadLink).toBe(false);
126+
});
127+
});
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*
2+
* Copyright (c) 2026-present, Okta, Inc. and/or its affiliates. All rights reserved.
3+
* The Okta software accompanied by this notice is provided pursuant to the Apache License, Version 2.0 (the "License.")
4+
*
5+
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.
6+
* Unless required by applicable law or agreed to in writing, software
7+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
8+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
*
10+
* See the License for the specific language governing permissions and limitations under the License.
11+
*/
12+
13+
import { IDX_STEP } from 'src/constants';
14+
import { getStubFormBag, getStubTransactionWithNextStep } from 'src/mocks/utils/utils';
15+
import {
16+
DescriptionElement,
17+
FieldElement,
18+
LinkElement,
19+
OpenOktaVerifyFPButtonElement,
20+
TitleElement,
21+
WidgetProps,
22+
} from 'src/types';
23+
24+
import * as idxUtils from '../../util/idxUtils';
25+
import { transformNfcPinEnroll } from './transformNfcPinEnroll';
26+
27+
describe('transformNfcPinEnroll', () => {
28+
const widgetProps: WidgetProps = {};
29+
30+
describe('Device Challenge Phase (no passcode field)', () => {
31+
let transaction: ReturnType<typeof getStubTransactionWithNextStep>;
32+
let formBag: ReturnType<typeof getStubFormBag>;
33+
34+
beforeEach(() => {
35+
transaction = getStubTransactionWithNextStep();
36+
formBag = getStubFormBag();
37+
formBag.uischema.elements = [];
38+
transaction.nextStep = { name: 'enroll-poll' };
39+
transaction.rawIdxState = {
40+
...transaction.rawIdxState,
41+
currentAuthenticator: {
42+
type: 'object',
43+
value: {
44+
// @ts-expect-error contextualData not fully typed
45+
contextualData: {
46+
setupNfcUrl: 'com-okta-authenticator:/actions/enroll?authenticator_key=nfc&nonce=mock',
47+
},
48+
type: 'proximity',
49+
key: 'nfc_pin',
50+
id: 'aut123',
51+
displayName: 'NFC',
52+
methods: [{ type: 'nfc_pin' }],
53+
},
54+
},
55+
};
56+
transaction.availableSteps = [{ name: IDX_STEP.SELECT_AUTHENTICATOR_ENROLL }];
57+
jest.spyOn(idxUtils, 'hasMinAuthenticatorOptions').mockReturnValue(true);
58+
});
59+
60+
afterEach(() => {
61+
jest.restoreAllMocks();
62+
});
63+
64+
it('should show "Set up NFC" title', () => {
65+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
66+
67+
const titleEl = result.uischema.elements[0] as TitleElement;
68+
expect(titleEl.type).toBe('Title');
69+
expect(titleEl.options.content).toBe('oie.enroll.nfc_pin.title');
70+
});
71+
72+
it('should show NFC enrollment description', () => {
73+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
74+
75+
const descEl = result.uischema.elements[1] as DescriptionElement;
76+
expect(descEl.type).toBe('Description');
77+
expect(descEl.options.content).toBe('oie.enroll.nfc_pin.instructions');
78+
});
79+
80+
it('should include OpenOktaVerifyFPButton with setupNfcUrl', () => {
81+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
82+
83+
const buttonEl = result.uischema.elements[2] as OpenOktaVerifyFPButtonElement;
84+
expect(buttonEl.type).toBe('OpenOktaVerifyFPButton');
85+
expect(buttonEl.options.href).toBe('com-okta-authenticator:/actions/enroll?authenticator_key=nfc&nonce=mock');
86+
expect(buttonEl.options.challengeMethod).toBe('CUSTOM_URI');
87+
});
88+
89+
it('should include "Return to authenticator list" link', () => {
90+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
91+
92+
// Element order: [Title, Description, OpenOktaVerifyFPButton, ReturnLink, CancelLink]
93+
const returnLink = result.uischema.elements[3] as LinkElement;
94+
expect(returnLink.type).toBe('Link');
95+
expect(returnLink.options.label).toBe('oie.enroll.switch.authenticator');
96+
});
97+
98+
it('should include "Back to sign in" cancel link', () => {
99+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
100+
101+
const lastEl = result.uischema.elements[result.uischema.elements.length - 1] as LinkElement;
102+
expect(lastEl.type).toBe('Link');
103+
expect(lastEl.options.step).toBe('cancel');
104+
expect(lastEl.options.label).toBe('goback');
105+
});
106+
107+
it('should NOT include download link', () => {
108+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
109+
110+
// Only 5 elements: [Title, Description, OpenOktaVerifyFPButton, ReturnLink, CancelLink]
111+
expect(result.uischema.elements.length).toBe(5);
112+
const hasDownloadLink = result.uischema.elements.some(
113+
(el) => (el as LinkElement).options?.href?.includes('apple.com'),
114+
);
115+
expect(hasDownloadLink).toBe(false);
116+
});
117+
});
118+
119+
describe('PIN Creation Phase (has passcode field)', () => {
120+
let transaction: ReturnType<typeof getStubTransactionWithNextStep>;
121+
let formBag: ReturnType<typeof getStubFormBag>;
122+
123+
beforeEach(() => {
124+
transaction = getStubTransactionWithNextStep();
125+
formBag = getStubFormBag();
126+
formBag.uischema.elements = [
127+
{
128+
type: 'Field',
129+
label: 'NFC',
130+
key: 'credentials.passcode',
131+
options: { inputMeta: { name: 'credentials.passcode', secret: true } },
132+
} as FieldElement,
133+
];
134+
transaction.nextStep = {
135+
name: 'enroll-authenticator',
136+
relatesTo: {
137+
value: {
138+
type: 'proximity',
139+
key: 'nfc_pin',
140+
id: 'aut123',
141+
displayName: 'NFC',
142+
methods: [{ type: 'nfc_pin' }],
143+
// @ts-expect-error settings not fully typed
144+
settings: { minLength: 4, maxLength: 4 },
145+
},
146+
},
147+
};
148+
transaction.availableSteps = [{ name: IDX_STEP.SELECT_AUTHENTICATOR_ENROLL }];
149+
});
150+
151+
it('should delegate to transformNfcPinCreate when passcode field exists', () => {
152+
const result = transformNfcPinEnroll({ transaction, formBag, widgetProps });
153+
154+
// transformNfcPinCreate adds Title as first element
155+
const titleEl = result.uischema.elements[0] as TitleElement;
156+
expect(titleEl.type).toBe('Title');
157+
expect(titleEl.options.content).toBe('oie.enroll.nfc_pin.create.title');
158+
});
159+
});
160+
});

0 commit comments

Comments
 (0)