Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions plane/models/work_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ class WorkItemDetail(BaseModel):
model_config = ConfigDict(extra="allow", populate_by_name=True)

id: str | None = None
assignees: list[UserLite] = Field(default_factory=list)
labels: list[Label] = Field(default_factory=list)
# The API returns bare UUID strings for these relations unless the request
# expands them (``expand=assignees,labels``), so accept both shapes — the
# same tolerance ``state`` (``str | StateLite``) and ``WorkItemExpand`` use.
assignees: list[str] | list[UserLite] = Field(default_factory=list)
labels: list[str] | list[Label] = Field(default_factory=list)
type_id: str | None = None
created_at: str | None = None
updated_at: str | None = None
Expand Down
48 changes: 48 additions & 0 deletions tests/unit/test_work_item_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Pure model-validation tests for work item response models (no HTTP).

The Plane API returns ``assignees`` and ``labels`` on a work item as bare UUID
strings unless the request expands them (``expand=assignees,labels``), in which
case they come back as nested objects. ``WorkItemDetail`` must accept both
shapes — the same tolerance it already grants ``state`` (``str | StateLite``).
"""

from plane.models.labels import Label
from plane.models.users import UserLite
from plane.models.work_items import WorkItemDetail


class TestWorkItemDetailRelations:
def test_accepts_unexpanded_id_lists(self) -> None:
"""Default (unexpanded) responses carry bare UUID strings."""
item = WorkItemDetail.model_validate(
{
"id": "wi-1",
"assignees": ["00000000-0000-0000-0000-000000000001"],
"labels": ["00000000-0000-0000-0000-000000000002"],
"state": "00000000-0000-0000-0000-000000000003",
}
)
assert item.assignees == ["00000000-0000-0000-0000-000000000001"]
assert item.labels == ["00000000-0000-0000-0000-000000000002"]
assert item.state == "00000000-0000-0000-0000-000000000003"

def test_accepts_expanded_object_lists(self) -> None:
"""``expand=assignees,labels`` responses carry nested objects."""
item = WorkItemDetail.model_validate(
{
"id": "wi-1",
"assignees": [{"id": "u-1", "display_name": "henry"}],
"labels": [{"id": "l-1", "name": "bug"}],
"state": {"id": "s-1", "name": "Todo"},
}
)
assert isinstance(item.assignees[0], UserLite)
assert item.assignees[0].id == "u-1"
assert isinstance(item.labels[0], Label)
assert item.labels[0].name == "bug"

def test_defaults_to_empty_lists(self) -> None:
"""Absent relations default to empty lists, not None."""
item = WorkItemDetail.model_validate({"id": "wi-1"})
assert item.assignees == []
assert item.labels == []