مخزن اختصاصی برنامهٔ وب؛ محتوای پوشهٔ web در ریشهٔ مخزن قرار گرفت. تاریخچهٔ پروژهٔ ربات (agy-bot) در این مخزن وجود ندارد و هیچ وابستگی runtime به آن نیست.
97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
"""§20 — list endpoints are paginated.
|
|
|
|
Every tenant-scoped list is unbounded in principle: an organization can create
|
|
thousands of tasks. Without a page size the API would happily serialize all of
|
|
them, so these tests pin the contract that a list is capped and that ``offset``
|
|
walks the result set.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from httpx import AsyncClient
|
|
|
|
from tests.conftest import auth_header, signup
|
|
|
|
|
|
# Fixtures resolve from conftest by name, so ``seeded`` needs no import here;
|
|
# importing it would shadow this parameter (ruff F811) and read as unused (F401).
|
|
@pytest_asyncio.fixture(autouse=True)
|
|
async def _platform_seed(seeded):
|
|
return seeded
|
|
|
|
|
|
async def test_tasks_list_is_paginated(client: AsyncClient):
|
|
token = (await signup(client, "page-tasks@example.com"))["access_token"]
|
|
headers = auth_header(token)
|
|
org_id = (
|
|
await client.post("/api/organizations", json={"name": "سازمان صفحه"}, headers=headers)
|
|
).json()["id"]
|
|
|
|
for index in range(5):
|
|
created = await client.post(
|
|
f"/api/organizations/{org_id}/tasks",
|
|
json={"title": f"کار {index}"},
|
|
headers=headers,
|
|
)
|
|
assert created.status_code == 201, created.text
|
|
|
|
page_one = await client.get(
|
|
f"/api/organizations/{org_id}/tasks", params={"limit": 2}, headers=headers
|
|
)
|
|
assert page_one.status_code == 200, page_one.text
|
|
assert len(page_one.json()) == 2, "limit must cap the page size"
|
|
|
|
page_two = await client.get(
|
|
f"/api/organizations/{org_id}/tasks",
|
|
params={"limit": 2, "offset": 2},
|
|
headers=headers,
|
|
)
|
|
assert len(page_two.json()) == 2
|
|
|
|
# Pages must not overlap: offset walks the ordering rather than repeating it.
|
|
ids_one = {t["id"] for t in page_one.json()}
|
|
ids_two = {t["id"] for t in page_two.json()}
|
|
assert not (ids_one & ids_two), "consecutive pages returned the same row"
|
|
|
|
everything = await client.get(
|
|
f"/api/organizations/{org_id}/tasks", params={"limit": 500}, headers=headers
|
|
)
|
|
assert len(everything.json()) == 5
|
|
|
|
|
|
@pytest.mark.parametrize("bad", [{"limit": 0}, {"limit": 501}, {"offset": -1}])
|
|
async def test_out_of_range_pagination_is_rejected(client: AsyncClient, bad: dict):
|
|
token = (await signup(client, f"page-bad-{abs(hash(str(bad))) % 10**6}@example.com"))[
|
|
"access_token"
|
|
]
|
|
headers = auth_header(token)
|
|
org_id = (
|
|
await client.post(
|
|
"/api/organizations", json={"name": "سازمان اعتبار"}, headers=headers
|
|
)
|
|
).json()["id"]
|
|
|
|
response = await client.get(
|
|
f"/api/organizations/{org_id}/tasks", params=bad, headers=headers
|
|
)
|
|
assert response.status_code == 422, response.text
|
|
|
|
|
|
async def test_projects_and_employees_accept_pagination(client: AsyncClient):
|
|
"""The parameter is accepted consistently across the tenant-scoped lists."""
|
|
token = (await signup(client, "page-multi@example.com"))["access_token"]
|
|
headers = auth_header(token)
|
|
org_id = (
|
|
await client.post("/api/organizations", json={"name": "سازمان چندگانه"}, headers=headers)
|
|
).json()["id"]
|
|
|
|
for path in ("projects", "employees", "threads", "tasks"):
|
|
response = await client.get(
|
|
f"/api/organizations/{org_id}/{path}",
|
|
params={"limit": 10, "offset": 0},
|
|
headers=headers,
|
|
)
|
|
assert response.status_code == 200, f"{path}: {response.text}"
|