Skip to content

SPRDT-1011: migrate listing courtscheduler calls to reshaped Phase-1 contract#13

Open
ozturkmenb wants to merge 13 commits into
team/ccsph2nfrom
dev/SPRDT-1011-listing-callers
Open

SPRDT-1011: migrate listing courtscheduler calls to reshaped Phase-1 contract#13
ozturkmenb wants to merge 13 commits into
team/ccsph2nfrom
dev/SPRDT-1011-listing-callers

Conversation

@ozturkmenb

Copy link
Copy Markdown

SPRDT-1011 — migrate listing's courtscheduler calls to the reshaped Phase-1 contract

Updates cpp-context-listing's courtscheduler client to the resource-based contract from the courtscheduler Phase-1 reshape (SPRDT-1089). In-place migration — no additive endpoints.

Calls migrated

Call Before After
search-by-id GET /courtschedule/search.court-schedules-by-id, param courtScheduleIds GET /sessions, param ids
release slots DELETE /hearingslots/{id} · remove.hearing.slots DELETE /sessions/{id} · release.sessions
list hearings PUT /list/hearingslots · list.hearings-in-court-sessions POST /hearings · list.hearings-in-sessions
extend multi-day POST /extendmultidayhearing/hearingslots PATCH /hearings/{id}
search-and-book (mags / crown / crown-fallback) GET query endpoints POST /hearings/{id} with JSON body · mags.search.and.book / crown.search.and.book

Unchanged (not in Phase-1 scope): get.hearing.slots, get.hearing.ids, validate-session-availability, provisionalBooking.

Response handling

  • searchBookSlots (mags) now reads sessions[0] (was a hearingSlots{} object).
  • multiDaySearchAndBook reads sessions[] (was courtSchedules[]).
  • Crown-fallback parser unchanged — matched by the courtscheduler single-day change (see dependency).
  • Other responses serialise identical domain objects, so their parsing is unchanged.

Dependency

Requires the courtscheduler Phase-1 change that makes single-day crown.search.and.book carry the booked session detail (room/time/draft/overbooked) flat on the response — on dev/SPRDT-1089-phase1-engine (courtscheduler PR #845). The integrated environment needs both this PR and that change deployed together.

Verification

  • mvn clean install — full reactor green (26 modules).
  • Integration tests via ./runIntegrationTests.sh236 passed, 0 failed, 3 skipped.
  • SonarQube quality gate — OK (new coverage 82%, new duplicated lines 0%, 0 critical/blocker violations).

Notes

  • Behaviour-preserving; the HTTP helpers were unified onto a single request executor (also removed duplication).
  • Fixes one latent bug surfaced by the new tests: extendMultiDayHearing returned a 500 (NPE) on an empty payload instead of a DataValidationException.

Update listing's courtscheduler client to the Phase-1 reshaped API:
- search-by-id: GET /courtschedule/search.court-schedules-by-id -> GET
  /sessions; query param courtScheduleIds -> ids
- release slots: DELETE /hearingslots/{id} -> DELETE /sessions/{id};
  action remove.hearing.slots -> release.sessions
- list hearings: PUT /list/hearingslots -> POST /hearings; action
  list.hearings-in-court-sessions -> list.hearings-in-sessions
- extend multiday: POST /extendmultidayhearing/hearingslots -> PATCH
  /hearings/{hearingId} (new HttpPatch helper)
- search-and-book (mags/crown/crown-fallback): GET query endpoints ->
  POST /hearings/{hearingId} with typed JSON body, actions
  mags.search.and.book / crown.search.and.book

Response parsing: mags searchAndBook now reads sessions[0] (was a
hearingSlots object); multiday reads sessions[] (was courtSchedules[]).
Crown-fallback parser unchanged - matched by the courtscheduler
single-day flat-field change. Other responses serialise identical
domain objects, so their parsing is unchanged.
- CourtSchedulerServiceStub (WireMock): retarget every stub to the new
  endpoints/actions — GET /sessions, DELETE /sessions/{id}
  (release.sessions), POST /hearings (list.hearings-in-sessions),
  PATCH /hearings/{id} (extend.multiday.hearing), POST /hearings/{id}
  (mags/crown.search.and.book). Multiday vs crown-fallback share the
  crown.search.and.book endpoint and are disambiguated by a
  durationInMinutes body matcher (>360 vs <=360). Response bodies
  reshaped: mags now {hearingId,sessions[]}, multiday courtSchedules->
  sessions, crown-fallback flat fields + empty sessions[].
- HearingSlotsServiceTest: expect new URLs/methods/headers; DELETE now
  202 ACCEPTED; HttpPatch for extend; POST bodies for search-and-book.
- CourtScheduleEnrichmentServiceTest + 3 search-book fixtures: new
  sessions[] response shape for mags/multiday.
- HearingSlotsService: guard extendMultiDayHearing against empty payload
  (was NPE on getString(hearingId) before the validation guard).

Both changed modules green (listing-common 165, listing-command-api 295).
The list.hearings-in-sessions REQUEST body is {"hearingSlots":[...]} (only
the RESPONSE uses "hearings"). The stub retarget mistakenly changed the
WireMock request-body matcher to containing("hearings"), which is not a
substring of "hearingSlots", so the real stub stopped matching and the
empty catch-all responded {"hearings":[]}. Enrichment then threw
IllegalStateException(Missing courtScheduleIds) -> 500 on every
list-court-hearing, cascading to 103 IT failures via the shared
whenCaseIsSubmittedForListing setup step. Restore the matcher to
containing("hearingSlots").
…r-call migration

S1192: add SESSION_START_TIME constant in CourtScheduleEnrichmentService (4 literal uses).
S3776: reduce postSearchBook cognitive complexity by extracting buildTypedJsonBody helper.
Duplication: unify post/patch/postSearchBook execute+response logic via executeAndBuildResponse.
Coverage: add unit tests for patch success/error/IOException, postSearchBook typed body
(durationInMinutes as number, isPolice as boolean, hearingId in path, crown content-type),
buildTypedJsonBody edge cases, and searchAndBookSlots paths (empty sessions, non-OK,
draft/isDraft fallback, judiciaries present/absent).
@ozturkmenb ozturkmenb requested a review from a team as a code owner June 25, 2026 07:59
@cpp-github-management

Copy link
Copy Markdown

Passed

Two request-body shape bugs surfaced against the real courtscheduler on
ns-ste-ccm-22 (both masked by loosely-matching WireMock IT stubs):

1. list.hearings-in-sessions sent hearingSlots[].ids instead of
   courtScheduleIds - the COURT_SCHEDULE_IDS constant was repurposed to
   "ids" for the GET /sessions query param but also built the list body
   key. Split into COURT_SCHEDULE_IDS ("courtScheduleIds", body key) and
   IDS_PARAM ("ids", query param).
2. multiDaySearchAndBook omitted courtCentreId + hearingDate, which
   crown.search.and.book requires (additionalProperties:false). Supply
   them from the hearing context at both call sites; the engine ignores
   them for the anchored path but schema validation requires them present.

Harden CourtSchedulerServiceStub to match on the required nested keys
(courtScheduleId for list; courtCentreId + hearingDate for multiday crown)
so a missing-required-key regression fails the ITs instead of silently
passing. Add unit tests for both body shapes.
@cpp-github-management

Copy link
Copy Markdown

Passed

…ale draft id on update

mergeCourtScheduleIdsFromNonDefaultDays previously skipped hearingDays that
already carried a courtScheduleId, so on a reschedule the old draft id was
left in place. The downstream fetch then returned the draft session,
isDraft=true propagated to the aggregate, canAllocateForCrown() was closed,
and hearing-rescheduled was emitted with allocated:false.

Fix: remove the early-return guard and allow the nonDefaultDays id to
overwrite the stale draft id when they differ. The new (non-draft) id is then
fetched, isDraft=false is propagated, canAllocateForCrown() opens, and
hearing-allocated-for-listing-v2 + hearing-rescheduled(allocated:true) are
both emitted correctly.

CROWN-only path (mergeCourtScheduleIdsFromNonDefaultDays is only called from
enrichCrownUpdateHearing); MAGS behaviour unchanged.
@cpp-github-management

Copy link
Copy Markdown

Failed

…day window

The J05 fix commit (205 new lines) shrank SonarQube's NUMBER_OF_DAYS=1 new-code
denominator, flipping new_maintainability_rating to D by surfacing pre-existing
smells inside the new-code period. Clear all 20 counted smells; every change is
behaviour-preserving:

CourtScheduleEnrichmentService (production):
- S3358: extract nested ternaries into locals (startTime fallback, anchor centre
  id, anchor hearing date, isDraft resolution via if/else)
- S6201: use instanceof pattern variables for JsonObject casts
- S1125: simplify the CROWN isPolice boolean expression

Tests (CourtScheduleEnrichmentServiceTest, ListingCommandApiTest, HearingAggregateTest):
- S5786: drop redundant public on JUnit5 test methods
- S6204: Stream.toList() instead of collect(Collectors.toList())
- S1117: rename locals that shadowed fields
- S1128: remove unused import

Unit tests green: CourtScheduleEnrichmentServiceTest 140, ListingCommandApiTest 33,
HearingAggregateTest 173, 0 failures.
@cpp-github-management

Copy link
Copy Markdown

Passed

… (J05)

Adds CrownUpdateHearingSingleDayIT: a CROWN, originally-unallocated single-day
hearing updated via update-hearing-for-listing with a non-draft courtScheduleId
on nonDefaultDays must end ALLOCATED (polls listing.search.hearing for
$.allocated==true and the new $.startDate).

Locks the end-to-end single-day CROWN allocation outcome (the J05 scenario) at
the integration layer. The update-hearing-for-listing schema is
additionalProperties:false and does not expose hearingDays, so the exact
stale-draft-id merge collision remains covered by the unit/aggregate tests
(CourtScheduleEnrichmentServiceTest, HearingAggregateTest); this IT locks the
e2e outcome. Stubs: stubSearchCourtSchedulesByIdSession(isDraft=false) +
stubListHearingInCourtSessionsForCourtSchedule; dates via ItClock.plusWorkingDays.

Passes in the full listing IT suite (Tests run: 1, 0 failures).
@cpp-github-management

Copy link
Copy Markdown

Passed

@cpp-github-management

Copy link
Copy Markdown

Passed

…troom from resolved final schedule

update-hearing-for-listing with only a courtScheduleId on nonDefaultDays (no room
fields) booked the session but never allocated: the handler resolved a null
command-level courtroom, called removeCourtRoom, and canAllocateForCrown() stayed
closed. Allocation only worked when the caller (UI) also supplied the room.

Fix: inverse of stripRoomInfoIfAnyDraft (ADR-005) — when the command carries no
courtroom and every enriched hearingDay resolved to a FINAL (isDraft=false)
session in one distinct room, promote that room to the command level (and backfill
a roomless selectedCourtCentre, which the handler prefers for CROWN). Draft
sessions are roomless on every courtscheduler query path so they can never
qualify; payload-supplied rooms are never overridden; multi-day spans across
different rooms stay underived.

Locked failing-first at both levels: CourtScheduleEnrichmentServiceTest (3 new
derivation tests red->green + draft/parity/mixed-room pins, 146/146) and
CrownUpdateHearingScheduleOnlyIT (schedule-only allocate + ADR-005 draft mirror,
red 90s-timeout on allocated=false -> green 2/2).
…t be resolved

When the CROWN update enrichment could not resolve or book the requested
sessions (multi-day search or single-day fetch returning empty), it logged a
WARN and returned the seeded hearingDays unchanged — carrying an unverified
courtScheduleId and a payload room. The aggregate then allocated the hearing
while courtscheduler's bookings were untouched, diverging the two services
(observed live on ns-ste-ccm-34: multi-day allocate collapsed to a 1-day
allocated hearing while all three draft bookings remained).

Fix: markDaysDraftWhenSessionsUnresolved — on either empty-result path the
enriched days are marked isDraft=true, so canAllocateForCrown() stays closed,
stripRoomInfoIfAnyDraft (ADR-005) clears day rooms downstream, and the
courtScheduleIds are preserved for traceability. Two failing-first unit tests
lock the behaviour; the two tests pinning the old return-unchanged contract
were retargeted. Full listing IT suite green (239 tests).
…k ignores the requested start date

A crown.search.and.book response whose block starts on a different date
than the command requested is no longer silently adopted: the days are
re-marked draft (same fail-safe as unresolved sessions) so the hearing
window cannot diverge from the caller's intent.
@ozturkmenb

Copy link
Copy Markdown
Author
image

@cpp-github-management

Copy link
Copy Markdown

Passed

…s block total + anchor

- validate CROWN update payload shape (CrownNonDefaultDaysValidator, 400 on violation):
  at most one virtual=true nonDefaultDay; startDate must equal the virtual day's
  date; genuine (non-virtual) days must fall within startDate..endDate
- multi-day booking uses the virtual day's duration as the block TOTAL and its
  courtScheduleId/date as the anchor sent to courtscheduler, instead of summing
  every day (a mixed proxy+genuine payload double-counted, e.g. 1440+360=1800,
  and over-booked a 5th session past the requested endDate)
- after session extraction, hearingDays whose date matches a genuine nonDefaultDay
  take that day's startTime (list-response times previously always won)
@cpp-github-management

Copy link
Copy Markdown

Passed

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.

1 participant