Skip to content

Commit ff9a3f5

Browse files
feat: Added list method functionality to API
1 parent 3613a87 commit ff9a3f5

3 files changed

Lines changed: 183 additions & 0 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,3 +254,6 @@ Thumbs.db
254254

255255
# Playwright auth state (contains session cookies/tokens)
256256
.playwright_auth_state.json
257+
258+
# test resources
259+
testing/

campus_python/api/v1/timetable.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Campus API timetable resource (v1).
44
"""
55

6+
import typing
7+
68
import campus.model
79

810
from ...interface import Resource, ResourceCollection
@@ -114,6 +116,22 @@ def new(self, metadata: dict, data: dict) -> dict:
114116
resp.raise_for_status()
115117
return resp.json()
116118

119+
def list(self, **filters: typing.Any) -> "list[campus.model.TimetableMetadata]":
120+
"""List timetables matching the provided filters.
121+
122+
Args:
123+
**filters: Arbitrary filter parameters applied to the query.
124+
125+
Returns:
126+
list[campus.model.TimetableMetadata]: Matching timetable metadata objects.
127+
"""
128+
resp = self.client.get(self.make_path(), query=filters if filters else None)
129+
resp.raise_for_status()
130+
return [
131+
campus.model.TimetableMetadata.from_resource(item)
132+
for item in resp.json()
133+
]
134+
117135
class Timetable(Resource):
118136
"""A single timetable with start date."""
119137

tests/unit/test_timetable_list.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import unittest
2+
from unittest.mock import Mock, MagicMock
3+
4+
from campus_python.api.v1.timetable import Timetables
5+
from campus_python.interface import ResourceRoot
6+
import campus.model
7+
8+
9+
class TestTimetablesList(unittest.TestCase):
10+
"""Test Timetables.list() method."""
11+
12+
def setUp(self):
13+
"""Set up test fixtures."""
14+
# Create mock client
15+
self.mock_client = Mock()
16+
self.mock_client.base_url = "https://api.example.com"
17+
18+
# Create resource root
19+
self.root = ResourceRoot(self.mock_client)
20+
self.root.url_prefix = "api/v1"
21+
22+
# Create Timetables instance
23+
self.timetables = Timetables(self.mock_client, root=self.root)
24+
25+
def test_list_without_filters(self):
26+
"""Test list() returns timetables without filters."""
27+
# Mock the response - backend returns direct list
28+
mock_response = Mock()
29+
mock_response.json.return_value = [
30+
{
31+
"id": "tt-123",
32+
"filename": "schedule.xlsx",
33+
"start_date": "2026-01-01T00:00:00Z",
34+
"end_date": "2026-06-30T00:00:00Z",
35+
"created_at": "2026-01-01T00:00:00Z"
36+
},
37+
{
38+
"id": "tt-456",
39+
"filename": "other.xlsx",
40+
"start_date": "2026-07-01T00:00:00Z",
41+
"end_date": "2026-12-31T00:00:00Z",
42+
"created_at": "2026-07-01T00:00:00Z"
43+
}
44+
]
45+
mock_response.raise_for_status = Mock()
46+
47+
self.mock_client.get.return_value = mock_response
48+
49+
# Call list()
50+
result = self.timetables.list()
51+
52+
# Verify the client was called correctly
53+
self.mock_client.get.assert_called_once_with(
54+
"/api/v1/timetable/",
55+
query=None
56+
)
57+
58+
# Verify results
59+
self.assertEqual(len(result), 2)
60+
self.assertEqual(result[0].id, "tt-123")
61+
self.assertEqual(result[0].filename, "schedule.xlsx")
62+
self.assertEqual(result[1].id, "tt-456")
63+
self.assertEqual(result[1].filename, "other.xlsx")
64+
65+
def test_list_with_filters(self):
66+
"""Test list() with filter parameters."""
67+
# Mock the response - backend returns direct list
68+
mock_response = Mock()
69+
mock_response.json.return_value = [
70+
{
71+
"id": "tt-123",
72+
"filename": "schedule.xlsx",
73+
"start_date": "2026-01-01T00:00:00Z",
74+
"end_date": "2026-06-30T00:00:00Z",
75+
"created_at": "2026-01-01T00:00:00Z"
76+
}
77+
]
78+
mock_response.raise_for_status = Mock()
79+
80+
self.mock_client.get.return_value = mock_response
81+
82+
# Call list() with filters
83+
result = self.timetables.list(filename="schedule.xlsx")
84+
85+
# Verify the client was called with correct filters
86+
self.mock_client.get.assert_called_once_with(
87+
"/api/v1/timetable/",
88+
query={"filename": "schedule.xlsx"}
89+
)
90+
91+
# Verify results
92+
self.assertEqual(len(result), 1)
93+
self.assertEqual(result[0].filename, "schedule.xlsx")
94+
95+
def test_list_empty_result(self):
96+
"""Test list() returns empty list when no timetables found."""
97+
# Mock the response - backend returns direct list
98+
mock_response = Mock()
99+
mock_response.json.return_value = []
100+
mock_response.raise_for_status = Mock()
101+
102+
self.mock_client.get.return_value = mock_response
103+
104+
# Call list()
105+
result = self.timetables.list()
106+
107+
# Verify results
108+
self.assertEqual(len(result), 0)
109+
110+
def test_list_with_multiple_filters(self):
111+
"""Test list() with multiple filter parameters."""
112+
# Mock the response - backend returns direct list
113+
mock_response = Mock()
114+
mock_response.json.return_value = [
115+
{
116+
"id": "tt-789",
117+
"filename": "test.xlsx",
118+
"start_date": "2026-01-01T00:00:00Z",
119+
"end_date": "2026-06-30T00:00:00Z",
120+
"created_at": "2026-01-01T00:00:00Z"
121+
}
122+
]
123+
mock_response.raise_for_status = Mock()
124+
125+
self.mock_client.get.return_value = mock_response
126+
127+
# Call list() with multiple filters
128+
result = self.timetables.list(
129+
filename="test.xlsx",
130+
start_date="2026-01-01T00:00:00Z"
131+
)
132+
133+
# Verify the client was called with correct filters
134+
self.mock_client.get.assert_called_once_with(
135+
"/api/v1/timetable/",
136+
query={
137+
"filename": "test.xlsx",
138+
"start_date": "2026-01-01T00:00:00Z"
139+
}
140+
)
141+
142+
# Verify results
143+
self.assertEqual(len(result), 1)
144+
self.assertEqual(result[0].filename, "test.xlsx")
145+
146+
def test_list_propagates_client_errors(self):
147+
"""Test list() propagates client errors."""
148+
# Mock response with raise_for_status that raises exception
149+
mock_response = Mock()
150+
mock_response.raise_for_status.side_effect = Exception("Client error")
151+
152+
self.mock_client.get.return_value = mock_response
153+
154+
# Call list() should raise exception
155+
with self.assertRaises(Exception) as context:
156+
self.timetables.list()
157+
158+
self.assertEqual(str(context.exception), "Client error")
159+
160+
161+
if __name__ == "__main__":
162+
unittest.main()

0 commit comments

Comments
 (0)