Skip to content

fix(roles): PUT /v1/roles/{roleId} no longer returns roleChildren duplicated 4x (#37303) - #37344

Open
hassandotcms wants to merge 3 commits into
mainfrom
37303-roles-put-duplicate-children
Open

fix(roles): PUT /v1/roles/{roleId} no longer returns roleChildren duplicated 4x (#37303)#37344
hassandotcms wants to merge 3 commits into
mainfrom
37303-roles-put-duplicate-children

Conversation

@hassandotcms

Copy link
Copy Markdown
Contributor

Fixes #37303

Problem

PUT /api/v1/roles/{roleId} returned the updated role with its roleChildrenlist duplicated four times and childCount inflated to match — a role with one child came back reporting four. Only the response body was wrong; the persisted rows were always correct (an immediate GET returned the real children). Enough to break a client: the Roles (Beta) portlet splices the PUT response into its tree, and any integration reading childCount to decide whether to recurse got the wrong answer. The reparent DBFQN cascade, seeded from the same list, also re-saved descendants redundantly.

Root cause

RoleFactoryImpl.populatChildrenForRolesHelper appended to a role's existing roleChildren instead of replacing it. A Role instance that arrived already populated therefore accumulated. Within one save() the same instance passes through the population path four times (arrives populated from cache → the DBFQN cascade loop → setFQNForDB's getRoleById reload → end of save()), producing exactly four copies. toRoleViews then serialized whatever ids were in the list, and RoleView.childCount is getRoleChildren().size().

Fix

Give populateChildrenForRoles replace semantics: reset each input role's roleChildren before the chunked queries, so population is idempotent. The reset is in the method (not the helper, which must keep accumulating across the 200-role chunks of a single population). This corrects the PUT response, removes the redundant cascade re-saves, and stops the shared cache-resident instance from being polluted mid-transaction. Also renames the private populatChildrenForRoles* method family to populateChildrenForRoles* (all call sites are in-file).

No REST contract change — response shape is untouched, only the values are corrected. No DB schema, ES mapping, or serialized-format change; rollback-safe.

Testing

  • Integration (new): PUT reparent / plain field update / move-to-root each return one child with childCount == 1 matching a subsequent GET (RoleResourceIntegrationTest); factory-level idempotency, and a childless role
    keeps roleChildren == null (RoleAPITest); reparent keeps children
    de-duplicated at every depth of a 3-level subtree.
  • Suites: RoleResourceIntegrationTest, RoleAPITest, RoleResourceCountsIntegrationTest, RoleResourceUsersIntegrationTest — all green (80 tests, 0 failures).
  • Manual (curl): reparent PUT returns childCount: 1 / [child] (was 4), matching GET before and after; plain update and move-to-root both childCount: 1.

… PUT response (#37303)

Defect spec for PUT /api/v1/roles/{roleId} returning each child of the
updated role four times, with childCount inflated to match. Covers the
curl reproduction, the verified root cause (populatChildrenForRolesHelper
appends instead of replacing, four population passes on one Role instance
per save), fix scope with explicit non-goals, regression risk, and
measurable acceptance criteria.

Spec-only commit — PR 1 of 2 per the Spec-Kit flow; implementation
follows on this branch after spec approval.
)

populateChildrenForRoles appended to a role's existing roleChildren instead
of replacing it, so an instance that passed through the population path more
than once per save() accumulated its children. PUT ran one instance through
four times, returning each child 4x with childCount inflated to match; the
DBFQN reparent cascade, seeded from the same list, also re-saved descendants
redundantly. Persisted rows were always correct — only the response and the
cascade work were wrong.

Reset each input role's roleChildren before the chunked queries so population
is idempotent (replace, not accumulate). Reset in the method, not the helper,
which must keep accumulating across 200-role chunks. Also renames the private
populatChildrenForRoles* family to populateChildrenForRoles* (all call sites
in-file).

Adds integration coverage: PUT reparent / plain update / move-to-root each
return one child with childCount 1; factory-level idempotency (childless role
stays null); and reparent keeps children de-duplicated at every depth.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

PUT /v1/roles/{roleId} returns the role's children duplicated 4x, with childCount inflated to match

1 participant