gabriel / musehub public
test_branch_reset.py python
199 lines 6.5 KB
Raw
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
1 """TDD — POST /repos/{repo_id}/branches/{name}/reset
2
3 Gap
4 ---
5 No endpoint exists to repoint a branch head to an arbitrary (earlier) commit.
6 This is needed to undo the corrupt merge commit from bug #36 so the author
7 can re-trigger a clean merge.
8
9 Acceptance criteria
10 -------------------
11 B1 POST reset with a known commit_id updates the branch head and returns
12 the new commit_id.
13 B2 POST reset with an unknown commit_id returns 404.
14 B3 POST reset on an unknown branch name returns 404.
15 B4 Endpoint requires valid MSign auth → 401 without headers.
16 B5 POST reset on an unknown repo returns 404.
17 """
18 from __future__ import annotations
19
20 from datetime import datetime, timezone
21
22 import pytest
23 from httpx import AsyncClient
24 from sqlalchemy import select
25 from sqlalchemy.ext.asyncio import AsyncSession
26
27 from muse.core.types import fake_id
28 from musehub.core.genesis import compute_branch_id
29 from musehub.db.musehub_repo_models import MusehubBranch, MusehubCommit, MusehubCommitRef
30 from musehub.types.json_types import StrDict
31
32
33 # ---------------------------------------------------------------------------
34 # Helpers
35 # ---------------------------------------------------------------------------
36
37
38 async def _create_repo(client: AsyncClient, auth_headers: StrDict, name: str) -> str:
39 resp = await client.post(
40 "/api/repos",
41 json={"name": name, "owner": "testuser", "initialize": False},
42 headers=auth_headers,
43 )
44 assert resp.status_code == 201, resp.text
45 return str(resp.json()["repoId"])
46
47
48 async def _seed_branch(
49 db: AsyncSession,
50 repo_id: str,
51 branch_name: str,
52 *,
53 n_commits: int = 2,
54 ) -> list[str]:
55 """Insert n_commits in a chain on branch_name. Returns commit_ids oldest-first."""
56 commit_ids: list[str] = []
57 parent_ids: list[str] = []
58 for i in range(n_commits):
59 cid = fake_id(f"{repo_id}-{branch_name}-{i}")
60 db.add(MusehubCommit(
61 commit_id=cid,
62 branch=branch_name,
63 parent_ids=parent_ids,
64 message=f"commit {i} on {branch_name}",
65 author="aaronrene",
66 timestamp=datetime.now(tz=timezone.utc),
67 ))
68 db.add(MusehubCommitRef(repo_id=repo_id, commit_id=cid))
69 commit_ids.append(cid)
70 parent_ids = [cid]
71 db.add(MusehubBranch(
72 branch_id=compute_branch_id(repo_id, branch_name),
73 repo_id=repo_id,
74 name=branch_name,
75 head_commit_id=commit_ids[-1],
76 ))
77 await db.commit()
78 return commit_ids
79
80
81 # ---------------------------------------------------------------------------
82 # B1 — reset moves the branch head to the target commit
83 # ---------------------------------------------------------------------------
84
85
86 @pytest.mark.asyncio
87 async def test_b1_reset_updates_branch_head(
88 client: AsyncClient,
89 auth_headers: StrDict,
90 db_session: AsyncSession,
91 ) -> None:
92 """B1: POST reset with a valid commit_id updates head and returns it.
93
94 RED: endpoint does not exist.
95 GREEN: branch.head_commit_id is updated to the requested commit.
96 """
97 repo_id = await _create_repo(client, auth_headers, "reset-main-repo")
98 commit_ids = await _seed_branch(db_session, repo_id, "main", n_commits=2)
99 old_head, new_head = commit_ids[1], commit_ids[0] # reset back one commit
100
101 resp = await client.post(
102 f"/api/repos/{repo_id}/branches/main/reset",
103 json={"commitId": new_head},
104 headers=auth_headers,
105 )
106 assert resp.status_code == 200, resp.text
107 body = resp.json()
108 assert body["branch"] == "main"
109 assert body["commitId"] == new_head
110 assert body["previousCommitId"] == old_head
111
112 # Verify the DB row was actually updated.
113 row = (await db_session.execute(
114 select(MusehubBranch).where(
115 MusehubBranch.repo_id == repo_id,
116 MusehubBranch.name == "main",
117 )
118 )).scalar_one()
119 assert row.head_commit_id == new_head
120
121
122 # ---------------------------------------------------------------------------
123 # B2 — reset to an unknown commit_id returns 404
124 # ---------------------------------------------------------------------------
125
126
127 @pytest.mark.asyncio
128 async def test_b2_reset_unknown_commit_returns_404(
129 client: AsyncClient,
130 auth_headers: StrDict,
131 db_session: AsyncSession,
132 ) -> None:
133 """B2: POST reset with a commit_id not in the store returns 404."""
134 repo_id = await _create_repo(client, auth_headers, "reset-bad-commit-repo")
135 await _seed_branch(db_session, repo_id, "main")
136
137 resp = await client.post(
138 f"/api/repos/{repo_id}/branches/main/reset",
139 json={"commitId": fake_id("ghost-commit")},
140 headers=auth_headers,
141 )
142 assert resp.status_code == 404, resp.text
143
144
145 # ---------------------------------------------------------------------------
146 # B3 — reset on an unknown branch name returns 404
147 # ---------------------------------------------------------------------------
148
149
150 @pytest.mark.asyncio
151 async def test_b3_reset_unknown_branch_returns_404(
152 client: AsyncClient,
153 auth_headers: StrDict,
154 db_session: AsyncSession,
155 ) -> None:
156 """B3: POST reset on a branch that doesn't exist returns 404."""
157 repo_id = await _create_repo(client, auth_headers, "reset-no-branch-repo")
158 commit_ids = await _seed_branch(db_session, repo_id, "main")
159
160 resp = await client.post(
161 f"/api/repos/{repo_id}/branches/nonexistent/reset",
162 json={"commitId": commit_ids[0]},
163 headers=auth_headers,
164 )
165 assert resp.status_code == 404, resp.text
166
167
168 # ---------------------------------------------------------------------------
169 # B4 — auth required
170 # ---------------------------------------------------------------------------
171
172
173 @pytest.mark.asyncio
174 async def test_b4_reset_requires_auth(client: AsyncClient) -> None:
175 """B4: POST reset without auth returns 401."""
176 resp = await client.post(
177 f"/api/repos/{fake_id('r')}/branches/main/reset",
178 json={"commitId": fake_id("c")},
179 )
180 assert resp.status_code == 401
181
182
183 # ---------------------------------------------------------------------------
184 # B5 — unknown repo returns 404
185 # ---------------------------------------------------------------------------
186
187
188 @pytest.mark.asyncio
189 async def test_b5_reset_unknown_repo_returns_404(
190 client: AsyncClient,
191 auth_headers: StrDict,
192 ) -> None:
193 """B5: POST reset on an unknown repo_id returns 404."""
194 resp = await client.post(
195 f"/api/repos/{fake_id('ghost-repo')}/branches/main/reset",
196 json={"commitId": fake_id("c")},
197 headers=auth_headers,
198 )
199 assert resp.status_code == 404, resp.text
File History 14 commits
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 8 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 11 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 14 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 29 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 33 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 34 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 35 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 35 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago