[ENG-401] fix: token queue should respect sub queues while calling serve next - #3708
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughToken and sub-queue scheduling now enforce stricter assignment and status rules. A new sub-queue endpoint advances CREATED tokens atomically, while queue selection excludes already-assigned tokens. Update tests now use CREATED. ChangesToken queue consistency
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR fixes sub-queue token calling by routing
Confidence Score: 3/5The update endpoint now incorrectly rejects field edits on already-IN_PROGRESS tokens when the payload includes the current sub_queue and status, and the new set_next/set_next_token endpoints read their key objects outside the lock without re-fetching inside, leaving concurrent overwrites of current_token possible. The validate_data guard introduced in this PR blocks any update to an already-IN_PROGRESS token that includes both sub_queue and status=IN_PROGRESS in the payload — a regression that will affect clients sending full object representations. Additionally, both set_next (token.py) and set_next_token (token_sub_queue.py) snapshot their objects before acquiring the advisory lock, so an existing current_token on the sub_queue can be silently overwritten. care/emr/api/viewsets/scheduling/token.py (validate_data guard, set_next snapshot) and care/emr/api/viewsets/scheduling/token_sub_queue.py (set_next_token snapshot and missing current_token guard) Important Files Changed
Reviews (9): Last reviewed commit: "Merge branch 'ENG-401-token-queue-should..." | Re-trigger Greptile |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3708 +/- ##
===========================================
- Coverage 79.79% 79.73% -0.07%
===========================================
Files 479 479
Lines 23116 23140 +24
Branches 2409 2412 +3
===========================================
+ Hits 18446 18451 +5
- Misses 4066 4082 +16
- Partials 604 607 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
care/emr/tests/test_token_api.py (1)
278-292: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winMissing test coverage for new sub-queue validation and clearing behavior.
The status change from
IN_PROGRESStoCREATEDis correct — the newperform_updatelogic (lines 113-120 intoken.py) would block assigning a sub_queue while transitioning toIN_PROGRESS. However, the new behavior itself is entirely untested:
- Sub_queue change during IN_PROGRESS is blocked — no test verifies that updating a token to
IN_PROGRESSwhile changing its sub_queue returns 400 with "Use set_next endpoint to change the sub queue of a token".- Current_token conflict on IN_PROGRESS with same sub_queue — no test verifies that setting
IN_PROGRESSwith an existing sub_queue that has a differentcurrent_tokenreturns 400 with "Sub Queue already has a current token".- Current_token clearing on status transition away from IN_PROGRESS — no test verifies that updating a token from
IN_PROGRESStoFULFILLED/CANCELLEDclears the sub_queue'scurrent_token.- Next-token prioritization (
token_queue.pylines 235-238) — no test verifies thatset_next_token_to_subqueueprefers tokens already assigned to the requested sub_queue over unassigned waiting tokens.The existing tests (
test_update_token_with_existing_current_token_in_subqueue,test_update_token_with_current_token_as_null_in_subqueue) cover the firstvalidate_datablock but none exercise theIN_PROGRESS-gated paths.Would you like me to generate the missing test cases?
Also applies to: 314-328
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/tests/test_token_api.py` around lines 278 - 292, The token API tests lack coverage for the new sub-queue validation and current-token lifecycle behavior. Add focused tests around the existing update-token test cases and `perform_update` covering: rejecting an IN_PROGRESS update that changes sub_queue with the specified 400 message; rejecting IN_PROGRESS when the sub_queue has a different current_token; clearing current_token when transitioning from IN_PROGRESS to FULFILLED or CANCELLED; and verifying `set_next_token_to_subqueue` prioritizes tokens already assigned to the requested sub_queue over unassigned waiting tokens.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@care/emr/tests/test_token_api.py`:
- Around line 278-292: The token API tests lack coverage for the new sub-queue
validation and current-token lifecycle behavior. Add focused tests around the
existing update-token test cases and `perform_update` covering: rejecting an
IN_PROGRESS update that changes sub_queue with the specified 400 message;
rejecting IN_PROGRESS when the sub_queue has a different current_token; clearing
current_token when transitioning from IN_PROGRESS to FULFILLED or CANCELLED; and
verifying `set_next_token_to_subqueue` prioritizes tokens already assigned to
the requested sub_queue over unassigned waiting tokens.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 4955bd10-01bf-440e-9009-38445ca4c38a
📒 Files selected for processing (3)
care/emr/api/viewsets/scheduling/token.pycare/emr/api/viewsets/scheduling/token_queue.pycare/emr/tests/test_token_api.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
care/emr/api/viewsets/scheduling/token.py (2)
107-115: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winAdd the resource guard here too. This branch only catches sub-queue swaps, so a token with no existing sub-queue can still attach one from the wrong resource. Match the create path and reject
instance.sub_queue.resource != obj.queue.resourcebefore the current transition check.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 107 - 115, Update the validation logic containing the existing sub-queue transition check to also guard the target queue’s resource: reject when obj.sub_queue exists and obj.sub_queue.resource differs from obj.queue.resource, before evaluating the current instance transition condition. Keep the existing mismatch check for sub-queue swaps so both creation-like attachments and transitions enforce resource consistency.
201-213: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winLock the sub-queue row in
set_next.transaction.atomic()doesn’t serialize concurrent calls here, so two requests can both pass thecurrent_tokencheck and one will quietly overwrite the other. Useselect_for_update()onTokenSubQueuelike the create path does.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 201 - 213, Update set_next to retrieve the TokenSubQueue through select_for_update() before checking or assigning current_token, matching the locking approach used by the create path; keep the lookup inside the existing transaction.atomic() block so concurrent calls serialize safely.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@care/emr/api/viewsets/scheduling/token.py`:
- Around line 107-115: Update the validation logic containing the existing
sub-queue transition check to also guard the target queue’s resource: reject
when obj.sub_queue exists and obj.sub_queue.resource differs from
obj.queue.resource, before evaluating the current instance transition condition.
Keep the existing mismatch check for sub-queue swaps so both creation-like
attachments and transitions enforce resource consistency.
- Around line 201-213: Update set_next to retrieve the TokenSubQueue through
select_for_update() before checking or assigning current_token, matching the
locking approach used by the create path; keep the lookup inside the existing
transaction.atomic() block so concurrent calls serialize safely.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: cedfac83-4a0d-49cd-8098-4162ab0c84d8
📒 Files selected for processing (1)
care/emr/api/viewsets/scheduling/token.py
d82319b to
5ada8a3
Compare
5ada8a3 to
b21fa4a
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
care/emr/api/viewsets/scheduling/token.py (3)
199-200: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winError message doesn't match the actual condition.
The check rejects any non-CREATED status (FULFILLED, CANCELLED, ENTERED_IN_ERROR), but the message says "Token in serving state cannot be set next," implying only IN_PROGRESS is rejected.
✏️ Proposed fix
- raise ValidationError("Token in serving state cannot be set next") + raise ValidationError("Token must be in CREATED state to set next")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 199 - 200, Update the validation in the token status transition logic to use an error message that accurately describes rejection of every status other than CREATED, rather than referring only to the serving state. Keep the existing condition and ValidationError behavior unchanged.
196-214: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winMissing lock in
set_nextallows concurrent races onsub_queue.current_token.The sibling endpoints
token_queue.py:set_next_token_to_subqueueandtoken_sub_queue.py:set_next_tokenboth wrap their current-token updates inLock(...), butset_nextonly usestransaction.atomic(). Two concurrent calls setting different tokens as next for the same sub-queue will both succeed, with the lastsub_queue.save()winning — the losing token is silently transitioned toIN_PROGRESSwithout being the current_token. The status check on line 199 is also outside any lock, creating a TOCTOU window.🔒 Proposed fix: add lock and move status check inside it
`@action`(detail=True, methods=["POST"]) def set_next(self, request, *args, **kwargs): obj = self.get_object() - if obj.status != TokenStatusOptions.CREATED.value: - raise ValidationError("Token in serving state cannot be set next") request_obj = SetCurrentTokenRequest(**request.data) queue = obj.queue self.authorize_update(None, obj) - with transaction.atomic(): + sub_queue = get_object_or_404( + TokenSubQueue, + external_id=request_obj.sub_queue, + resource=queue.resource, + ) + with Lock(f"sub_queue:current_token:{sub_queue.id}"), transaction.atomic(): + obj.refresh_from_db() + if obj.status != TokenStatusOptions.CREATED.value: + raise ValidationError("Token must be in CREATED state to set next") sub_queue = get_object_or_404( TokenSubQueue, external_id=request_obj.sub_queue, resource=queue.resource, ) sub_queue.current_token = obj - sub_queue.save() + sub_queue.save(update_fields=["current_token", "modified_date"]) obj.status = TokenStatusOptions.IN_PROGRESS.value obj.sub_queue = sub_queue - obj.save() + obj.save(update_fields=["status", "sub_queue", "modified_date"]) return Response(self.get_retrieve_pydantic_model().serialize(obj).to_json())🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 196 - 214, Update set_next to wrap the transaction in the same Lock(...) mechanism used by token_queue.py:set_next_token_to_subqueue and token_sub_queue.py:set_next_token, locking the target sub_queue before modifying it. Move the obj.status validation inside the lock/transaction so the check and current_token update are atomic, preventing concurrent requests from transitioning a losing token to IN_PROGRESS.
84-93: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winCompare sub-queue objects consistently
instance.sub_queueis aTokenSubQueueobject here, so comparing it tomodel_obj.sub_queue.external_idwill always trip the "Sub Queue already has a current token" guard on unchanged updates. Compare againstmodel_obj.sub_queue(orinstance.sub_queue.external_id) instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 84 - 93, Update the sub-queue comparison in the token validation logic to compare like-for-like values: use model_obj.sub_queue against the TokenSubQueue object in instance.sub_queue, or compare both external IDs. Preserve the existing existing_current check and ValidationError behavior for genuinely different sub-queues.
🧹 Nitpick comments (2)
care/emr/api/viewsets/scheduling/token_queue.py (1)
240-244: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPrefer
save(update_fields=[...])over baresave().Same pattern as the other set-next endpoints —
sub_queue.save()andnext_token.save()write all fields. See consolidated comment.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token_queue.py` around lines 240 - 244, Update the set-next flow around sub_queue.current_token and next_token to use save(update_fields=[...]) for both sub_queue and next_token, listing only the fields modified immediately before each save. Preserve the existing assignment and status/sub-queue updates.care/emr/api/viewsets/scheduling/token.py (1)
210-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winBare
save()in all three set-next endpoints should useupdate_fields. The shared root cause is that each set-next endpoint callssave()without restricting fields, whileperform_updateintoken.pyalready demonstrates thesave(update_fields=[...])pattern. This risks overwriting concurrent modifications to unrelated fields.
care/emr/api/viewsets/scheduling/token.py#L210-L214:sub_queue.save(update_fields=["current_token", "modified_date"])andobj.save(update_fields=["status", "sub_queue", "modified_date"])care/emr/api/viewsets/scheduling/token_sub_queue.py#L144-L148:obj.save(update_fields=["current_token", "modified_date"])andnext_token.save(update_fields=["status", "sub_queue", "modified_date"])care/emr/api/viewsets/scheduling/token_queue.py#L240-L244:sub_queue.save(update_fields=["current_token", "modified_date"])andnext_token.save(update_fields=["status", "sub_queue", "modified_date"])🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@care/emr/api/viewsets/scheduling/token.py` around lines 210 - 214, Replace the unrestricted saves in the set-next endpoints with targeted update_fields saves: in care/emr/api/viewsets/scheduling/token.py lines 210-214, update sub_queue.save to persist current_token and modified_date, and obj.save to persist status, sub_queue, and modified_date; in care/emr/api/viewsets/scheduling/token_sub_queue.py lines 144-148, update obj.save to persist current_token and modified_date, and next_token.save to persist status, sub_queue, and modified_date. Also apply the same targeted fields in the set-next flow of care/emr/api/viewsets/scheduling/token_queue.py lines 240-244 for sub_queue and next_token.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@care/emr/api/viewsets/scheduling/token.py`:
- Around line 199-200: Update the validation in the token status transition
logic to use an error message that accurately describes rejection of every
status other than CREATED, rather than referring only to the serving state. Keep
the existing condition and ValidationError behavior unchanged.
- Around line 196-214: Update set_next to wrap the transaction in the same
Lock(...) mechanism used by token_queue.py:set_next_token_to_subqueue and
token_sub_queue.py:set_next_token, locking the target sub_queue before modifying
it. Move the obj.status validation inside the lock/transaction so the check and
current_token update are atomic, preventing concurrent requests from
transitioning a losing token to IN_PROGRESS.
- Around line 84-93: Update the sub-queue comparison in the token validation
logic to compare like-for-like values: use model_obj.sub_queue against the
TokenSubQueue object in instance.sub_queue, or compare both external IDs.
Preserve the existing existing_current check and ValidationError behavior for
genuinely different sub-queues.
---
Nitpick comments:
In `@care/emr/api/viewsets/scheduling/token_queue.py`:
- Around line 240-244: Update the set-next flow around sub_queue.current_token
and next_token to use save(update_fields=[...]) for both sub_queue and
next_token, listing only the fields modified immediately before each save.
Preserve the existing assignment and status/sub-queue updates.
In `@care/emr/api/viewsets/scheduling/token.py`:
- Around line 210-214: Replace the unrestricted saves in the set-next endpoints
with targeted update_fields saves: in care/emr/api/viewsets/scheduling/token.py
lines 210-214, update sub_queue.save to persist current_token and modified_date,
and obj.save to persist status, sub_queue, and modified_date; in
care/emr/api/viewsets/scheduling/token_sub_queue.py lines 144-148, update
obj.save to persist current_token and modified_date, and next_token.save to
persist status, sub_queue, and modified_date. Also apply the same targeted
fields in the set-next flow of care/emr/api/viewsets/scheduling/token_queue.py
lines 240-244 for sub_queue and next_token.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 070bc2bb-0051-4f16-9416-4db03d8d3794
📒 Files selected for processing (4)
care/emr/api/viewsets/scheduling/token.pycare/emr/api/viewsets/scheduling/token_queue.pycare/emr/api/viewsets/scheduling/token_sub_queue.pycare/emr/tests/test_token_api.py
🚧 Files skipped from review as they are similar to previous changes (1)
- care/emr/tests/test_token_api.py
Proposed Changes
Associated Issue
Merge Checklist
/docsOnly PR's with test cases included and passing lint and test pipelines will be reviewed
@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins
Summary by CodeRabbit
New Features
set-nextendpoint for sub-queues to atomically pick the earliest available token, set it as the current token, and transition it into progress.Bug Fixes
IN_PROGRESS.IN_PROGRESS.Tests
CREATEDpayloads and expectCREATEDresponses.