Skip to content
Merged
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
14 changes: 10 additions & 4 deletions campus_python/api/v1/circles.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
Campus API circles resource (v1).
"""

from typing import Any

import campus.model

from ...interface import Resource, ResourceCollection
Expand All @@ -22,21 +24,25 @@ def list(self) -> "list[campus.model.Circle]":
resp.raise_for_status()
return [
campus.model.Circle.from_resource(item)
for item in resp.json()["circles"]
for item in resp.json()["data"]
]

def new(
self,
*,
name: str,
description: str,
tag: str,
description: str = "",
parents: dict[str, int] | None = None,
) -> campus.model.Circle:
resp = self.client.post(self.make_path(), json={
payload: dict[str, Any] = {
"name": name,
"tag": tag,
"description": description,
})
}
if parents is not None:
payload["parents"] = parents
resp = self.client.post(self.make_path(), json=payload)
resp.raise_for_status()
return campus.model.Circle.from_resource(resp.json())

Expand Down
95 changes: 95 additions & 0 deletions tests/unit/test_circles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Unit tests for the circles collection client (issue #55).

Circles.list() must unwrap the ``{"data": [...]}`` envelope the API
sends (it looked up a ``"circles"`` key the API never sends), and
Circles.new() must carry ``tag``/``parents`` in the POST payload
instead of silently dropping them. The expected shapes mirror the
campus-suite routes on branch ``weekly``.
"""

import unittest
from unittest.mock import Mock

import campus.model

from campus_python.api.v1 import ApiRoot


def make_api() -> tuple[ApiRoot, Mock]:
"""Create an ApiRoot backed by a mock JSON client."""
client = Mock()
return ApiRoot(json_client=client), client


CIRCLE_RESOURCE = {
"id": "circle_ab12cd34",
"name": "Design Team",
"description": "Handles UI/UX",
"tag": "project",
"members": {},
"sources": {},
}


class TestCirclesList(unittest.TestCase):
"""Circles.list() must unwrap the API's data envelope."""

def setUp(self):
self.api, self.client = make_api()

def test_list_unwraps_data_envelope(self):
self.client.get.return_value.json.return_value = {
"data": [dict(CIRCLE_RESOURCE)]
}
circles = self.api.circles.list()
self.client.get.assert_called_once_with("/api/v1/circles/")
self.assertEqual(len(circles), 1)
self.assertIsInstance(circles[0], campus.model.Circle)
self.assertEqual(circles[0].name, "Design Team")

def test_list_returns_empty_list_for_empty_envelope(self):
self.client.get.return_value.json.return_value = {"data": []}
self.assertEqual(self.api.circles.list(), [])


class TestCirclesNew(unittest.TestCase):
"""Circles.new() must send tag/parents instead of dropping them."""

def setUp(self):
self.api, self.client = make_api()

def test_new_sends_all_fields(self):
self.client.post.return_value.json.return_value = dict(CIRCLE_RESOURCE)
circle = self.api.circles.new(
name="Design Team",
tag="project",
description="Handles UI/UX",
parents={"circle_parent01": 15},
)
self.client.post.assert_called_once_with(
"/api/v1/circles/",
json={
"name": "Design Team",
"tag": "project",
"description": "Handles UI/UX",
"parents": {"circle_parent01": 15},
},
)
self.assertIsInstance(circle, campus.model.Circle)
self.assertEqual(circle.id, "circle_ab12cd34")

def test_new_defaults_description_and_omits_parents(self):
self.client.post.return_value.json.return_value = dict(CIRCLE_RESOURCE)
self.api.circles.new(name="Design Team", tag="project")
self.client.post.assert_called_once_with(
"/api/v1/circles/",
json={
"name": "Design Team",
"tag": "project",
"description": "",
},
)


if __name__ == "__main__":
unittest.main()
Loading