Skip to content

fix(journey-client): create JourneyLoginFailure step, handle Login Failure case#574

Open
vatsalparikh wants to merge 3 commits intomainfrom
sdks-4796
Open

fix(journey-client): create JourneyLoginFailure step, handle Login Failure case#574
vatsalparikh wants to merge 3 commits intomainfrom
sdks-4796

Conversation

@vatsalparikh
Copy link
Copy Markdown
Contributor

@vatsalparikh vatsalparikh commented Apr 20, 2026

JIRA Ticket

https://pingidentity.atlassian.net/browse/SDKS-4796

Description

While working on the Journey Client migration, I noticed that when AM returns a failure response (e.g., unauthorized / invalid credentials), the Journey Client was returning a generic “no data” error and the LoginFailure path in createJourneyObject() was effectively never reached. This PR closes that gap by ensuring LoginFailure is triggered and a JourneyLoginFailure is returned when the server provides a failure payload with a code.

What changed

  • start() / next() now map RTK Query error responses that include a code into createJourneyObject(), which returns JourneyLoginFailure (hitting the previously-unreached LoginFailure branch).
  • Added unit coverage for the LoginFailure mapping.
  • Updated the journey-app e2e consumer to avoid throwing on LoginFailure and instead renders the LoginFailure error message.
  • Added a changeset for @forgerock/journey-client.

Edit

Ryan shared his closed PR that was trying to fix this issue: #541. Will use this as a reference.

How to test

  • Build and start the e2e/journey-app
  • Go to http://localhost:5829/?clientId=tenant and enter incorrect credentials
  • You should see 'Login Failure' with the change in this PR. When you remove the error handling logic from journey.api.ts file, you should see unknown node in console, which means login failure is not handled as a step.

Summary by CodeRabbit

  • Bug Fixes

    • Authentication failures that include step-like responses are now reported as login failures instead of generic errors.
    • Login form submit failures update only the error UI, avoiding a full form re-render.
  • Tests

    • Expanded tests for authentication error translation, response handling, and redirect behavior.
  • Chores

    • Minor public typings and helper exports added to improve result handling and diagnostics.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 20, 2026

🦋 Changeset detected

Latest commit: de777ff

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@forgerock/journey-client Patch
@forgerock/davinci-client Patch
@forgerock/device-client Patch
@forgerock/oidc-client Patch
@forgerock/protect Patch
@forgerock/sdk-types Patch
@forgerock/sdk-utilities Patch
@forgerock/iframe-manager Patch
@forgerock/sdk-logger Patch
@forgerock/sdk-oidc Patch
@forgerock/sdk-request-middleware Patch
@forgerock/storage Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes mapping of RTK Query results into a single JourneyResult via resolveJourneyResult, updates start/next to return that result, adds tests for treating 401 responses with Step-shaped payloads as LoginFailure, minor import/metadata tweaks, and an e2e change to render only the error UI on LoginFailure.

Changes

Core Journey Result Mapping

Layer / File(s) Summary
Types / Exports
packages/journey-client/src/types.ts
Re-exports WellknownResponse and callbackType from @forgerock/sdk-types.
Data Shape / Helpers
packages/journey-client/src/lib/journey.utils.ts
Adds JourneyResult union, STEP_LIKE_KEYS, type guards, and exported resolveJourneyResult(data, error); createJourneyObject becomes an exported function and preserves step-type selection logic.
Core Client Integration
packages/journey-client/src/lib/client.store.ts
start and next now destructure { data, error } from dispatched endpoint results and return resolveJourneyResult(data, error) directly (removes prior manual no_response_data branch).
Release Metadata
.changeset/whole-mangos-find.md
Adds a patch changeset documenting LoginFailure mapping behavior.
Header / Imports
packages/journey-client/src/lib/journey.api.ts
Minor import ordering and copyright year update.

Tests and Mocks

Layer / File(s) Summary
Test Environment / Imports
packages/journey-client/src/lib/client.store.test.ts
Switches to Node Vitest environment and imports types from local ../types.js.
Mocking / Helpers
packages/journey-client/src/lib/client.store.test.ts
Extends setupMockFetch to accept authenticateStatus; /authenticate mock now rejects when journeyResponse is null and returns JSON Response with supplied status for Step payloads.
Behavioral Tests
packages/journey-client/src/lib/client.store.test.ts, packages/journey-client/src/lib/journey.utils.test.ts
Adds tests asserting 401 /authenticate with Step payload maps to LoginFailure (includes payload and getter accessors). Adds resolveJourneyResult unit tests covering no-data, request-failed, SerializedError, and Step-shaped error payloads. Updates expected GenericError kinds/messages.
Node Shims for Tests
packages/journey-client/src/lib/client.store.test.ts
Adds window.location getter shim to allow spying in Node Vitest.

E2E UI

Layer / File(s) Summary
Type Imports
e2e/journey-app/main.ts
Reorders type-only imports (JourneyClient, RequestMiddleware) inside existing import block (type-only).
UI Error Handling
e2e/journey-app/main.ts
On LoginFailure in the submit flow, replaces renderForm() with renderError() so only the error UI is updated.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (caller)
  participant JC as JourneyClient.start/next
  participant Store as Store.dispatch
  participant Backend as Remote API

  Client->>JC: call start/next(params)
  JC->>Store: dispatch request action
  Store->>Backend: network request (/authenticate)
  Backend-->>Store: Response (data OR error with error.data)
  Store-->>JC: { data, error }
  JC->>JC: resolveJourneyResult(data, error) -> createJourneyObject if step-shaped
  JC-->>Client: JourneyResult (Step | LoginSuccess | LoginFailure | GenericError)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • ryanbas21
  • ancheetah
  • cerebrl

Poem

🐇 I hopped through code, a curious sight,
Found errors wearing step-shaped light.
I nudged them outward, gave them voice,
Now failures surface — a sensible choice.
Tests clap paws; the journey’s right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly describes the main change: creating JourneyLoginFailure and handling the previously-unreachable LoginFailure case in the journey-client.
Description check ✅ Passed The description includes a JIRA ticket link and comprehensive details about the problem, changes, and testing approach, meeting the repository's template requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sdks-4796

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nx-cloud
Copy link
Copy Markdown
Contributor

nx-cloud Bot commented Apr 20, 2026

View your CI Pipeline Execution ↗ for commit a1822d2


☁️ Nx Cloud last updated this comment at 2026-05-05 02:54:23 UTC

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/journey-client/src/lib/client.store.test.ts (1)

265-277: Minor: globalThis.window is assigned but never cleaned up, and the initial defineProperty is redundant.

Two small points on the new shim:

  1. (globalThis as unknown as { window?: unknown }).window = {} leaks across tests within this file (no matching cleanup in afterEach). It happens to be benign today because this is the only test using window, but a future test run in any other order could observe a stray window global. Consider restoring it alongside locationSpy.mockRestore().
  2. The Object.defineProperty(window, 'location', { get: () => ({ assign: vi.fn() }) }) on lines 268–273 is immediately overridden by vi.spyOn(window, 'location', 'get').mockReturnValue(...) on line 274 — the inner assign: vi.fn() is never invoked, and the spread ...window.location on line 275 just reads the spied getter. Keeping only the defineProperty (so the property exists as a getter for spyOn to replace) with a minimal value, or dropping one of the two, would make the intent clearer.
♻️ Suggested cleanup
-    // Node test environment doesn't provide `window`, so create a minimal shim
-    // with a real `location` getter so we can keep using vi.spyOn(..., 'get').
-    (globalThis as unknown as { window?: unknown }).window = {};
-    Object.defineProperty(window, 'location', {
-      configurable: true,
-      get: () => ({
-        assign: vi.fn(),
-      }),
-    });
-    const locationSpy = vi.spyOn(window, 'location', 'get').mockReturnValue({
-      ...window.location,
-      assign: assignMock,
-    });
+    // Node test environment doesn't provide `window`, so create a minimal shim
+    // with a real `location` getter so we can keep using vi.spyOn(..., 'get').
+    (globalThis as unknown as { window: unknown }).window = {};
+    Object.defineProperty(window, 'location', {
+      configurable: true,
+      get: () => ({ assign: assignMock }),
+    });
+    const locationSpy = vi.spyOn(window, 'location', 'get');
     ...
     locationSpy.mockRestore();
+    delete (globalThis as unknown as { window?: unknown }).window;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.test.ts` around lines 265 - 277,
The test creates a global window shim and defines a getter for window.location
but never cleans up; modify the test to (1) make the minimal defineProperty for
window.location (so vi.spyOn can replace the getter) and remove the redundant
inner assign fn, and (2) restore the original state in afterEach by calling
locationSpy.mockRestore() and deleting or restoring globalThis.window (or saving
and reassigning the original window) so the global isn't leaked; reference the
locationSpy, assignMock and the temporary globalThis.window assignment when
updating the test.
packages/journey-client/src/lib/client.store.ts (1)

159-200: Consider extracting the error-mapping logic into a shared helper.

The start and next methods now contain near-identical blocks (lines 159–175 and 182–200) that unwrap { data, error }, map error.data with a defined code to createJourneyObject, and fall back to a GenericError with only the message string differing. Extracting a small helper (e.g., mapJourneyResult(result, fallbackMessage)) would remove the duplication and keep the two methods symmetric going forward.

Also, the errorData as Step | undefined cast is unchecked — if the server ever returns a non-object body (e.g., a string or HTML), errorStep?.code would still pass the optional-chain but could be against a non-Step shape. A narrow typeof errorData === 'object' && errorData !== null guard before the cast would be safer.

♻️ Proposed refactor
+    const mapResult = (
+      { data, error }: { data?: Step; error?: unknown },
+      fallbackMessage: string,
+    ): JourneyResult => {
+      if (data) {
+        return createJourneyObject(data);
+      }
+      const errorData = (error as FetchBaseQueryError | undefined)?.data;
+      if (typeof errorData === 'object' && errorData !== null) {
+        const errorStep = errorData as Step;
+        if (errorStep.code !== undefined) {
+          return createJourneyObject(errorStep);
+        }
+      }
+      return {
+        error: 'no_response_data',
+        message: fallbackMessage,
+        type: 'unknown_error',
+      };
+    };

Then start/next reduce to a single return mapResult(await store.dispatch(...), '...') call.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.ts` around lines 159 - 200,
Extract the duplicated error-unwrapping logic used in start and next into a
shared helper (e.g., mapJourneyResult or mapJourneyResponse) that accepts the
dispatch result and a fallback message; the helper should check for data and
return createJourneyObject(data) when present, then safely inspect error data by
first guarding typeof errorData === 'object' && errorData !== null before
treating it as a Step and returning createJourneyObject(errorStep) if
errorStep.code is defined, and finally return a GenericError object with the
provided fallbackMessage if neither path yields a Journey object—then replace
the duplicated blocks in start and next with a single call to this helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/src/lib/client.store.test.ts`:
- Around line 265-277: The test creates a global window shim and defines a
getter for window.location but never cleans up; modify the test to (1) make the
minimal defineProperty for window.location (so vi.spyOn can replace the getter)
and remove the redundant inner assign fn, and (2) restore the original state in
afterEach by calling locationSpy.mockRestore() and deleting or restoring
globalThis.window (or saving and reassigning the original window) so the global
isn't leaked; reference the locationSpy, assignMock and the temporary
globalThis.window assignment when updating the test.

In `@packages/journey-client/src/lib/client.store.ts`:
- Around line 159-200: Extract the duplicated error-unwrapping logic used in
start and next into a shared helper (e.g., mapJourneyResult or
mapJourneyResponse) that accepts the dispatch result and a fallback message; the
helper should check for data and return createJourneyObject(data) when present,
then safely inspect error data by first guarding typeof errorData === 'object'
&& errorData !== null before treating it as a Step and returning
createJourneyObject(errorStep) if errorStep.code is defined, and finally return
a GenericError object with the provided fallbackMessage if neither path yields a
Journey object—then replace the duplicated blocks in start and next with a
single call to this helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3033ff09-5ae0-426f-9ba5-70f1baf0c83b

📥 Commits

Reviewing files that changed from the base of the PR and between 9088443 and bec0866.

📒 Files selected for processing (4)
  • .changeset/journey-loginfailure-on-error-code.md
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
💤 Files with no reviewable changes (1)
  • e2e/journey-app/main.ts

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 20, 2026

Open in StackBlitz

@forgerock/davinci-client

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/davinci-client@574

@forgerock/device-client

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/device-client@574

@forgerock/journey-client

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/journey-client@574

@forgerock/oidc-client

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/oidc-client@574

@forgerock/protect

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/protect@574

@forgerock/sdk-types

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/sdk-types@574

@forgerock/sdk-utilities

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/sdk-utilities@574

@forgerock/iframe-manager

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/iframe-manager@574

@forgerock/sdk-logger

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/sdk-logger@574

@forgerock/sdk-oidc

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/sdk-oidc@574

@forgerock/sdk-request-middleware

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/sdk-request-middleware@574

@forgerock/storage

pnpm add https://pkg.pr.new/ForgeRock/ping-javascript-sdk/@forgerock/storage@574

commit: b109b87

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 15.80%. Comparing base (5d6747a) to head (b109b87).
⚠️ Report is 37 commits behind head on main.

❌ Your project status has failed because the head coverage (15.80%) is below the target coverage (40.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #574       +/-   ##
===========================================
- Coverage   70.90%   15.80%   -55.11%     
===========================================
  Files          53      154      +101     
  Lines        2021    26693    +24672     
  Branches      377     1136      +759     
===========================================
+ Hits         1433     4218     +2785     
- Misses        588    22475    +21887     
Files with missing lines Coverage Δ
packages/journey-client/src/lib/client.store.ts 82.20% <100.00%> (+8.83%) ⬆️
packages/journey-client/src/lib/journey.api.ts 66.66% <ø> (ø)
packages/journey-client/src/lib/journey.utils.ts 93.15% <100.00%> (+16.48%) ⬆️

... and 100 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

Deployed 262f196 to https://ForgeRock.github.io/ping-javascript-sdk/pr-574/262f196110ba6b93e1503b4818f11c7dfbedfba7 branch gh-pages in ForgeRock/ping-javascript-sdk

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 20, 2026

📦 Bundle Size Analysis

📦 Bundle Size Analysis

🚨 Significant Changes

🔻 @forgerock/device-client - 0.0 KB (-9.9 KB, -100.0%)
🔻 @forgerock/journey-client - 0.0 KB (-89.9 KB, -100.0%)

📊 Minor Changes

📉 @forgerock/device-client - 9.9 KB (-0.0 KB)
📈 @forgerock/journey-client - 90.4 KB (+0.5 KB)

➖ No Changes

@forgerock/davinci-client - 48.0 KB
@forgerock/oidc-client - 25.2 KB
@forgerock/sdk-utilities - 11.2 KB
@forgerock/sdk-types - 7.9 KB
@forgerock/protect - 150.1 KB
@forgerock/storage - 1.5 KB
@forgerock/sdk-oidc - 4.8 KB
@forgerock/sdk-request-middleware - 4.5 KB
@forgerock/sdk-logger - 1.6 KB
@forgerock/iframe-manager - 2.4 KB


14 packages analyzed • Baseline from latest main build

Legend

🆕 New package
🔺 Size increased
🔻 Size decreased
➖ No change

ℹ️ How bundle sizes are calculated
  • Current Size: Total gzipped size of all files in the package's dist directory
  • Baseline: Comparison against the latest build from the main branch
  • Files included: All build outputs except source maps and TypeScript build cache
  • Exclusions: .map, .tsbuildinfo, and .d.ts.map files

🔄 Updated automatically on each push to this PR

Copy link
Copy Markdown
Contributor

@SteinGabriel SteinGabriel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor findings.

Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.test.ts Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/journey-client/src/lib/journey.api.ts (1)

139-202: Extract the duplicated login-failure mapping into a helper.

The blocks at lines 139-155 and 186-202 are identical. Consider extracting into a small helper to reduce drift risk when the set of failure codes or the shape handling evolves.

♻️ Proposed refactor
+function mapLoginFailureResponse(
+  response: QueryReturnValue<unknown, FetchBaseQueryError, FetchBaseQueryMeta>,
+): QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> {
+  if ('error' in response) {
+    const errorData = (response.error as FetchBaseQueryError | undefined)?.data as
+      | Step
+      | undefined;
+    if (errorData?.code && LOGIN_FAILURE_CODES.includes(errorData.code)) {
+      return { data: errorData };
+    }
+  }
+  return response as QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta>;
+}

Then use return mapLoginFailureResponse(response); in both start and next.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.api.ts` around lines 139 - 202,
Extract the duplicated login-failure mapping into a small helper (e.g.
mapLoginFailureResponse) that accepts the endpoint response and returns a
QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> when the
response body is a Step with a code in LOGIN_FAILURE_CODES, or undefined/falsey
otherwise; move the logic that inspects ('error' in response), casts to
(response.error as FetchBaseQueryError)?.data as Step, checks data.code and
LOGIN_FAILURE_CODES into this helper, then replace the duplicated blocks in both
the start and next builder handlers with a single call like: return
mapLoginFailureResponse(response) ?? (continue with existing return).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/journey-client/src/lib/journey.api.ts`:
- Around line 139-202: Extract the duplicated login-failure mapping into a small
helper (e.g. mapLoginFailureResponse) that accepts the endpoint response and
returns a QueryReturnValue<Step, FetchBaseQueryError, FetchBaseQueryMeta> when
the response body is a Step with a code in LOGIN_FAILURE_CODES, or
undefined/falsey otherwise; move the logic that inspects ('error' in response),
casts to (response.error as FetchBaseQueryError)?.data as Step, checks data.code
and LOGIN_FAILURE_CODES into this helper, then replace the duplicated blocks in
both the start and next builder handlers with a single call like: return
mapLoginFailureResponse(response) ?? (continue with existing return).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d2a0334-1d00-4e63-85af-4145e953a6d4

📥 Commits

Reviewing files that changed from the base of the PR and between bec0866 and 16dd372.

📒 Files selected for processing (4)
  • .changeset/polite-horses-dig.md
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/journey-client/src/lib/client.store.ts
  • .changeset/polite-horses-dig.md

Copy link
Copy Markdown
Contributor

@SteinGabriel SteinGabriel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look great! Everything worked as expected when I tested it locally. Just need to update a stale copyright date.

Comment thread packages/journey-client/src/lib/journey.api.ts Outdated
Copy link
Copy Markdown
Collaborator

@cerebrl cerebrl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some alternative thoughts on this solution.

Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment thread packages/journey-client/src/lib/client.store.ts Outdated
Comment on lines +139 to +143
/**
* If the endpoint returned an HTTP error whose body is an AM Step with a
* login-failure code, treat it as successful data so callers receive the
* Step via the `data` path (keeps downstream logic simpler).
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about tightly coupling our understanding of "true errors" to validation or authentication errors by relying solely on the error code. The logic we use in the original SDK was much more "liberal". It essentially treated all errors as FRLoginFailure: https://github.com/ForgeRock/forgerock-javascript-sdk/blob/develop/packages/javascript-sdk/src/fr-auth/index.ts#L76.

Rather than doing all of this logic here, we could grab error from this destructure: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/client.store.ts#L174, and pass it to createJourneyObject here: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/client.store.ts#L183. Finally, just handle the logic here: https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/journey-client/src/lib/journey.utils.ts#L28.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, yeah, earlier logic in forgerock sdk was simply treating every error as FRLoginFailure so I tried to think about what constitutes as login failure and used 4xx codes.

Yeah, error codes can feel too restrictive but that's the only reliable data point about login failures. I think it just depends on what we want to classify as login failure. If we want to include 500 error from server as login failure as well, then yes, this is tightly coupled.

I looked at Ryan's closed PR and updated the logic of determining a login failure. If data contains any Step like properties then we treat it as login failure. Open to suggestions on whether you think this is the best way to determine a login failure.

Thanks for the suggestion on moving logic to journey utils. client.store file is now clean and all logic is handled by journey.utils file.

Thanks!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see Vatsal may have already implemented Justin's suggestion above but I've proposed another improvement below. We can handle the error either inside or outside of createJourneyObject, but I think it is important to also standardize the type narrowing of RTK errors throughout our SDK, checking for a Fetch error or Serialized error. After we know what type of error it is then we can return a LoginFailure or GenericError.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/journey-client/src/lib/journey.utils.ts (1)

49-50: Nit: consider hoisting resolveStep above createJourneyObject to drop the eslint-disable.

Declaring resolveStep before its only caller removes the need for // eslint-disable-next-line @typescript-eslint/no-use-before-define`` and reads linearly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.ts` around lines 49 - 50, Hoist
the helper function resolveStep so it is declared before its only caller
createJourneyObject; move the resolveStep function definition above
createJourneyObject, remove the now-unnecessary // eslint-disable-next-line
`@typescript-eslint/no-use-before-define` comment, and run lint to verify no
use-before-define warning remains. Ensure resolveStep and createJourneyObject
names are unchanged to preserve references.
packages/journey-client/src/lib/client.store.test.ts (1)

388-399: Consider aligning the test name with the new assertion.

The test is named start_NoDataFromServer_ReturnsGenericError and previously validated error === 'no_response_data', but now asserts error === 'request_failed'. Since setupMockFetch(null) actually causes /authenticate to reject (i.e., the RTK Query error path with status: 'FETCH_ERROR'), this test is really covering the "fetch failed" case rather than the "server returned nothing" case. Renaming to something like start_FetchRejects_ReturnsRequestFailedError would better describe the scenario, and a separate start_ServerReturnsEmptyBody_ReturnsNoResponseData could cover the truly empty-response case via createJourneyObject(undefined, undefined) (already covered in journey.utils.test.ts).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/client.store.test.ts` around lines 388 - 399,
Rename the test to reflect that the fetch call rejects rather than the server
returning an empty body: update the test title from
start_NoDataFromServer_ReturnsGenericError to something like
start_FetchRejects_ReturnsRequestFailedError, leaving the test body unchanged
(it uses setupMockFetch(null), invokes journey({ config: mockConfig }) and
client.start(), and asserts via isGenericError(result) and result.error ===
'request_failed'); this makes it clear the scenario exercised is the RTK Query
fetch-error path rather than a no-response-data case.
packages/journey-client/src/lib/journey.utils.test.ts (1)

15-93: Solid coverage for the new createJourneyObject(step, error) signature.

Good branch coverage across (step with authId), (step with successUrl), (no step + no error → no_response_data), (no step + error with status → request_failed), (non-Step-like error.datarequest_failed), and (Step-like error.dataLoginFailure).

Consider adding one more case to lock in behavior for a 5xx (e.g., status: 500) that returns a Step-like error.data (i.e., a body containing one of STEP_LIKE_KEYS like code) — this currently maps to LoginFailure regardless of HTTP status. If that's the intended behavior, a test asserting it prevents future regressions; if the intent is login-specific (401/403) as discussed in earlier review rounds, the utility’s logic should be revisited.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.test.ts` around lines 15 - 93,
Add a test covering the case where error.status is 5xx but error.data is
Step-like to lock in current behavior (or to indicate needed change);
specifically update the createJourneyObject tests to include a case that calls
createJourneyObject(undefined, { status: 500, data: { code: 500, message:
'Server Err' } }) and assert whether it returns a JourneyLoginFailure (check
type === StepType.LoginFailure and payload equals the Step-like data, plus
validate getters like getCode/getMessage) to document/lock the handling of
STEP_LIKE_KEYS by createJourneyObject.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 20-34: The STEP_LIKE_KEYS heuristic is too permissive and causes
non-auth errors to be treated as a Step; tighten the check in resolveStep (and
the downstream createJourneyObject path) by requiring a more specific
login-shaped signature before casting error.data to Step — for example, require
either (a) an HTTP auth status (status or statusCode equal to 401 or 403) OR (b)
a login-specific field combination like authId or successUrl present, rather
than any single key from STEP_LIKE_KEYS; update STEP_LIKE_KEYS/use in
resolveStep to check these conditions and only cast to Step when they pass so
createJourneyObject only produces JourneyLoginFailure for true AM/login failure
shapes.

---

Nitpick comments:
In `@packages/journey-client/src/lib/client.store.test.ts`:
- Around line 388-399: Rename the test to reflect that the fetch call rejects
rather than the server returning an empty body: update the test title from
start_NoDataFromServer_ReturnsGenericError to something like
start_FetchRejects_ReturnsRequestFailedError, leaving the test body unchanged
(it uses setupMockFetch(null), invokes journey({ config: mockConfig }) and
client.start(), and asserts via isGenericError(result) and result.error ===
'request_failed'); this makes it clear the scenario exercised is the RTK Query
fetch-error path rather than a no-response-data case.

In `@packages/journey-client/src/lib/journey.utils.test.ts`:
- Around line 15-93: Add a test covering the case where error.status is 5xx but
error.data is Step-like to lock in current behavior (or to indicate needed
change); specifically update the createJourneyObject tests to include a case
that calls createJourneyObject(undefined, { status: 500, data: { code: 500,
message: 'Server Err' } }) and assert whether it returns a JourneyLoginFailure
(check type === StepType.LoginFailure and payload equals the Step-like data,
plus validate getters like getCode/getMessage) to document/lock the handling of
STEP_LIKE_KEYS by createJourneyObject.

In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 49-50: Hoist the helper function resolveStep so it is declared
before its only caller createJourneyObject; move the resolveStep function
definition above createJourneyObject, remove the now-unnecessary //
eslint-disable-next-line `@typescript-eslint/no-use-before-define` comment, and
run lint to verify no use-before-define warning remains. Ensure resolveStep and
createJourneyObject names are unchanged to preserve references.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23eb68ce-90c9-4575-adf7-55f70f46f8d7

📥 Commits

Reviewing files that changed from the base of the PR and between 16dd372 and a24550c.

📒 Files selected for processing (7)
  • .changeset/whole-mangos-find.md
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
  • packages/journey-client/src/lib/journey.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • .changeset/whole-mangos-find.md
  • packages/journey-client/src/lib/journey.api.ts

Comment on lines +20 to +34
const STEP_LIKE_KEYS = [
'authId',
'callbacks',
'code',
'description',
'detail',
'header',
'ok',
'realm',
'reason',
'stage',
'status',
'successUrl',
'tokenId',
] as const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Permissive STEP_LIKE_KEYS heuristic may misclassify generic errors as LoginFailure.

resolveStep accepts error.data as a Step when any single key from STEP_LIKE_KEYS is present. Several of those keys (status, ok, code, header, detail) are very common on non-AM error bodies. For example:

  • A proxy/CDN error body like { status: 503, code: 'UPSTREAM', description: '...' } would satisfy the heuristic, be cast to Step, then — because it has neither authId nor successUrl — fall into the LoginFailure branch in createJourneyObject, yielding a JourneyLoginFailure whose getCode()/getMessage()/getReason() return misleading or undefined values to callers that type-guard on type === 'LoginFailure'.

This conflicts with the earlier review intent to make this path login-specific. Consider tightening the heuristic to something that actually correlates with AM login-failure bodies (e.g., presence of a login-specific combination, or restricting the error-data path to HTTP 401/403 responses), or validating more fields before the as Step cast at Line 90.

🛡️ Example: require a login-failure-shaped body (status 401/403, plus a meaningful field combo)
-const STEP_LIKE_KEYS = [
-  'authId',
-  'callbacks',
-  'code',
-  'description',
-  'detail',
-  'header',
-  'ok',
-  'realm',
-  'reason',
-  'stage',
-  'status',
-  'successUrl',
-  'tokenId',
-] as const;
+const AUTH_STEP_KEYS = ['authId', 'callbacks', 'successUrl', 'tokenId'] as const;
+const LOGIN_FAILURE_STATUSES = new Set([401, 403]);
+
+function looksLikeAuthStep(data: Record<string, unknown>): boolean {
+  return AUTH_STEP_KEYS.some((key) => key in data);
+}
+
+function looksLikeLoginFailure(data: Record<string, unknown>, status: unknown): boolean {
+  return (
+    typeof status === 'number' &&
+    LOGIN_FAILURE_STATUSES.has(status) &&
+    ('code' in data || 'reason' in data || 'detail' in data)
+  );
+}
@@
-  const data = errorObj?.data;
-  if (data && typeof data === 'object' && STEP_LIKE_KEYS.some((key) => key in data)) {
-    return data as Step;
-  }
+  const data = errorObj?.data;
+  if (
+    data &&
+    typeof data === 'object' &&
+    (looksLikeAuthStep(data as Record<string, unknown>) ||
+      looksLikeLoginFailure(data as Record<string, unknown>, errorObj?.status))
+  ) {
+    return data as Step;
+  }

Also applies to: 83-107

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.ts` around lines 20 - 34, The
STEP_LIKE_KEYS heuristic is too permissive and causes non-auth errors to be
treated as a Step; tighten the check in resolveStep (and the downstream
createJourneyObject path) by requiring a more specific login-shaped signature
before casting error.data to Step — for example, require either (a) an HTTP auth
status (status or statusCode equal to 401 or 403) OR (b) a login-specific field
combination like authId or successUrl present, rather than any single key from
STEP_LIKE_KEYS; update STEP_LIKE_KEYS/use in resolveStep to check these
conditions and only cast to Step when they pass so createJourneyObject only
produces JourneyLoginFailure for true AM/login failure shapes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
packages/journey-client/src/lib/journey.utils.ts (1)

20-34: ⚠️ Potential issue | 🟠 Major

Tighten the Step-like error heuristic before routing to LoginFailure.

STEP_LIKE_KEYS still includes very common error fields like code, status, detail, and ok, so unrelated API failures can be misclassified as JourneyLoginFailure. That makes the new error-path handling broader than the login-specific behavior this PR is trying to add.

🔧 Suggested tightening
-const STEP_LIKE_KEYS = [
-  'authId',
-  'callbacks',
-  'code',
-  'description',
-  'detail',
-  'header',
-  'ok',
-  'realm',
-  'reason',
-  'stage',
-  'status',
-  'successUrl',
-  'tokenId',
-] as const;
+const LOGIN_FAILURE_STATUSES = new Set([401, 403]);
+const STEP_LIKE_KEYS = ['authId', 'callbacks', 'successUrl', 'tokenId'] as const;
@@
-  if (data && typeof data === 'object' && STEP_LIKE_KEYS.some((key) => key in data)) {
+  if (
+    data &&
+    typeof data === 'object' &&
+    ((typeof errorObj?.status === 'number' && LOGIN_FAILURE_STATUSES.has(errorObj.status)) ||
+      STEP_LIKE_KEYS.some((key) => key in data))
+  ) {

Also applies to: 80-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/journey-client/src/lib/journey.utils.ts` around lines 20 - 34, The
STEP_LIKE_KEYS array is too permissive and causes unrelated API errors to be
treated as login failures; restrict the heuristic by removing generic fields
('code', 'status', 'detail', 'ok', 'description', 'reason') and only keep
login-specific identifiers (e.g., 'authId', 'callbacks', 'header', 'realm',
'tokenId', 'successUrl', 'stage') so only objects with explicit login-related
shape route to JourneyLoginFailure; update the STEP_LIKE_KEYS constant and any
other usage sites (the other occurrence referenced around the same file) to use
the tightened list so the LoginFailure path is only triggered for genuine
login-shaped payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/journey-client/src/lib/journey.utils.ts`:
- Around line 20-34: The STEP_LIKE_KEYS array is too permissive and causes
unrelated API errors to be treated as login failures; restrict the heuristic by
removing generic fields ('code', 'status', 'detail', 'ok', 'description',
'reason') and only keep login-specific identifiers (e.g., 'authId', 'callbacks',
'header', 'realm', 'tokenId', 'successUrl', 'stage') so only objects with
explicit login-related shape route to JourneyLoginFailure; update the
STEP_LIKE_KEYS constant and any other usage sites (the other occurrence
referenced around the same file) to use the tightened list so the LoginFailure
path is only triggered for genuine login-shaped payloads.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ce5ba34-661e-4c44-9881-10433079f8b2

📥 Commits

Reviewing files that changed from the base of the PR and between a24550c and b109b87.

📒 Files selected for processing (6)
  • e2e/journey-app/main.ts
  • packages/journey-client/src/lib/client.store.test.ts
  • packages/journey-client/src/lib/client.store.ts
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
  • packages/journey-client/src/lib/journey.utils.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/journey-client/src/lib/journey.api.ts
  • packages/journey-client/src/lib/journey.utils.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • e2e/journey-app/main.ts

Copy link
Copy Markdown
Collaborator

@ancheetah ancheetah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments about type imports and an alternative to how we could do the error handling.

Comment thread packages/journey-client/src/lib/client.store.test.ts Outdated
Comment on lines +22 to +23
import { callbackType, type GenericError, type Step } from '@forgerock/sdk-types';
import type { ActionTypes, RequestMiddleware } from '@forgerock/sdk-request-middleware';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these can be imported from @forgerock/journey-client/types as well. It's preferable to be able to import everything directly from journey client and this helps us test that we are providing all the necessary types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

Comment on lines +156 to +157
const { data, error } = await store.dispatch(journeyApi.endpoints.start.initiate(options));
return createJourneyObject(data, error);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mentioned this a few times before to the web team, but this is not the first time we've come across this RTK pattern where we have to handle data and error. There is a standard way of narrowing the error type and that is done elsewhere in the SDK and perhaps we should do the same here. Eventually we should have a utility in the utilities package that does this generic error handling. After we handle the error then we can call createJourneyObject(data). This may also be a good opportunity to use the Effect TS library but that may be out of the scope of this ticket.

https://github.com/ForgeRock/ping-javascript-sdk/blob/main/packages/device-client/src/lib/device.store.utils.ts

https://redux-toolkit.js.org/rtk-query/usage-with-typescript#type-safe-error-handling

@vatsalparikh vatsalparikh requested a review from cerebrl May 4, 2026 18:48
@vatsalparikh vatsalparikh force-pushed the sdks-4796 branch 3 times, most recently from 14a5f3d to 8649b37 Compare May 5, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

5 participants