"""TDD — fleet pagination: offset/limit, total count, first/last/prev/next state.""" from __future__ import annotations import math import pytest from datetime import datetime, timezone from sqlalchemy.ext.asyncio import AsyncSession from musehub.db.musehub_repo_models import MusehubCommit, MusehubCommitRef from tests.factories import create_repo from muse.core.types import blob_id def _utc() -> datetime: return datetime.now(tz=timezone.utc) async def _add_commit( session: AsyncSession, repo_id: str, seed: str, agent_id: str = "claude-code", model_id: str = "claude-sonnet-4-6", ) -> None: row = MusehubCommit( commit_id=blob_id(seed.encode()), branch="dev", parent_ids=[], message=f"feat: {seed}", author="gabriel", timestamp=_utc(), agent_id=agent_id, model_id=model_id, ) session.add(row) session.add(MusehubCommitRef(repo_id=repo_id, commit_id=row.commit_id)) await session.commit() _IDENTITY = { "handle": "gabriel", "type": "human", "user_id": None, } # --------------------------------------------------------------------------- # P1 — first page returns up to PAGE_SIZE agents, next_cursor set when more exist # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p1_first_page_returns_page_size_and_next_cursor( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") # Create PAGE_SIZE + 1 distinct agents for i in range(_FLEET_PAGE_SIZE + 1): await _add_commit(db_session, repo.repo_id, f"p1-c{i}", agent_id=f"agent-{i:02d}") result = await _fetch_agent_fleet(db_session, _IDENTITY) assert len(result["agents"]) == _FLEET_PAGE_SIZE assert result["next_cursor"] == _FLEET_PAGE_SIZE # --------------------------------------------------------------------------- # P2 — last page has no next_cursor # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p2_last_page_has_no_next_cursor( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") for i in range(_FLEET_PAGE_SIZE + 1): await _add_commit(db_session, repo.repo_id, f"p2-c{i}", agent_id=f"agent-{i:02d}") result = await _fetch_agent_fleet(db_session, _IDENTITY, offset=_FLEET_PAGE_SIZE) assert len(result["agents"]) == 1 assert result["next_cursor"] is None # --------------------------------------------------------------------------- # P3 — total_agents count is always the full fleet size regardless of page # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p3_total_agents_reflects_full_fleet( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") n = _FLEET_PAGE_SIZE + 3 for i in range(n): await _add_commit(db_session, repo.repo_id, f"p3-c{i}", agent_id=f"agent-{i:02d}") page1 = await _fetch_agent_fleet(db_session, _IDENTITY, offset=0) page2 = await _fetch_agent_fleet(db_session, _IDENTITY, offset=_FLEET_PAGE_SIZE) assert page1["total_agents"] == n assert page2["total_agents"] == n # --------------------------------------------------------------------------- # P4 — total_pages derived correctly from total_agents and PAGE_SIZE # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p4_total_pages_rounds_up( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") n = _FLEET_PAGE_SIZE + 1 # exactly 2 pages for i in range(n): await _add_commit(db_session, repo.repo_id, f"p4-c{i}", agent_id=f"agent-{i:02d}") result = await _fetch_agent_fleet(db_session, _IDENTITY) assert result["total_pages"] == math.ceil(n / _FLEET_PAGE_SIZE) # --------------------------------------------------------------------------- # P5 — current_page derived from offset # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p5_current_page_reflects_offset( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") for i in range(_FLEET_PAGE_SIZE * 3): await _add_commit(db_session, repo.repo_id, f"p5-c{i}", agent_id=f"agent-{i:02d}") p1 = await _fetch_agent_fleet(db_session, _IDENTITY, offset=0) p2 = await _fetch_agent_fleet(db_session, _IDENTITY, offset=_FLEET_PAGE_SIZE) p3 = await _fetch_agent_fleet(db_session, _IDENTITY, offset=_FLEET_PAGE_SIZE * 2) assert p1["current_page"] == 1 assert p2["current_page"] == 2 assert p3["current_page"] == 3 # --------------------------------------------------------------------------- # P6 — single page: no next_cursor, total_pages == 1 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_p6_single_page_fleet( db_session: AsyncSession, ) -> None: from musehub.api.routes.musehub.ui_user_profile import _fetch_agent_fleet, _FLEET_PAGE_SIZE repo = await create_repo(db_session, owner="gabriel", visibility="public") for i in range(3): await _add_commit(db_session, repo.repo_id, f"p6-c{i}", agent_id=f"agent-{i:02d}") result = await _fetch_agent_fleet(db_session, _IDENTITY) assert len(result["agents"]) == 3 assert result["next_cursor"] is None assert result["total_pages"] == 1 assert result["current_page"] == 1