Skip to content

feat(evo-flow): surface the CRM rejection reason on a 422, not a generic error (EVO-2203) - #111

Merged
gomessguii merged 2 commits into
developfrom
feat/EVO-2203
Jul 25, 2026
Merged

feat(evo-flow): surface the CRM rejection reason on a 422, not a generic error (EVO-2203)#111
gomessguii merged 2 commits into
developfrom
feat/EVO-2203

Conversation

@nickoliveira23

@nickoliveira23 nickoliveira23 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The three pipeline nodes (assign-to-pipeline, move-to-pipeline-stage, create-pipeline-task) already turn a CRM failure into a visible node error via createErrorResult(error) — the EVO-1740 pattern. But a 422 threw BadRequestException(body), whose .message is generic, so an archived-pipeline refusal reached the journey run as "Bad Request Exception". The 422 handler now lifts the CRM envelope's error.message to the exception message, so the run shows the reason ("Pipeline is archived and cannot receive conversations") while getResponse() keeps error.code for callers that branch on it.

Test plan

  • npx jest src/shared/crm-client/crm-client.service.spec.ts — 16 passed, incl. the new "surfaces the CRM error message on a 422 envelope, keeping the code"
  • npx jest src/shared/crm-client/crm-client.hardening.spec.ts — 11 passed (no regression on the shared 422 path)
  • npx eslint on the touched file — no new error vs baseline; npx tsc --noEmit clean

Changed Files

  • src/shared/crm-client/crm-client.service.ts
  • src/shared/crm-client/crm-client.service.spec.ts

Related PRs

Linked Issue

Summary by Sourcery

Surface CRM 422 rejection reasons as readable messages in BadRequestException responses while preserving the underlying error code for callers.

Enhancements:

  • Introduce a typed CRM 422 error envelope and update 422 handling to lift the CRM error message into the exception payload while retaining the error code.

Tests:

  • Add a test ensuring 422 responses expose the CRM error message to callers and keep the error code available via the response.

…ric error (EVO-2203)

The pipeline nodes already turn a CRM failure into a visible node error via
createErrorResult(error), but a 422 threw BadRequestException(body) whose
message is generic — so an archived-pipeline refusal reached the journey run
as "Bad Request Exception". The 422 handler now lifts the CRM envelope's
error.message to the exception message, so the run shows the reason
("Pipeline is archived and cannot receive conversations") while getResponse()
keeps error.code for callers that branch on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adjusts CRM 422 error handling to surface the CRM-provided rejection reason in the BadRequestException while preserving the structured error code, and adds a focused test to validate the new behavior.

Sequence diagram for updated CRM 422 error handling

sequenceDiagram
  actor Caller
  participant CrmClientService
  participant CRM

  Caller->>CrmClientService: post(path, body)
  CrmClientService->>CRM: fetch(path, body)
  CRM-->>CrmClientService: 422 response

  alt response.json succeeds
    CrmClientService->>CrmClientService: response.json()
    CrmClientService->>CrmClientService: BadRequestException({ ...errorBody, message: errorBody.error.message })
  else response.json throws
    CrmClientService->>CrmClientService: response.text()
    CrmClientService->>CrmClientService: BadRequestException(reason)
  end
Loading

File-Level Changes

Change Details Files
Refine 422-response handling in the CRM client to extract the CRM error envelope and set a meaningful BadRequestException message while retaining the error code for callers.
  • Introduce a CrmErrorEnvelope interface modeling the CRM 422 error structure with nested error.code and error.message.
  • Change the 422 branch to parse JSON into CrmErrorEnvelope, falling back to text when JSON parsing fails.
  • Compute a human-readable reason from errorBody.error.message or the raw text, with a safe default if no message is available.
  • Throw BadRequestException with either an augmented envelope that includes a top-level message or the plain reason string, ensuring the exception.message reflects the CRM rejection reason.
src/shared/crm-client/crm-client.service.ts
Add a unit test to ensure the CRM error message and code are correctly surfaced on 422 responses.
  • Create a test case for a 422 response containing a PIPELINE_ARCHIVED CRM error envelope with a detailed message.
  • Assert that the rejected error has the expected message string and preserves the error.code inside the response payload.
src/shared/crm-client/crm-client.service.spec.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

… actually use (EVO-2203)

Code review of #111. The 422 message lift landed in requestGeneric, whose only
consumer is contacts-client. The three pipeline nodes call addToPipeline /
moveToPipelineStage / createPipelineTask, which go through executeRequest — its
422 branch was untouched, so the journey run kept showing the raw JSON envelope
("CRM Validation error: {\"success\":false,\"error\":{...}}"). describeCrm422 now
folds code and reason into that string. The "CRM Validation error" prefix stays:
executeRequest matches on it to not retry a refusal.

Spreading the envelope also overwrote a top-level `message` with the placeholder,
so a 422 from render_record_invalid's fallback lost its reason ("Email is
invalid" became "CRM rejected the request"). crmRejectionReason now prefers
error.message, then a top-level message, then the raw body.

Tests: the refusal is covered where the nodes read it (client level, both
endpoints, no retry) and end to end on assign-to-pipeline with a real client —
the node specs mock the CRM client away, which is why the gap went unseen.
@gomessguii
gomessguii merged commit 93111c5 into develop Jul 25, 2026
6 checks passed
@gomessguii
gomessguii deleted the feat/EVO-2203 branch July 25, 2026 00:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants