"""TDD — POST /repos/{repo_id}/branches/{name}/reset Gap --- No endpoint exists to repoint a branch head to an arbitrary (earlier) commit. This is needed to undo the corrupt merge commit from bug #36 so the author can re-trigger a clean merge. Acceptance criteria ------------------- B1 POST reset with a known commit_id updates the branch head and returns the new commit_id. B2 POST reset with an unknown commit_id returns 404. B3 POST reset on an unknown branch name returns 404. B4 Endpoint requires valid MSign auth → 401 without headers. B5 POST reset on an unknown repo returns 404. """ from __future__ import annotations from datetime import datetime, timezone import pytest from httpx import AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from muse.core.types import fake_id from musehub.core.genesis import compute_branch_id from musehub.db.musehub_repo_models import MusehubBranch, MusehubCommit, MusehubCommitRef from musehub.types.json_types import StrDict # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- async def _create_repo(client: AsyncClient, auth_headers: StrDict, name: str) -> str: resp = await client.post( "/api/repos", json={"name": name, "owner": "testuser", "initialize": False}, headers=auth_headers, ) assert resp.status_code == 201, resp.text return str(resp.json()["repoId"]) async def _seed_branch( db: AsyncSession, repo_id: str, branch_name: str, *, n_commits: int = 2, ) -> list[str]: """Insert n_commits in a chain on branch_name. Returns commit_ids oldest-first.""" commit_ids: list[str] = [] parent_ids: list[str] = [] for i in range(n_commits): cid = fake_id(f"{repo_id}-{branch_name}-{i}") db.add(MusehubCommit( commit_id=cid, branch=branch_name, parent_ids=parent_ids, message=f"commit {i} on {branch_name}", author="aaronrene", timestamp=datetime.now(tz=timezone.utc), )) db.add(MusehubCommitRef(repo_id=repo_id, commit_id=cid)) commit_ids.append(cid) parent_ids = [cid] db.add(MusehubBranch( branch_id=compute_branch_id(repo_id, branch_name), repo_id=repo_id, name=branch_name, head_commit_id=commit_ids[-1], )) await db.commit() return commit_ids # --------------------------------------------------------------------------- # B1 — reset moves the branch head to the target commit # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_b1_reset_updates_branch_head( client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: """B1: POST reset with a valid commit_id updates head and returns it. RED: endpoint does not exist. GREEN: branch.head_commit_id is updated to the requested commit. """ repo_id = await _create_repo(client, auth_headers, "reset-main-repo") commit_ids = await _seed_branch(db_session, repo_id, "main", n_commits=2) old_head, new_head = commit_ids[1], commit_ids[0] # reset back one commit resp = await client.post( f"/api/repos/{repo_id}/branches/main/reset", json={"commitId": new_head}, headers=auth_headers, ) assert resp.status_code == 200, resp.text body = resp.json() assert body["branch"] == "main" assert body["commitId"] == new_head assert body["previousCommitId"] == old_head # Verify the DB row was actually updated. row = (await db_session.execute( select(MusehubBranch).where( MusehubBranch.repo_id == repo_id, MusehubBranch.name == "main", ) )).scalar_one() assert row.head_commit_id == new_head # --------------------------------------------------------------------------- # B2 — reset to an unknown commit_id returns 404 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_b2_reset_unknown_commit_returns_404( client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: """B2: POST reset with a commit_id not in the store returns 404.""" repo_id = await _create_repo(client, auth_headers, "reset-bad-commit-repo") await _seed_branch(db_session, repo_id, "main") resp = await client.post( f"/api/repos/{repo_id}/branches/main/reset", json={"commitId": fake_id("ghost-commit")}, headers=auth_headers, ) assert resp.status_code == 404, resp.text # --------------------------------------------------------------------------- # B3 — reset on an unknown branch name returns 404 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_b3_reset_unknown_branch_returns_404( client: AsyncClient, auth_headers: StrDict, db_session: AsyncSession, ) -> None: """B3: POST reset on a branch that doesn't exist returns 404.""" repo_id = await _create_repo(client, auth_headers, "reset-no-branch-repo") commit_ids = await _seed_branch(db_session, repo_id, "main") resp = await client.post( f"/api/repos/{repo_id}/branches/nonexistent/reset", json={"commitId": commit_ids[0]}, headers=auth_headers, ) assert resp.status_code == 404, resp.text # --------------------------------------------------------------------------- # B4 — auth required # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_b4_reset_requires_auth(client: AsyncClient) -> None: """B4: POST reset without auth returns 401.""" resp = await client.post( f"/api/repos/{fake_id('r')}/branches/main/reset", json={"commitId": fake_id("c")}, ) assert resp.status_code == 401 # --------------------------------------------------------------------------- # B5 — unknown repo returns 404 # --------------------------------------------------------------------------- @pytest.mark.asyncio async def test_b5_reset_unknown_repo_returns_404( client: AsyncClient, auth_headers: StrDict, ) -> None: """B5: POST reset on an unknown repo_id returns 404.""" resp = await client.post( f"/api/repos/{fake_id('ghost-repo')}/branches/main/reset", json={"commitId": fake_id("c")}, headers=auth_headers, ) assert resp.status_code == 404, resp.text