feat: add pagination metadata to gitea_list_prs (#340)
Return wrapped {prs, pagination} from gitea_list_prs with has_more,
inventory_complete, and page traversal via api_fetch_page. Route
gitea_review_pr inventory through gitea_list_prs so trust gates can
prove pagination finality. Add assess_list_prs_pagination_proof verifier.
Closes #340
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
"""Tests for gitea_list_prs pagination metadata (#340)."""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
||||
|
||||
import gitea_auth # noqa: E402
|
||||
from mcp_server import gitea_list_prs # noqa: E402
|
||||
from review_proofs import assess_list_prs_pagination_proof # noqa: E402
|
||||
|
||||
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
||||
URL = "https://gitea.example.com/api/v1/repos/o/r/pulls?state=open"
|
||||
|
||||
SAMPLE_PR = {
|
||||
"number": 1,
|
||||
"title": "PR 1",
|
||||
"state": "open",
|
||||
"head": {"ref": "branch1"},
|
||||
"base": {"ref": "master"},
|
||||
"mergeable": True,
|
||||
"updated_at": "2026-07-05T12:00:00Z",
|
||||
}
|
||||
|
||||
|
||||
class TestApiFetchPage(unittest.TestCase):
|
||||
@patch("gitea_auth.api_request")
|
||||
def test_final_page_metadata(self, mock_req):
|
||||
mock_req.return_value = [SAMPLE_PR]
|
||||
items, meta = gitea_auth.api_fetch_page(URL, FAKE_AUTH, page=1, limit=50)
|
||||
self.assertEqual(len(items), 1)
|
||||
self.assertFalse(meta["has_more"])
|
||||
self.assertTrue(meta["is_final_page"])
|
||||
self.assertIsNone(meta["next_page"])
|
||||
|
||||
@patch("gitea_auth.api_request")
|
||||
def test_full_page_has_more(self, mock_req):
|
||||
mock_req.return_value = [{"id": i} for i in range(2)]
|
||||
items, meta = gitea_auth.api_fetch_page(URL, FAKE_AUTH, page=1, limit=2)
|
||||
self.assertEqual(len(items), 2)
|
||||
self.assertTrue(meta["has_more"])
|
||||
self.assertFalse(meta["is_final_page"])
|
||||
self.assertEqual(meta["next_page"], 2)
|
||||
|
||||
|
||||
class TestGiteaListPrsPagination(unittest.TestCase):
|
||||
@patch("mcp_server.api_fetch_page")
|
||||
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_explicit_page_returns_has_more(self, _auth, mock_fetch):
|
||||
mock_fetch.return_value = (
|
||||
[SAMPLE_PR],
|
||||
{
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"returned_count": 1,
|
||||
"has_more": False,
|
||||
"next_page": None,
|
||||
"is_final_page": True,
|
||||
},
|
||||
)
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
result = gitea_list_prs(page=1, per_page=50)
|
||||
self.assertEqual(len(result["prs"]), 1)
|
||||
self.assertFalse(result["pagination"]["inventory_complete"])
|
||||
self.assertTrue(result["pagination"]["is_final_page"])
|
||||
|
||||
@patch("mcp_server.api_fetch_page")
|
||||
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
|
||||
def test_multi_page_fetch_all_marks_inventory_complete(self, _auth, mock_fetch):
|
||||
mock_fetch.side_effect = [
|
||||
(
|
||||
[dict(SAMPLE_PR, number=i) for i in (1, 2)],
|
||||
{
|
||||
"page": 1,
|
||||
"per_page": 2,
|
||||
"returned_count": 2,
|
||||
"has_more": True,
|
||||
"next_page": 2,
|
||||
"is_final_page": False,
|
||||
},
|
||||
),
|
||||
(
|
||||
[dict(SAMPLE_PR, number=3)],
|
||||
{
|
||||
"page": 2,
|
||||
"per_page": 2,
|
||||
"returned_count": 1,
|
||||
"has_more": False,
|
||||
"next_page": None,
|
||||
"is_final_page": True,
|
||||
},
|
||||
),
|
||||
]
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
result = gitea_list_prs(per_page=2)
|
||||
self.assertEqual([p["number"] for p in result["prs"]], [1, 2, 3])
|
||||
self.assertTrue(result["pagination"]["inventory_complete"])
|
||||
self.assertEqual(result["pagination"]["pages_fetched"], 2)
|
||||
self.assertEqual(result["pagination"]["total_count"], 3)
|
||||
|
||||
|
||||
class TestListPrsPaginationProof(unittest.TestCase):
|
||||
def test_wrapped_final_page_passes_completeness_claim(self):
|
||||
response = {
|
||||
"prs": [],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"returned_count": 0,
|
||||
"has_more": False,
|
||||
"next_page": None,
|
||||
"is_final_page": True,
|
||||
"inventory_complete": True,
|
||||
"pages_fetched": 1,
|
||||
"total_count": 0,
|
||||
},
|
||||
}
|
||||
result = assess_list_prs_pagination_proof(
|
||||
response, inventory_complete_claimed=True
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
|
||||
def test_bare_list_blocks_completeness_claim(self):
|
||||
result = assess_list_prs_pagination_proof(
|
||||
[{"number": 1}], inventory_complete_claimed=True
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn("pagination metadata", " ".join(result["reasons"]))
|
||||
|
||||
def test_single_page_without_complete_blocks_claim(self):
|
||||
response = {
|
||||
"prs": [{"number": 1}],
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"per_page": 50,
|
||||
"returned_count": 1,
|
||||
"has_more": True,
|
||||
"next_page": 2,
|
||||
"is_final_page": False,
|
||||
"inventory_complete": False,
|
||||
"pages_fetched": 1,
|
||||
"total_count": None,
|
||||
},
|
||||
}
|
||||
result = assess_list_prs_pagination_proof(
|
||||
response, inventory_complete_claimed=True
|
||||
)
|
||||
self.assertFalse(result["proven"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user