Testing Guide — Demo UI

How to use the dynamic AVUI to exercise Wave 2 rule services — what to select, what to validate, and the full scenario catalog.

Quick links.

Before you start

UI layout — what each tab does

MAKE A TRANSFER

The real transfer form. The TO dropdown populates from GET /mock/accounts (60 accounts, grouped by category). The FROM dropdown has 3 mock bank accounts. You enter an amount, pick a contribution reason, and a retirement warning banner appears when an IRA account is selected. Submitting calls POST /ach/contribution with the real snake_case payload.

TRANSFER ACTIVITY

Shows the history of submitted transactions, fetched from the mock-api’s GET /mock/contributions endpoint. Pending transactions expose a Cancel button that fires DELETE /ach/queued/{txId} against the process-api.

API CONTRACTS

Static reference panel. Shows the actual request and response shape for each of the three new endpoints (POST /ach/contribution, POST /ach/withdrawal/with-reason, DELETE /ach/queued/{txId}) so you know exactly what to send and what to expect.

TESTING SCENARIOS

Ten one-click scenarios that auto-fill the form and submit the request for you. This is the fastest way to see each Wave 2 rule service fire. Each button triggers the exact payload documented below and renders the live response in the API Log panel.

Scenario catalog — click-by-click

Each scenario below corresponds to one button in the Testing Scenarios tab. You can also run any of them manually from the Make a Transfer tab using the selections listed. Expected responses are the exact bytes returned by the pipeline on 2026-04-24.

1. Hero — $20k Roth over-limit 🔴 Negative-test ✓ Live

What to select:

  • TO: 10000024 — Xander Roth IRA (RRH)
  • Contribution Reason: Current Year IRA Contribution
  • Amount: 20000

What the UI shows: a red rejection banner citing a ContributionLimit rule failure with HTTP 422. The API Log panel shows the full request and the exact error body.

What to validate: type: "ContributionLimit", status_code: 422, message reads “Projected contributions $20,000.00 exceed IRS limit $8,000.00 for RRH 2026”. The catch-up maths matter: $7,000 base + $1,000 age-50+ catch-up = $8,000 because the fixture sets IraData.Age=65.

Why it matters: this is the headline proof that ContributionLimitService is live, reads age from mock data, and applies the correct IRS catch-up.

Expected response body
{
  "status": "Fail",
  "status_code": 422,
  "data": {
    "error_message": {
      "type": "ContributionLimit",
      "code": 422,
      "errors": [{
        "message": "Projected contributions $20,000.00 exceed IRS limit $8,000.00 for RRH 2026"
      }]
    }
  }
}

2. Happy — $3k Roth current year ✓ Live

What to select:

  • TO: 10000024 — Xander Roth IRA (RRH)
  • Contribution Reason: Current Year IRA Contribution
  • Amount: 3000

What the UI shows: green success banner with a request ID. Transfer Activity tab gets a new Pending row.

What to validate: status: "Success", status_code: 200, scheduled_execution_date equals today + 1 business day, cm_request_id is a fresh GUID on every click.

Why it matters: proves the full pipeline (all 10 orchestrator steps, all 6 rule services) completes when inputs are valid.

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "a697e969-f2ba-4426-8d76-b951b07afcde"
  }
}

3. HSA rejected — no HDHP 🔴 Negative-test ✓ Live

What to select:

  • TO: 10000047 — Harris HSA (HMM)
  • HDHP checkbox: unchecked
  • Contribution Reason: HSA Contribution
  • Amount: 1000

What the UI shows: red rejection banner, HsaEligibility failure.

What to validate: type: "HsaEligibility", status_code: 422, message “HSA contribution requires HDHP coverage”.

Why it matters: confirms the HSA-specific rule fires only for H-prefixed class codes and gates on the HDHP metadata flag.

Expected response body
{
  "status": "Fail",
  "status_code": 422,
  "data": {
    "error_message": {
      "type": "HsaEligibility",
      "code": 422,
      "errors": [{ "message": "HSA contribution requires HDHP coverage" }]
    }
  }
}

4. HSA success — HDHP covered ✓ Live

What to select:

  • TO: 10000045 — Hampton HSA (HBK)
  • HDHP checkbox: checked
  • Contribution Reason: HSA Contribution
  • Amount: 1000

What the UI shows: green success banner; new Pending row in Transfer Activity.

What to validate: status: "Success", status_code: 200, scheduled_execution_date present.

Why it matters: proves the HSA rule doesn’t fire when the HDHP flag is set, and that HSA contributions flow through the pipeline identically to other contributions.

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "..."
  }
}

5. Prior-year window closed 🔴 Negative-test ✓ Live

What to select:

  • TO: 10000024 — Xander Roth IRA (RRH)
  • Amount: 3000
  • Contribution Year dropdown: 2025

What the UI shows: red rejection banner citing a ContributionYear rule failure.

What to validate: type: "ContributionYear", status_code: 422, message reads “Prior-year 2025 contribution window closed (after Apr 15)”. Note: this scenario is date-dependent. It only fails from Apr 16 onwards; before Apr 15 the same payload passes.

Why it matters: enforces the exact IRS Jan 1 – Apr 15 prior-year contribution window at the wire level.

Expected response body
{
  "status": "Fail",
  "status_code": 422,
  "data": {
    "error_message": {
      "type": "ContributionYear",
      "code": 422,
      "errors": [{ "message": "Prior-year 2025 contribution window closed (after Apr 15)" }]
    }
  }
}

6. SEP contribution ✓ Live

What to select:

  • TO: 10000031 — Orwell SEP IRA (QOI)
  • Contribution Reason: SEP Employer Contribution
  • Amount: 50000

What the UI shows: green success banner.

What to validate: status: "Success", status_code: 200. The 2026 SEP limit is $70,000 so $50,000 passes.

Why it matters: confirms ContributionLimitService picks the SEP limit table (not IRA) when metadata carries contribution_type: "SepEmployer".

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "..."
  }
}

7. SIMPLE IRA deferral ✓ Live

What to select:

  • TO: 10000037 — Sherwin SIMPLE IRA (QIS)
  • Contribution Reason: SIMPLE Employee
  • Amount: 10000

What the UI shows: green success banner.

What to validate: status: "Success", status_code: 200.

Why it matters: confirms the SIMPLE code path: ContributionLimitService picks the SIMPLE limit plus both catch-ups from metadata.

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "..."
  }
}

8. Brokerage deposit ✓ Live

What to select:

  • TO: 10000027 — Morris Brokerage (BMS)
  • Contribution Reason: Personal deposit
  • Amount: 500

What the UI shows: green success banner, no retirement warning.

What to validate: status: "Success", status_code: 200. No retirement rules fire because the class code starts with B (non-retirement).

Why it matters: confirms the rule layer correctly short-circuits for non-retirement accounts and doesn’t block ordinary brokerage deposits.

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "..."
  }
}

9. Withdrawal with RMD reason ✓ Live

What to select:

  • Switch to Withdrawal mode (sets transaction_type: "ACHD")
  • FROM: 10000024 — Xander Roth IRA (RRH)
  • Amount: 5000
  • Transfer Reason: RMD

What the UI shows: green success banner. Request is posted to POST /ach/withdrawal/with-reason, not /ach/contribution.

What to validate: status: "Success", status_code: 200. The API Log panel shows the withdrawal endpoint in the request URL.

Why it matters: proves UC-W2 (reason-coded withdrawal) is implementable; the TransferReason path is operational for RMD, ExcessReturn, and Other.

Expected response body
{
  "status": "Success",
  "status_code": 200,
  "data": {
    "scheduled_execution_date": "2026-04-27",
    "cm_request_id": "b2326ec5-3f83-4a96-b6fb-6f2a3f50c618"
  }
}

10. Cancel queued stub (UC-W4) ⚠ Stub

What to select:

  • In the Transfer Activity tab, click Cancel on any queued item.
  • OR enter txId DEMO-999 in the cancel box.

What the UI shows: acknowledgement toast with the cancellation status.

What to validate: the API Log panel shows DELETE /ach/queued/DEMO-999 returned 200 with body {"transaction_id":"DEMO-999","status":"canceled-stub"}. This is a stub — no persistent queue state changes.

Why it matters: locks down the contract for UC-W4 so the BFF/UI work can proceed ahead of Wave 3 when the real queue is wired.

Expected response body
{
  "transaction_id": "DEMO-999",
  "status": "canceled-stub"
}

What to check in the API Log panel

Troubleshooting.

Validation checklist — sign-off criteria

Run the 10 scenarios end-to-end before declaring the UI demo-ready. Tick each item as you go.

Advanced — crafting your own payloads

The 10 scenarios cover the happy and sad paths for every Wave 2 rule, but you can go beyond them by posting directly against the local stack.

The snake_case payload shape

{
  "transaction_request": {
    "account_id": 10000024,
    "lpl_account_number": "10000024",
    "amount": "3000.00",
    "firm_id": 1,
    "transaction_type": "ACHC",
    "origin_id": "AccountView",
    "beta_item_seq_no": "1",
    "requested_execution_date": "2026-04-24T12:00:00Z",
    "rep_id": "ABC1",
    "client_id": 1024,
    "transfer_reason": "IRA Contribution",
    "transfer_type": "contributionType",
    "user_context": { "advisor_user_name": "TestAdvisor" }
  },
  "contribution_metadata": {
    "contribution_year": 2026,
    "contribution_type": "Regular",
    "actor_type": "Investor",
    "ytd_contributions_amount": 0,
    "is_hdhp_covered": false,
    "rollover_type": null
  }
}

Enum values you can set

FieldAccepted values
contribution_typeRegular, CatchUp, Spousal, SepEmployer, SepSelf, SimpleEmployee, SimpleEmployer, Rollover, Conversion, Recharacterization, ExcessReturn
rollover_typeDirectTrustee, Indirect60Day, Roth401kToRothIra, IraToIra, Spousal, Inherited
transfer_reason (on withdrawal)RMD, ExcessReturn, Other
transaction_typeACHC (contribution/deposit) or ACHD (withdrawal)
actor_typeInvestor, Advisor, OPS

Sample cURL that reproduces Scenario 1 outside the UI

curl -X POST http://localhost:5001/ach/contribution \
  -H 'Content-Type: application/json' \
  -d '{
    "transaction_request": {
      "account_id": 10000024, "lpl_account_number": "10000024",
      "amount": "20000.00", "firm_id": 1, "transaction_type": "ACHC",
      "origin_id": "AccountView", "beta_item_seq_no": "1",
      "requested_execution_date": "2026-04-24T12:00:00Z",
      "rep_id": "ABC1", "client_id": 1024,
      "transfer_reason": "IRA Contribution", "transfer_type": "contributionType",
      "user_context": {"advisor_user_name": "TestAdvisor"}
    },
    "contribution_metadata": {
      "contribution_year": 2026, "contribution_type": "Regular",
      "actor_type": "Investor", "ytd_contributions_amount": 0
    }
  }'

This is the exact payload the UI sends when you click Scenario 1. Paste it in a terminal (Git Bash, WSL, or PowerShell with curl.exe) and you’ll get the same 422 ContributionLimit response.

Related links.