From 5d714577f4a508fa37afc2161880affd4ab2127f Mon Sep 17 00:00:00 2001 From: ryanbas21 Date: Fri, 30 May 2025 09:24:59 -0600 Subject: [PATCH] fix: device-options-field --- .changeset/angry-roses-wash.md | 5 + e2e/davinci-app/components/flow-link.ts | 1 + e2e/davinci-app/components/single-value.ts | 1 + .../src/phone-number-field.test.ts | 129 ++++++++++++++---- e2e/davinci-suites/src/register.test.ts | 2 +- e2e/davinci-suites/src/utils/demo-user.ts | 2 + .../src/lib/collector.utils.test.ts | 8 +- .../davinci-client/src/lib/collector.utils.ts | 18 +-- .../davinci-client/src/lib/davinci.types.ts | 4 +- .../davinci-client/src/lib/node.reducer.ts | 1 + 10 files changed, 128 insertions(+), 43 deletions(-) create mode 100644 .changeset/angry-roses-wash.md diff --git a/.changeset/angry-roses-wash.md b/.changeset/angry-roses-wash.md new file mode 100644 index 0000000000..81f32a43f0 --- /dev/null +++ b/.changeset/angry-roses-wash.md @@ -0,0 +1,5 @@ +--- +'@forgerock/davinci-client': patch +--- + +Fixes the device-fields which were changed to options on the object diff --git a/e2e/davinci-app/components/flow-link.ts b/e2e/davinci-app/components/flow-link.ts index 223f6cf57f..8e928758c6 100644 --- a/e2e/davinci-app/components/flow-link.ts +++ b/e2e/davinci-app/components/flow-link.ts @@ -16,6 +16,7 @@ export default function flowLinkComponent( button.classList.add('flow-link'); button.type = 'button'; + button.name = collector.output.label || 'no-label-provided-err'; button.innerText = collector.output.label; formEl?.appendChild(button); diff --git a/e2e/davinci-app/components/single-value.ts b/e2e/davinci-app/components/single-value.ts index 711b06ee7a..c3e4b8fab4 100644 --- a/e2e/davinci-app/components/single-value.ts +++ b/e2e/davinci-app/components/single-value.ts @@ -22,6 +22,7 @@ export default function singleValueComponent( labelEl.textContent = collector.output.label || 'Select an option'; labelEl.setAttribute('for', collector.output.key || 'dropdown-field'); labelEl.className = 'dropdown-label'; + labelEl.setAttribute('for', collector.output.key || 'dropdown-field'); // Create the select element const selectEl = document.createElement('select'); diff --git a/e2e/davinci-suites/src/phone-number-field.test.ts b/e2e/davinci-suites/src/phone-number-field.test.ts index 82ad8df647..4447200499 100644 --- a/e2e/davinci-suites/src/phone-number-field.test.ts +++ b/e2e/davinci-suites/src/phone-number-field.test.ts @@ -5,39 +5,114 @@ * of the MIT license. See the LICENSE file for details. */ import { expect, test } from '@playwright/test'; -import { asyncEvents } from './utils/async-events.js'; +import { password } from './utils/demo-user.js'; -test('Test happy paths on test page', async ({ page }) => { - const { navigate } = asyncEvents(page); - await navigate('/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); +test('Login - add email device - authenticate with email device', async ({ page }) => { + /** Go to page */ + await page.goto('/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); - expect(page.url()).toBe('http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); - - await expect(page.getByText('Create Your Profile')).toBeVisible(); + expect(page.url()).toContain( + 'http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e', + ); + /** + * Register a new user + */ + await page.goto('http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); + await expect(page.getByRole('button', { name: 'USER_REGISTRATION' })).toBeVisible(); + await page.getByRole('button', { name: 'USER_REGISTRATION' }).click(); + await page.getByRole('textbox', { name: 'Email' }).click(); + await page.getByRole('textbox', { name: 'Email' }).fill('fakeemail@user.com'); + await page.getByRole('textbox', { name: 'Password' }).fill('U.QPDWEN47ZMyJhCDmhGLK*nr'); + await page.getByRole('textbox', { name: 'Given Name' }).fill('demouser'); + await page.getByRole('textbox', { name: 'Family Name' }).fill('demouser'); + expect(await page.getByRole('button', { name: 'Continue' })).toBeVisible(); + await page.getByRole('button', { name: 'Continue' }).click(); + await expect(page.getByRole('heading', { name: 'Registration Complete' })).toBeVisible(); + await page.getByRole('button').click(); + await page.getByRole('button', { name: 'Logout' }).click(); + /*** + * Login with the new user + **/ + await page.goto('http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); + await page.getByRole('button', { name: 'USER_LOGIN' }).click(); + await page.getByText('SDK Automation - Sign On'); + await page.getByRole('textbox', { name: 'Username' }).fill('fakeemail@user.com'); + await page.getByRole('textbox', { name: 'Password' }).fill(password); + await page.getByRole('button', { name: 'Sign On' }).click(); - await page.getByLabel('Email Address').fill('test@test.com'); - await page.getByLabel('Password').fill('apassword'); - await page.getByLabel('Placeholder').fill('12345678901'); + /** Register a device */ + await page.getByText('Select Test Form'); + await page.getByRole('button', { name: 'DEVICE_REGISTRATION' }).click(); + await page.getByText('SDK Automation - Device Registration'); + await page.getByRole('button', { name: 'Email' }).click(); + await page.getByText('SDK Automation - Device Registration'); + await page.getByRole('textbox', { name: 'Email Address' }).fill('test+my_fake_user@example.com'); + await page.getByRole('button', { name: 'Submit' }).click(); + await expect(page.getByText('EMAIL MFA Registered')).toBeVisible(); + await page.getByRole('button').click(); - const requestPromise = page.waitForRequest((request) => - request - .url() - .includes( - 'https://auth.pingone.ca/02fb4743-189a-4bc7-9d6c-a919edfe6447/davinci/connections/8209285e0d2f3fc76bfd23fd10d45e6f/capabilities/customForm?next=true', - ), + /** Authenticate with the Device */ + await page.getByRole('button', { name: 'DEVICE_AUTHENTICATION' }).click(); + await page.getByText('SDK Automation - Device Authentication'); + await page.getByRole('button', { name: 'Email' }).click(); + await page.getByRole('button', { name: 'USER_DELETE' }).click(); + await page.getByRole('heading', { name: 'Success' }); + await page.getByRole('button', { name: 'Start over' }).click(); +}); +test('Login - add phone device - authenticate with phone device', async ({ page }) => { + await page.goto('/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); + /*** + * Go to page + ***/ + expect(page.url()).toContain( + 'http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e', ); + /** + * Register a new user + **/ + await page.goto('http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); + await expect(page.getByRole('button', { name: 'USER_REGISTRATION' })).toBeVisible(); + await page.getByRole('button', { name: 'USER_REGISTRATION' }).click(); + await page.getByRole('textbox', { name: 'Email' }).click(); + await page.getByRole('textbox', { name: 'Email' }).fill('fakeemail2@user.com'); + await page.getByRole('textbox', { name: 'Password' }).fill('U.QPDWEN47ZMyJhCDmhGLK*nr'); + await page.getByRole('textbox', { name: 'Given Name' }).fill('demouser'); + await page.getByRole('textbox', { name: 'Family Name' }).fill('demouser'); + expect(await page.getByRole('button', { name: 'Continue' })).toBeVisible(); + await page.getByRole('button', { name: 'Continue' }).click(); + await expect(page.getByRole('heading', { name: 'Registration Complete' })).toBeVisible(); + await page.getByRole('button').click(); + await page.getByRole('button', { name: 'Logout' }).click(); + await page.goto('http://localhost:5829/?clientId=20dd0ed0-bb9b-4c8f-9a60-9ebeb4b348e0'); + + /** + * Login with the new user + **/ + await page.getByRole('button', { name: 'USER_LOGIN' }).click(); + await page.getByText('SDK Automation - Sign On'); + await page.getByRole('textbox', { name: 'Username' }).fill('fakeemail2@user.com'); + await page.getByRole('textbox', { name: 'Password' }).fill(password); + await page.getByRole('button', { name: 'Sign On' }).click(); + + /** Register a Device */ + await page.getByText('Select Test Form'); + await page.getByRole('button', { name: 'DEVICE_REGISTRATION' }).click(); + await page.getByText('SDK Automation - Device Registration'); + await page.getByRole('button', { name: 'Text Message' }).click(); + await expect(page.getByText('SDK Automation - Enter Phone Number')).toBeVisible(); + await expect(page.locator('#countryCode')).toBeVisible(); + await page.locator('#countryCode').selectOption('1'); + await page.getByRole('textbox', { name: 'Enter Phone Number' }).fill('3035550100'); await page.getByRole('button', { name: 'Submit' }).click(); + await expect(page.getByText('SMS/Voice MFA Registered')).toBeVisible(); + await page.getByRole('button').click(); - const request = await requestPromise; - const postedData = JSON.parse(request.postData()); - const data = postedData.parameters.data; - expect(data).toEqual({ - actionKey: 'submit', - formData: { - 'user.email': 'test@test.com', - 'user.password': 'apassword', - 'phone-field': { phoneNumber: '12345678901', countryCode: 'CA' }, - }, - }); + /** Authenticate with the Device */ + await page.getByRole('button', { name: 'DEVICE_AUTHENTICATION' }).click(); + await page.getByText('SDK Automation - Device Authentication'); + await page.getByRole('button', { name: 'Text Message' }).click(); + await page.getByRole('button', { name: 'USER_DELETE' }).click(); + await page.getByRole('heading', { name: 'Success' }); + await page.getByRole('button', { name: 'Start over' }).click(); }); diff --git a/e2e/davinci-suites/src/register.test.ts b/e2e/davinci-suites/src/register.test.ts index f0e6646064..8dfba86576 100644 --- a/e2e/davinci-suites/src/register.test.ts +++ b/e2e/davinci-suites/src/register.test.ts @@ -8,7 +8,7 @@ import { expect, test } from '@playwright/test'; import { asyncEvents } from './utils/async-events.js'; import { password } from './utils/demo-user.js'; -test('Test happy paths on test page', async ({ page }) => { +test.skip('Test happy paths on test page', async ({ page }) => { const { navigate } = asyncEvents(page); await navigate('/'); diff --git a/e2e/davinci-suites/src/utils/demo-user.ts b/e2e/davinci-suites/src/utils/demo-user.ts index f267379cd6..b9e9fd45c0 100644 --- a/e2e/davinci-suites/src/utils/demo-user.ts +++ b/e2e/davinci-suites/src/utils/demo-user.ts @@ -6,3 +6,5 @@ */ export const username = 'demouser'; export const password = 'U.QPDWEN47ZMyJhCDmhGLK*nr'; +export const phoneNumber1 = '888123456'; +export const phoneNumber2 = '888123457'; diff --git a/packages/davinci-client/src/lib/collector.utils.test.ts b/packages/davinci-client/src/lib/collector.utils.test.ts index 2cd1b1553d..575ecb1d56 100644 --- a/packages/davinci-client/src/lib/collector.utils.test.ts +++ b/packages/davinci-client/src/lib/collector.utils.test.ts @@ -420,7 +420,7 @@ describe('Object value collectors', () => { key: 'device-auth-key', label: 'Device Authentication', type: 'DEVICE_AUTHENTICATION', - devices: [ + options: [ { type: 'device1', iconSrc: 'icon1.png', @@ -441,7 +441,7 @@ describe('Object value collectors', () => { required: true, }; - const transformedDevices = mockField.devices.map((device) => ({ + const transformedDevices = mockField.options.map((device) => ({ label: device.title, value: device.id, content: device.value, @@ -487,7 +487,7 @@ describe('Object value collectors', () => { key: 'device-reg-key', label: 'Device Registration', type: 'DEVICE_REGISTRATION', - devices: [ + options: [ { type: 'device1', iconSrc: 'icon1.png', @@ -504,7 +504,7 @@ describe('Object value collectors', () => { required: true, }; - const transformedDevices = mockField.devices.map((device, idx) => ({ + const transformedDevices = mockField.options.map((device, idx) => ({ label: device.title, value: device.type, content: device.description, diff --git a/packages/davinci-client/src/lib/collector.utils.ts b/packages/davinci-client/src/lib/collector.utils.ts index be5fce4424..c662b644dc 100644 --- a/packages/davinci-client/src/lib/collector.utils.ts +++ b/packages/davinci-client/src/lib/collector.utils.ts @@ -360,18 +360,18 @@ export function returnObjectCollector< error = `${error}Type is not found in the field object. `; } - let devices; + let options; let defaultValue; if (field.type === 'DEVICE_AUTHENTICATION') { - if (!('devices' in field)) { + if (!('options' in field)) { error = `${error}Device options are not found in the field object. `; } - if (Array.isArray(field.devices) && field.devices.length === 0) { + if (Array.isArray(field.options) && field.options.length === 0) { error = `${error}Device options are not an array or is empty. `; } - const unmappedDefault = field.devices.find((device) => device.default); + const unmappedDefault = field.options.find((device) => device.default); defaultValue = { type: unmappedDefault ? unmappedDefault.type : '', value: unmappedDefault ? unmappedDefault.value : '', @@ -379,7 +379,7 @@ export function returnObjectCollector< }; // Map DaVinci spec to normalized SDK API - devices = field.devices.map((device) => ({ + options = field.options.map((device) => ({ type: device.type, label: device.title, content: device.value, @@ -388,18 +388,18 @@ export function returnObjectCollector< default: device.default, })); } else if (field.type === 'DEVICE_REGISTRATION') { - if (!('devices' in field)) { + if (!('options' in field)) { error = `${error}Device options are not found in the field object. `; } - if (Array.isArray(field.devices) && field.devices.length === 0) { + if (Array.isArray(field.options) && field.options.length === 0) { error = `${error}Device options are not an array or is empty. `; } defaultValue = ''; // Map DaVinci spec to normalized SDK API - devices = field.devices.map((device, idx) => ({ + options = field.options.map((device, idx) => ({ type: device.type, label: device.title, content: device.description, @@ -428,7 +428,7 @@ export function returnObjectCollector< key: field.key, label: field.label, type: field.type, - ...(devices && { options: devices || [] }), + ...(options && { options: options || [] }), ...(defaultValue && { value: defaultValue }), }, } as InferValueObjectCollectorType; diff --git a/packages/davinci-client/src/lib/davinci.types.ts b/packages/davinci-client/src/lib/davinci.types.ts index b82d10eaf1..ba77f9bca8 100644 --- a/packages/davinci-client/src/lib/davinci.types.ts +++ b/packages/davinci-client/src/lib/davinci.types.ts @@ -120,7 +120,7 @@ export type DeviceAuthenticationField = { type: 'DEVICE_AUTHENTICATION'; key: string; label: string; - devices: { + options: { type: string; iconSrc: string; title: string; @@ -135,7 +135,7 @@ export type DeviceRegistrationField = { type: 'DEVICE_REGISTRATION'; key: string; label: string; - devices: { + options: { type: string; iconSrc: string; title: string; diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts index 5523dc19dc..4fd8aeaa9f 100644 --- a/packages/davinci-client/src/lib/node.reducer.ts +++ b/packages/davinci-client/src/lib/node.reducer.ts @@ -242,6 +242,7 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build if (typeof action.payload.id !== 'string') { throw new Error('Index argument must be a string'); } + // Iterate through the options object and find option to update const option = collector.output.options.find( (option) => option.value === action.payload.value,