-
Notifications
You must be signed in to change notification settings - Fork 16
fix: DeckClient.update_card partial update bugs #453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Tests document current behavior of update_card method: - Updating without title fails (400) - title required but conditionally sent - Updating with title clears description - PUT is full replacement Related: #452 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
PR Review: DeckClient.update_card Integration TestsThank you for this comprehensive PR documenting the bugs in 🎯 StrengthsExcellent Documentation
High-Quality Test Suite
Strong Investigation
🐛 Issues & Recommendations1. Critical: Tests Document Bugs But Don't Verify FixesProblem: This PR adds tests that verify current buggy behavior, but doesn't include the fix itself. The tests will pass in their current form, which doesn't help prevent regression. Recommendation:
Example: @pytest.mark.xfail(reason="Bug #452: description cleared instead of preserved", strict=True)
async def test_update_title_only_clears_description(self, nc_client, deck_test_card):
# ...
assert updated.description == "Original description" # This should pass after fix2. Test Assertion IssueIn # BUG: Description was cleared instead of preserved
assert updated.description == "" # Should be "Original description"Problem: This assertion verifies the bug, not the desired behavior. When the bug is fixed, this test will fail. Recommendation: Either:
3. Missing Test Case: Update Description with TitleWhile you test "title only" and "title + description", consider adding:
async def test_update_description_with_title(self, nc_client, deck_test_card):
"""Updating description when title is also provided should work."""
await nc_client.deck.update_card(
board_id=deck_test_card["board_id"],
stack_id=deck_test_card["stack_id"],
card_id=deck_test_card["card_id"],
title="Original Title", # Explicitly preserve
description="New description",
)
updated = await nc_client.deck.get_card(...)
assert updated.title == "Original Title"
assert updated.description == "New description"4. Proposed Fix ApproachBased on your investigation and the current implementation in Current behavior (lines 292-293): if title is not None:
json_data["title"] = titleFixed behavior: # Title is required by Deck API, always include it
json_data["title"] = title if title is not None else current_card.title
# Description should be preserved when not explicitly provided
json_data["description"] = description if description is not None else (
current_card.description or ""
)Rationale:
5. Code QualityType Hints: Following CLAUDE.md conventions:
Async Best Practices: ✅ All async patterns follow repository conventions (anyio compatible) 6. Performance Consideration
🔒 SecurityNo security concerns identified. Standard CRUD operations with proper authentication handled by 📊 Test CoverageCurrent: Tests cover all parameter combinations (title, description, duedate, archived, order, type, owner) Suggested additions:
📝 Next StepsOption A: Keep as Documentation PR (Current State)
Option B: Convert to Full Fix PR (Recommended)
✅ Approval RecommendationConditional approval pending:
The investigation quality and test coverage are excellent. Once the test assertion strategy is clarified, this will be a solid contribution! 🎉 References
Great work documenting this API quirk! The Deck API's undocumented full-replacement behavior is now well-tested. |
Refactor tests to assert what SHOULD happen (partial updates preserve unchanged fields) rather than documenting current buggy behavior. Tests will fail until fix is implemented in client or upstream. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Code Review: DeckClient.update_card Integration TestsThank you for this PR! The integration tests are well-structured and clearly document the expected behavior. Here's my detailed feedback: ✅ Strengths
🔍 Issues Identified1. Critical Bug in Current ImplementationLooking at Bug #1: Lines 292-293 conditionally include if title is not None:
json_data["title"] = titleBut the Deck API requires Bug #2: Lines 294-295 conditionally include if description is not None:
json_data["description"] = descriptionSince PUT is a full replacement, omitting The current implementation already fetches 2. Inconsistency with Issue #452Issue #452 claims that json_data["type"] = type if type is not None else current_card.type
json_data["owner"] = ... # Complex logic to extract ownerQuestion: Does the Deck API actually reject 3. Missing
|
| Aspect | Rating | Notes |
|---|---|---|
| Test Quality | ⭐⭐⭐⭐⭐ | Comprehensive, well-documented |
| Test Coverage | ⭐⭐⭐⭐ | Covers main scenarios, could add edge cases |
| Follows Conventions | ⭐⭐⭐⭐⭐ | Matches CLAUDE.md patterns perfectly |
| Implementation Fix | ⏳ Pending | Needs implementation to match tests |
| API Verification | ⏳ Required | Must confirm API requirements |
Overall: Excellent test design that clearly documents the bug and expected behavior. Once API requirements are verified and the implementation is fixed, this will be a valuable contribution! 🚀
The Deck PUT API is a full replacement, not a partial update. Previously, title and description were conditionally sent, causing: - 400 errors when title not provided (it's required) - Description being cleared when not explicitly set Now all required fields (title, type, owner) and description are always included in the payload using current card values when not explicitly provided. This matches the existing pattern for type/owner. Also simplified owner extraction since DeckCard.validate_owner already ensures it's always a string. Fixes #452 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
Code Review for PR #453Thank you for this comprehensive fix to the ✅ Strengths1. Excellent Test-Driven ApproachThe PR follows TDD principles beautifully:
This progression is pedagogically valuable and makes the problem/solution crystal clear. 2. Comprehensive Test CoverageThe test suite in
3. Code Quality ImprovementsThe implementation fix is clean and well-documented: # Before: Complex conditional logic with nested ternaries
json_data["owner"] = (
owner if owner is not None
else (current_card.owner if isinstance(current_card.owner, str)
else current_card.owner.uid if hasattr(current_card.owner, "uid")
else current_card.owner.primaryKey)
)
# After: Simple, leveraging existing model validator
json_data = {
"owner": owner if owner is not None else current_card.owner,
# ...
}The simplified owner handling is possible because 4. Proper Understanding of PUT SemanticsThe fix correctly recognizes that the Deck API's PUT endpoint is a full replacement, not a partial update. The solution (fetch-then-merge) is the standard pattern for implementing partial updates over PUT APIs. 🔍 Observations & Suggestions1. Performance Consideration: Extra GET RequestCurrent behavior: Every # Line 290
current_card = await self.get_card(board_id, stack_id, card_id)Trade-off analysis:
Alternative approaches (for consideration in future work):
Recommendation: Current approach is acceptable given:
Consider documenting this trade-off in the method docstring. 2. Potential Bug: Empty Description HandlingLine 303: "description": description if description is not None else (current_card.description or ""),Question: Should Current behavior:
Test gap: No test for "clear description" scenario. Suggestion: Consider whether callers should be able to clear the description: # Option 1: Allow explicit empty string to clear
await update_card(description="") # Clears to empty
# Option 2: Use sentinel value for clarity
from typing import Literal
CLEAR = object()
await update_card(description=CLEAR) # Explicitly clearsIf current behavior is intentional (no way to clear description), document it. Otherwise, add test case: async def test_update_clears_description(nc_client, deck_test_card):
"""Passing empty string should clear the description."""
await nc_client.deck.update_card(
...,
description="", # Explicitly clear
)
updated = await nc_client.deck.get_card(...)
assert updated.description == ""3. Type Hints: Missing Return Type AnnotationThe async def update_card(
self,
board_id: int,
stack_id: int,
card_id: int,
# ... parameters
) -> None: # ✅ Already correctActually, reviewing again - this is already correct! Good job following CLAUDE.md conventions. 4. Test Fixture: Consider ParameterizationMany tests follow the same pattern (update field, verify preservation). Consider using @pytest.mark.parametrize("field,value,getter", [
("title", "New Title", lambda c: c.title),
("description", "New Desc", lambda c: c.description),
("order", 99, lambda c: c.order),
("archived", True, lambda c: c.archived),
])
async def test_update_single_field_preserves_others(
nc_client, deck_test_card, field, value, getter
):
await nc_client.deck.update_card(
deck_test_card["board_id"],
deck_test_card["stack_id"],
deck_test_card["card_id"],
**{field: value}
)
updated = await nc_client.deck.get_card(...)
assert getter(updated) == value
assert updated.title == "Original Title" # etc.However, the current verbose approach is also valid for:
Your call on this one! 🔒 Security Review✅ No security concerns identified:
📋 Checklist Against CLAUDE.md
🧪 Pre-Merge ChecklistBefore merging, please verify:
🎯 RecommendationAPPROVE with minor suggestions This is high-quality work that:
Suggested follow-up (not blocking):
Great work on this fix! The TDD approach and thorough testing are exemplary. 🎉 |
Summary
Fixes partial update bugs in
DeckClient.update_cardwhere:titlecaused 400 errorsdescriptioncleared itFixes: #452
Root Cause
The Deck PUT API is a full replacement, not a partial update. Fields omitted from the payload are either rejected (required fields) or cleared (optional fields).
Solution
Always include
title,type,owner, anddescriptionin the payload, using current card values when not explicitly provided. This matches the existing pattern that was already used fortypeandowner.Bonus: Simplified owner extraction logic since
DeckCard.validate_owneralready ensures it's always a string.Test Results
All 8 integration tests pass:
This PR was generated with the help of AI, and reviewed by a Human