Skip to content

Commit c214844

Browse files
committed
Make query matching explicit via mock_route params
Remove ParamMatcher introspection and pass query expectations explicitly through mock_route(params=...). mock_route now maps params directly to respx params__eq route selection, while runtime matchers remain for dynamic request assertions like auth and request-id.
1 parent 784ce3a commit c214844

10 files changed

Lines changed: 28 additions & 76 deletions

tests/test_api_comments.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
data_matcher,
1414
enumerate_async,
1515
mock_route,
16-
param_matcher,
1716
request_id_matcher,
1817
)
1918
from todoist_api_python.models import Attachment
@@ -77,12 +76,10 @@ async def test_get_comments(
7776
url=endpoint,
7877
json=page,
7978
status=200,
79+
params={"task_id": task_id} | ({"cursor": cursor} if cursor else {}),
8080
matchers=[
8181
auth_matcher(),
8282
request_id_matcher(),
83-
param_matcher(
84-
{"task_id": task_id} | ({"cursor": cursor} if cursor else {})
85-
),
8683
],
8784
)
8885
cursor = page["next_cursor"]

tests/test_api_completed_tasks.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
auth_matcher,
1717
enumerate_async,
1818
mock_route,
19-
param_matcher,
2019
request_id_matcher,
2120
)
2221
from todoist_api_python._core.utils import format_datetime
@@ -59,10 +58,10 @@ async def test_get_completed_tasks_by_due_date(
5958
url=endpoint,
6059
json=page,
6160
status=200,
61+
params=params | ({"cursor": cursor} if cursor else {}),
6262
matchers=[
6363
auth_matcher(),
6464
request_id_matcher(),
65-
param_matcher(params | ({"cursor": cursor} if cursor else {})),
6665
],
6766
)
6867
cursor = page["next_cursor"]
@@ -124,10 +123,10 @@ async def test_get_completed_tasks_by_completion_date(
124123
url=endpoint,
125124
json=page,
126125
status=200,
126+
params=params | ({"cursor": cursor} if cursor else {}),
127127
matchers=[
128128
auth_matcher(),
129129
request_id_matcher(),
130-
param_matcher(params | ({"cursor": cursor} if cursor else {})),
131130
],
132131
)
133132
cursor = page["next_cursor"]

tests/test_api_labels.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
data_matcher,
1111
enumerate_async,
1212
mock_route,
13-
param_matcher,
1413
request_id_matcher,
1514
)
1615

@@ -71,10 +70,10 @@ async def test_get_labels(
7170
url=endpoint,
7271
json=page,
7372
status=200,
73+
params={"cursor": cursor} if cursor else {},
7474
matchers=[
7575
auth_matcher(),
7676
request_id_matcher(),
77-
param_matcher({"cursor": cursor} if cursor else {}),
7877
],
7978
)
8079
cursor = page["next_cursor"]
@@ -115,12 +114,10 @@ async def test_search_labels(
115114
url=endpoint,
116115
json=page,
117116
status=200,
117+
params={"query": query} | ({"cursor": cursor} if cursor else {}),
118118
matchers=[
119119
auth_matcher(),
120120
request_id_matcher(),
121-
param_matcher(
122-
{"query": query} | ({"cursor": cursor} if cursor else {})
123-
),
124121
],
125122
)
126123
cursor = page["next_cursor"]

tests/test_api_projects.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
data_matcher,
1111
enumerate_async,
1212
mock_route,
13-
param_matcher,
1413
request_id_matcher,
1514
)
1615

@@ -71,10 +70,10 @@ async def test_get_projects(
7170
url=endpoint,
7271
json=page,
7372
status=200,
73+
params={"cursor": cursor} if cursor else {},
7474
matchers=[
7575
auth_matcher(),
7676
request_id_matcher(),
77-
param_matcher({"cursor": cursor} if cursor else {}),
7877
],
7978
)
8079
cursor = page["next_cursor"]
@@ -115,12 +114,10 @@ async def test_search_projects(
115114
url=endpoint,
116115
json=page,
117116
status=200,
117+
params={"query": query} | ({"cursor": cursor} if cursor else {}),
118118
matchers=[
119119
auth_matcher(),
120120
request_id_matcher(),
121-
param_matcher(
122-
{"query": query} | ({"cursor": cursor} if cursor else {})
123-
),
124121
],
125122
)
126123
cursor = page["next_cursor"]
@@ -366,10 +363,10 @@ async def test_get_collaborators(
366363
url=endpoint,
367364
json=page,
368365
status=200,
366+
params={"cursor": cursor} if cursor else {},
369367
matchers=[
370368
auth_matcher(),
371369
request_id_matcher(),
372-
param_matcher({"cursor": cursor} if cursor else {}),
373370
],
374371
)
375372
cursor = page["next_cursor"]

tests/test_api_sections.py

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
data_matcher,
1111
enumerate_async,
1212
mock_route,
13-
param_matcher,
1413
request_id_matcher,
1514
)
1615

@@ -71,10 +70,10 @@ async def test_get_sections(
7170
url=endpoint,
7271
json=page,
7372
status=200,
73+
params={"cursor": cursor} if cursor else {},
7474
matchers=[
7575
auth_matcher(),
7676
request_id_matcher(),
77-
param_matcher({"cursor": cursor} if cursor else {}),
7877
],
7978
)
8079
cursor = page["next_cursor"]
@@ -115,12 +114,10 @@ async def test_get_sections_by_project(
115114
url=endpoint,
116115
json=page,
117116
status=200,
117+
params={"project_id": project_id} | ({"cursor": cursor} if cursor else {}),
118118
matchers=[
119119
auth_matcher(),
120120
request_id_matcher(),
121-
param_matcher(
122-
{"project_id": project_id} | ({"cursor": cursor} if cursor else {})
123-
),
124121
],
125122
)
126123
cursor = page["next_cursor"]
@@ -161,12 +158,10 @@ async def test_search_sections(
161158
url=endpoint,
162159
json=page,
163160
status=200,
161+
params={"query": query} | ({"cursor": cursor} if cursor else {}),
164162
matchers=[
165163
auth_matcher(),
166164
request_id_matcher(),
167-
param_matcher(
168-
{"query": query} | ({"cursor": cursor} if cursor else {})
169-
),
170165
],
171166
)
172167
cursor = page["next_cursor"]
@@ -208,13 +203,11 @@ async def test_search_sections_by_project(
208203
url=endpoint,
209204
json=page,
210205
status=200,
206+
params={"query": query, "project_id": project_id}
207+
| ({"cursor": cursor} if cursor else {}),
211208
matchers=[
212209
auth_matcher(),
213210
request_id_matcher(),
214-
param_matcher(
215-
{"query": query, "project_id": project_id}
216-
| ({"cursor": cursor} if cursor else {})
217-
),
218211
],
219212
)
220213
cursor = page["next_cursor"]

tests/test_api_shared_labels.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
auth_matcher,
1010
data_matcher,
1111
mock_route,
12-
param_matcher,
1312
request_id_matcher,
1413
)
1514

@@ -35,10 +34,10 @@ async def test_rename_shared_label(
3534
method="POST",
3635
url=endpoint,
3736
status=204,
37+
params={"name": name},
3838
matchers=[
3939
auth_matcher(),
4040
request_id_matcher(),
41-
param_matcher({"name": name}),
4241
data_matcher({"new_name": new_name}),
4342
],
4443
)

tests/test_api_tasks.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
data_matcher,
1818
enumerate_async,
1919
mock_route,
20-
param_matcher,
2120
request_id_matcher,
2221
)
2322

@@ -77,10 +76,10 @@ async def test_get_tasks(
7776
url=endpoint,
7877
json=page,
7978
status=200,
79+
params={"cursor": cursor} if cursor else {},
8080
matchers=[
8181
auth_matcher(),
8282
request_id_matcher(),
83-
param_matcher({"cursor": cursor} if cursor else {}),
8483
],
8584
)
8685
cursor = page["next_cursor"]
@@ -136,10 +135,10 @@ async def test_get_tasks_with_filters(
136135
url=endpoint,
137136
json=page,
138137
status=200,
138+
params=params | ({"cursor": cursor} if cursor else {}),
139139
matchers=[
140140
auth_matcher(),
141141
request_id_matcher(),
142-
param_matcher(params | ({"cursor": cursor} if cursor else {})),
143142
],
144143
)
145144
cursor = page["next_cursor"]
@@ -200,10 +199,10 @@ async def test_filter_tasks(
200199
url=endpoint,
201200
json=page,
202201
status=200,
202+
params=params | ({"cursor": cursor} if cursor else {}),
203203
matchers=[
204204
auth_matcher(),
205205
request_id_matcher(),
206-
param_matcher(params | ({"cursor": cursor} if cursor else {})),
207206
],
208207
)
209208
cursor = page["next_cursor"]

tests/test_authentication.py

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import pytest
77

88
from tests.data.test_defaults import DEFAULT_OAUTH_URL
9-
from tests.utils.test_utils import data_matcher, mock_route, param_matcher
9+
from tests.utils.test_utils import data_matcher, mock_route
1010
from todoist_api_python._core.endpoints import API_URL # Use new base URL
1111
from todoist_api_python.authentication import (
1212
get_auth_token,
@@ -85,15 +85,11 @@ async def test_revoke_auth_token(
8585
"DELETE",
8686
f"{API_URL}/access_tokens",
8787
status=200,
88-
matchers=[
89-
param_matcher(
90-
{
91-
"client_id": client_id,
92-
"client_secret": client_secret,
93-
"access_token": token,
94-
}
95-
)
96-
],
88+
params={
89+
"client_id": client_id,
90+
"client_secret": client_secret,
91+
"access_token": token,
92+
},
9793
)
9894

9995
result = revoke_auth_token(client_id, client_secret, token)

tests/test_http_requests.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
auth_matcher,
1111
data_matcher,
1212
mock_route,
13-
param_matcher,
1413
request_id_matcher,
1514
)
1615
from todoist_api_python._core.http_requests import delete, get, post
@@ -31,10 +30,10 @@ def test_get_with_params(respx_mock: respx.MockRouter) -> None:
3130
url=EXAMPLE_URL,
3231
json=EXAMPLE_RESPONSE,
3332
status=200,
33+
params=EXAMPLE_PARAMS,
3434
matchers=[
3535
auth_matcher(),
3636
request_id_matcher(DEFAULT_REQUEST_ID),
37-
param_matcher(EXAMPLE_PARAMS),
3837
],
3938
)
4039

@@ -152,10 +151,10 @@ def test_delete_with_params(respx_mock: respx.MockRouter) -> None:
152151
method="DELETE",
153152
url=EXAMPLE_URL,
154153
status=204,
154+
params=EXAMPLE_PARAMS,
155155
matchers=[
156156
auth_matcher(),
157157
request_id_matcher(DEFAULT_REQUEST_ID),
158-
param_matcher(EXAMPLE_PARAMS),
159158
],
160159
)
161160

tests/utils/test_utils.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,6 @@ def matcher(request: httpx.Request) -> None:
4242
return matcher
4343

4444

45-
class ParamMatcher:
46-
"""Callable query-parameter assertion matcher for request mocks."""
47-
48-
def __init__(self, params: dict[str, Any]) -> None:
49-
"""Normalize and store expected query params."""
50-
self.expected = _normalize_params(params) or {}
51-
52-
def __call__(self, request: httpx.Request) -> None:
53-
actual = _normalize_params(dict(request.url.params.multi_items())) or {}
54-
assert actual == self.expected
55-
56-
57-
def param_matcher(params: dict[str, Any]) -> Callable[[httpx.Request], None]:
58-
return ParamMatcher(params)
59-
60-
6145
def data_matcher(data: dict[str, Any]) -> Callable[[httpx.Request], None]:
6246
def matcher(request: httpx.Request) -> None:
6347
assert request.content
@@ -74,20 +58,12 @@ def mock_route(
7458
*,
7559
status: int = 200,
7660
json: JSONValue | object = _UNSET,
61+
params: dict[str, Any] | None = None,
7762
matchers: list[Callable[[httpx.Request], None]] | None = None,
7863
) -> None:
79-
"""Register a route with optional runtime request assertions.
80-
81-
Query params can be asserted via `param_matcher(...)` in the `matchers` list.
82-
When present, they are also used for deterministic route selection.
83-
"""
64+
"""Register a route with optional route params and request assertions."""
8465
runtime_matchers = matchers or []
85-
86-
route_params: dict[str, str] | None = None
87-
for matcher in runtime_matchers:
88-
if isinstance(matcher, ParamMatcher):
89-
route_params = matcher.expected
90-
break
66+
route_params = _normalize_params(params)
9167

9268
def handler(request: httpx.Request) -> httpx.Response:
9369
for matcher in runtime_matchers:

0 commit comments

Comments
 (0)