Skip to content

Commit 7505005

Browse files
committed
test: PaginatedResponse tests
1 parent cc7ae6c commit 7505005

1 file changed

Lines changed: 315 additions & 0 deletions

File tree

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# SPDX-FileCopyrightText: 2025-present deepset GmbH <info@deepset.ai>
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from typing import Any
6+
from unittest.mock import AsyncMock
7+
8+
import pytest
9+
10+
from deepset_mcp.api.shared_models import PaginatedResponse
11+
12+
13+
class MockItem:
14+
"""Mock item for testing PaginatedResponse."""
15+
16+
def __init__(self, item_id: str, name: str):
17+
self.item_id = item_id
18+
self.name = name
19+
20+
def dict(self) -> dict[str, str]:
21+
return {"item_id": self.item_id, "name": self.name}
22+
23+
24+
class TestPaginatedResponse:
25+
"""Test suite for PaginatedResponse model."""
26+
27+
def test_create_with_cursor_field_success(self) -> None:
28+
"""Test successful creation of PaginatedResponse with cursor field."""
29+
data = {
30+
"data": [
31+
{"item_id": "item1", "name": "First Item"},
32+
{"item_id": "item2", "name": "Second Item"},
33+
],
34+
"has_more": True,
35+
"total": 10,
36+
}
37+
38+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
39+
40+
assert response.data == data["data"]
41+
assert response.has_more is True
42+
assert response.total == 10
43+
assert response.next_cursor == "item2"
44+
45+
def test_create_with_cursor_field_no_more_pages(self) -> None:
46+
"""Test creation when has_more is False - should not set cursor."""
47+
data = {
48+
"data": [
49+
{"item_id": "item1", "name": "First Item"},
50+
],
51+
"has_more": False,
52+
"total": 1,
53+
}
54+
55+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
56+
57+
assert response.data == data["data"]
58+
assert response.has_more is False
59+
assert response.total == 1
60+
assert response.next_cursor is None
61+
62+
def test_create_with_cursor_field_empty_data(self) -> None:
63+
"""Test creation with empty data list."""
64+
data = {
65+
"data": [],
66+
"has_more": False,
67+
"total": 0,
68+
}
69+
70+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
71+
72+
assert response.data == []
73+
assert response.has_more is False
74+
assert response.total == 0
75+
assert response.next_cursor is None
76+
77+
def test_create_with_cursor_field_missing_cursor_field(self) -> None:
78+
"""Test creation when cursor field is missing from last item."""
79+
data = {
80+
"data": [
81+
{"item_id": "item1", "name": "First Item"},
82+
{"name": "Second Item"}, # Missing item_id
83+
],
84+
"has_more": True,
85+
"total": 10,
86+
}
87+
88+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
89+
90+
assert response.data == data["data"]
91+
assert response.has_more is True
92+
assert response.next_cursor is None # Should be None when cursor field missing
93+
94+
def test_create_without_cursor_field_raises_error(self) -> None:
95+
"""Test that creating PaginatedResponse without cursor field raises ValueError."""
96+
data = {
97+
"data": [
98+
{"item_id": "item1", "name": "First Item"},
99+
{"item_id": "item2", "name": "Second Item"},
100+
],
101+
"has_more": True,
102+
"total": 10,
103+
}
104+
105+
with pytest.raises(ValueError, match="Cursor field must be specified when creating PaginatedResponse"):
106+
PaginatedResponse.model_validate(data)
107+
108+
def test_create_without_cursor_field_no_more_pages_succeeds(self) -> None:
109+
"""Test that creating without cursor field succeeds when has_more is False."""
110+
data = {
111+
"data": [
112+
{"item_id": "item1", "name": "First Item"},
113+
],
114+
"has_more": False,
115+
"total": 1,
116+
}
117+
118+
# Should not raise error since has_more is False
119+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.model_validate(data)
120+
assert response.has_more is False
121+
assert response.next_cursor is None
122+
123+
def test_create_with_different_cursor_fields(self) -> None:
124+
"""Test creation with different cursor field names."""
125+
# Test with pipeline_id
126+
pipeline_data = {
127+
"data": [
128+
{"pipeline_id": "pipeline1", "name": "First Pipeline"},
129+
{"pipeline_id": "pipeline2", "name": "Second Pipeline"},
130+
],
131+
"has_more": True,
132+
}
133+
134+
pipeline_response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(
135+
pipeline_data, "pipeline_id"
136+
)
137+
assert pipeline_response.next_cursor == "pipeline2"
138+
139+
# Test with user_id
140+
user_data = {
141+
"data": [
142+
{"user_id": "user1", "email": "user1@example.com"},
143+
{"user_id": "user2", "email": "user2@example.com"},
144+
],
145+
"has_more": True,
146+
}
147+
148+
user_response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(
149+
user_data, "user_id"
150+
)
151+
assert user_response.next_cursor == "user2"
152+
153+
def test_inject_paginator(self) -> None:
154+
"""Test injecting paginator functionality."""
155+
data = {
156+
"data": [{"item_id": "item1", "name": "First Item"}],
157+
"has_more": False,
158+
}
159+
160+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
161+
162+
# Mock fetch function and base args
163+
fetch_func = AsyncMock()
164+
base_args = {"limit": 10}
165+
166+
response._inject_paginator(fetch_func, base_args)
167+
168+
assert response._fetch_func == fetch_func
169+
assert response._base_args == base_args
170+
171+
@pytest.mark.asyncio
172+
async def test_get_next_page_no_more(self) -> None:
173+
"""Test getting next page when has_more is False."""
174+
data = {
175+
"data": [{"item_id": "item1", "name": "First Item"}],
176+
"has_more": False,
177+
}
178+
179+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
180+
181+
# Inject paginator
182+
fetch_func = AsyncMock()
183+
response._inject_paginator(fetch_func, {"limit": 10})
184+
185+
next_page = await response._get_next_page()
186+
assert next_page is None
187+
fetch_func.assert_not_called()
188+
189+
@pytest.mark.asyncio
190+
async def test_get_next_page_no_cursor(self) -> None:
191+
"""Test getting next page when next_cursor is None."""
192+
data = {
193+
"data": [{"item_id": "item1", "name": "First Item"}],
194+
"has_more": True,
195+
}
196+
197+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
198+
# Override the next_cursor to None to test the case where it's missing
199+
response.next_cursor = None
200+
201+
# Inject paginator
202+
fetch_func = AsyncMock()
203+
response._inject_paginator(fetch_func, {"limit": 10})
204+
205+
next_page = await response._get_next_page()
206+
assert next_page is None
207+
fetch_func.assert_not_called()
208+
209+
@pytest.mark.asyncio
210+
async def test_get_next_page_success(self) -> None:
211+
"""Test successful retrieval of next page."""
212+
data = {
213+
"data": [{"item_id": "item1", "name": "First Item"}],
214+
"has_more": True,
215+
"next_cursor": "item1", # This should be the cursor value from the data
216+
}
217+
218+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
219+
220+
# Mock next page data
221+
next_page_data = {
222+
"data": [{"item_id": "item2", "name": "Second Item"}],
223+
"has_more": False,
224+
}
225+
next_page_mock: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(
226+
next_page_data, "item_id"
227+
)
228+
229+
# Mock fetch function
230+
fetch_func = AsyncMock(return_value=next_page_mock)
231+
base_args = {"limit": 10}
232+
233+
response._inject_paginator(fetch_func, base_args)
234+
235+
next_page = await response._get_next_page()
236+
237+
# Verify fetch was called with correct args
238+
fetch_func.assert_called_once_with(limit=10, before="item1")
239+
240+
# Verify returned page
241+
assert next_page == next_page_mock
242+
assert next_page._fetch_func == fetch_func
243+
assert next_page._base_args == base_args
244+
245+
@pytest.mark.asyncio
246+
async def test_get_next_page_not_initialized(self) -> None:
247+
"""Test getting next page when paginator is not initialized."""
248+
data = {
249+
"data": [{"item_id": "item1", "name": "First Item"}],
250+
"has_more": True,
251+
"next_cursor": "cursor1",
252+
}
253+
254+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
255+
256+
with pytest.raises(TypeError, match="Paginator has not been initialized"):
257+
await response._get_next_page()
258+
259+
@pytest.mark.asyncio
260+
async def test_items_iteration_single_page(self) -> None:
261+
"""Test iterating over items in a single page."""
262+
data = {
263+
"data": [
264+
{"item_id": "item1", "name": "First Item"},
265+
{"item_id": "item2", "name": "Second Item"},
266+
],
267+
"has_more": False,
268+
}
269+
270+
response: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(data, "item_id")
271+
response._inject_paginator(AsyncMock(), {"limit": 10})
272+
273+
items = []
274+
async for item in response.items():
275+
items.append(item)
276+
277+
assert len(items) == 2
278+
assert items[0]["item_id"] == "item1"
279+
assert items[1]["item_id"] == "item2"
280+
281+
@pytest.mark.asyncio
282+
async def test_items_iteration_multiple_pages(self) -> None:
283+
"""Test iterating over items across multiple pages."""
284+
# First page
285+
first_page_data = {
286+
"data": [{"item_id": "item1", "name": "First Item"}],
287+
"has_more": True,
288+
"next_cursor": "cursor1",
289+
}
290+
291+
# Second page
292+
second_page_data = {
293+
"data": [{"item_id": "item2", "name": "Second Item"}],
294+
"has_more": False,
295+
}
296+
297+
first_page: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(
298+
first_page_data, "item_id"
299+
)
300+
second_page: PaginatedResponse[dict[str, Any]] = PaginatedResponse.create_with_cursor_field(
301+
second_page_data, "item_id"
302+
)
303+
304+
# Mock fetch function to return second page
305+
fetch_func = AsyncMock(return_value=second_page)
306+
first_page._inject_paginator(fetch_func, {"limit": 10})
307+
308+
items = []
309+
async for item in first_page.items():
310+
items.append(item)
311+
312+
assert len(items) == 2
313+
assert items[0]["item_id"] == "item1"
314+
assert items[1]["item_id"] == "item2"
315+
fetch_func.assert_called_once()

0 commit comments

Comments
 (0)