Skip to content

fix: nest role type in update_opportunity_roles payload (unbreak set-role) - #15

Merged
jschfflr merged 2 commits into
mainfrom
fix/opportunity-roles-payload
Jul 9, 2026
Merged

fix: nest role type in update_opportunity_roles payload (unbreak set-role)#15
jschfflr merged 2 commits into
mainfrom
fix/opportunity-roles-payload

Conversation

@jschfflr

@jschfflr jschfflr commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The bug

update_opportunity_roles (backing deals set-role) sent the role type flat on each entry:

{"opportunity_id": "", "roles": [{"contact_id": "", "opportunity_contact_role_type_id": "", "is_primary": true}]}

Apollo 422'd every call with undefined method 'map' for nil — its handler calls .map on each entry's role field, which was absent. The command never worked against the live API (it passed only because tests mock the client).

Root cause

Captured the app UI's own update_roles call: each entry nests the role type under a role array:

{"contact_id": "", "is_primary": true, "role": [{"opportunity_contact_role_type_id": "", "is_primary": true}]}

Fix

update_opportunity_roles now reshapes the flat RoleAssignment entries into that nested wire format (role type omitted from the nested object when the caller didn't supply one). The public RoleAssignment interface is unchanged — callers still pass flat entries.

Testing

  • Updated test_update_opportunity_roles to assert the nested body (both with- and without-role-type entries).
  • Live-verified end-to-end on a real deal: linked a contact as primary, then flipped its role type — both persisted (num_contacts=1, correct role type on GET).
  • ruff ✅ · pyright ✅ · 181 tests ✅

Note: no CLI change needed (deals set-role already sends flat entries). No version bump (left for release time).

🤖 Generated with Claude Code

The endpoint expects each role entry's type nested under a 'role' array:
{contact_id, is_primary, role: [{opportunity_contact_role_type_id, is_primary}]}.
The client sent opportunity_contact_role_type_id flat on the entry with no
'role' key, so Apollo ran .map on nil and 422'd with
"undefined method 'map' for nil" — making 'deals set-role' fail every time.

update_opportunity_roles now reshapes the flat RoleAssignment entries into
the nested wire format (role type omitted from the nested object when absent).
Public RoleAssignment interface is unchanged. Live-verified end-to-end:
linked a contact + flipped role type, both persisted.

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

@peqy peqy 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.

Fix looks correct and matches the wire format Apollo expects; test coverage was updated appropriately.

Found 1 issue (0 critical, 1 improvement).

Comment thread src/qodev_apollo_api/client.py Outdated
# into the wire format the server actually accepts.
wire_roles: list[dict] = []
for entry in roles:
is_primary = bool(entry.get("is_primary"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Improvement: is_primary = bool(entry.get("is_primary")) will treat any truthy non-bool (e.g. the string 'false') as True, and it also forces an omitted value to False.

Fix: consider is_primary = entry.get("is_primary", False) (and optionally isinstance(is_primary, bool) / raise) so you don’t accidentally flip primaries due to Python truthiness.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 Update looks good — switching to entry.get("is_primary", False) avoids the truthiness pitfall I mentioned. Feel free to mark this thread resolved.

…l() coercion

Per peqy: bool(entry.get('is_primary')) would coerce a stray truthy non-bool
(e.g. the string 'false') to True. RoleAssignment types is_primary as bool,
so default to False when omitted and pass the value through unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jschfflr
jschfflr merged commit 36f8eb7 into main Jul 9, 2026
4 checks 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