gabriel / musehub public

test_backfill_marketplace_domain_links.py file-level

at sha256:f · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:e fix(9A-4 F7): env alias + staging secrets for overseer provenance gate.… · aaronrene · Jul 13, 2026
1 """Tests for scripts/backfill_marketplace_domain_links.py — musehub#120
2 Phase 3 (MDL_11-14).
3
4 Covers the testable core (`backfill_marketplace_domain_links(session,
5 apply=...)`), not the CLI/`main()` wrapper — the wrapper is thin plumbing
6 (argparse + init_db + print) with no branching logic worth a DB-backed
7 test.
8 """
9 from __future__ import annotations
10
11 import secrets
12 from datetime import datetime, timezone
13
14 import pytest
15 from sqlalchemy.ext.asyncio import AsyncSession
16
17 from musehub.core.genesis import compute_identity_id, compute_repo_id
18 from musehub.db.musehub_domain_models import MusehubDomain
19 from musehub.db.musehub_repo_models import MusehubRepo
20 from musehub.services.musehub_domains import compute_manifest_hash
21 from scripts.backfill_marketplace_domain_links import backfill_marketplace_domain_links
22
23
24 async def _db_domain(
25 session: AsyncSession,
26 *,
27 author_slug: str = "alice",
28 slug: str,
29 is_deprecated: bool = False,
30 ) -> MusehubDomain:
31 caps = {"dimensions": [], "merge_semantics": "three_way"}
32 domain = MusehubDomain(
33 domain_id=secrets.token_hex(16),
34 author_user_id=author_slug,
35 author_slug=author_slug,
36 slug=slug,
37 display_name=slug,
38 description="Test domain",
39 version="1.0.0",
40 manifest_hash=compute_manifest_hash(caps),
41 capabilities=caps,
42 viewer_type="generic",
43 install_count=0,
44 is_verified=False,
45 is_deprecated=is_deprecated,
46 created_at=datetime.now(timezone.utc),
47 updated_at=datetime.now(timezone.utc),
48 )
49 session.add(domain)
50 await session.flush()
51 return domain
52
53
54 async def _db_repo(
55 session: AsyncSession,
56 *,
57 owner: str = "bob",
58 domain_id: str,
59 marketplace_domain_id: str | None = None,
60 ) -> MusehubRepo:
61 owner_id = compute_identity_id(owner.encode())
62 created_at = datetime.now(tz=timezone.utc)
63 slug = f"repo-{secrets.token_hex(4)}"
64 repo = MusehubRepo(
65 repo_id=compute_repo_id(owner_id, slug, domain_id, created_at.isoformat()),
66 name=slug,
67 slug=slug,
68 owner=owner,
69 owner_user_id=owner_id,
70 visibility="public",
71 domain_id=domain_id,
72 marketplace_domain_id=marketplace_domain_id,
73 created_at=created_at,
74 updated_at=created_at,
75 )
76 session.add(repo)
77 await session.flush()
78 return repo
79
80
81 class TestBackfillDryRun:
82 @pytest.mark.asyncio
83 async def test_mdl11_dry_run_reports_without_writing(self, db_session: AsyncSession) -> None:
84 d = await _db_domain(db_session, slug="code")
85 repo = await _db_repo(db_session, domain_id="code")
86
87 report = await backfill_marketplace_domain_links(db_session, apply=False)
88
89 assert repo.repo_id in report.linked
90 assert report.skipped_ambiguous == []
91 assert report.skipped_no_match == []
92
93 # Dry-run must not mutate the ORM object at all.
94 refreshed = await db_session.get(MusehubRepo, repo.repo_id)
95 assert refreshed is not None
96 assert refreshed.marketplace_domain_id is None
97
98 d_refreshed = await db_session.get(MusehubDomain, d.domain_id)
99 assert d_refreshed is not None
100 assert d_refreshed.install_count == 0
101
102
103 class TestBackfillApply:
104 @pytest.mark.asyncio
105 async def test_mdl12_apply_writes_exactly_what_dry_run_reported(
106 self, db_session: AsyncSession
107 ) -> None:
108 d = await _db_domain(db_session, slug="mist")
109 repo = await _db_repo(db_session, domain_id="mist")
110
111 dry_report = await backfill_marketplace_domain_links(db_session, apply=False)
112 assert repo.repo_id in dry_report.linked
113
114 apply_report = await backfill_marketplace_domain_links(db_session, apply=True)
115 await db_session.commit()
116
117 assert apply_report.linked == dry_report.linked
118
119 refreshed = await db_session.get(MusehubRepo, repo.repo_id)
120 assert refreshed is not None
121 assert refreshed.marketplace_domain_id == d.domain_id
122
123 d_refreshed = await db_session.get(MusehubDomain, d.domain_id)
124 assert d_refreshed is not None
125 assert d_refreshed.install_count == 1
126
127
128 class TestBackfillIdempotent:
129 @pytest.mark.asyncio
130 async def test_mdl13_already_linked_repos_are_untouched(
131 self, db_session: AsyncSession
132 ) -> None:
133 d = await _db_domain(db_session, slug="identity")
134 already_linked = await _db_repo(
135 db_session, domain_id="identity", marketplace_domain_id=d.domain_id
136 )
137
138 report = await backfill_marketplace_domain_links(db_session, apply=True)
139 await db_session.commit()
140
141 assert already_linked.repo_id not in report.linked
142 assert already_linked.repo_id not in report.skipped_ambiguous
143 assert already_linked.repo_id not in report.skipped_no_match
144
145 # install_count untouched — this repo was never "newly" linked.
146 d_refreshed = await db_session.get(MusehubDomain, d.domain_id)
147 assert d_refreshed is not None
148 assert d_refreshed.install_count == 0
149
150 @pytest.mark.asyncio
151 async def test_mdl13_safe_to_rerun_after_apply(self, db_session: AsyncSession) -> None:
152 d = await _db_domain(db_session, slug="code")
153 repo = await _db_repo(db_session, domain_id="code")
154
155 await backfill_marketplace_domain_links(db_session, apply=True)
156 await db_session.commit()
157
158 second_report = await backfill_marketplace_domain_links(db_session, apply=True)
159 await db_session.commit()
160
161 assert repo.repo_id not in second_report.linked
162 assert repo.repo_id not in second_report.skipped_ambiguous
163 assert repo.repo_id not in second_report.skipped_no_match
164
165 d_refreshed = await db_session.get(MusehubDomain, d.domain_id)
166 assert d_refreshed is not None
167 assert d_refreshed.install_count == 1 # not double-counted
168
169
170 class TestBackfillReportsEverySkip:
171 @pytest.mark.asyncio
172 async def test_mdl14_ambiguous_repo_reported_by_repo_id(
173 self, db_session: AsyncSession
174 ) -> None:
175 await _db_domain(db_session, author_slug="alice", slug="code")
176 await _db_domain(db_session, author_slug="gabriel", slug="code")
177 repo = await _db_repo(db_session, domain_id="code")
178
179 report = await backfill_marketplace_domain_links(db_session, apply=True)
180 await db_session.commit()
181
182 assert repo.repo_id in report.skipped_ambiguous
183 assert repo.repo_id not in report.linked
184 refreshed = await db_session.get(MusehubRepo, repo.repo_id)
185 assert refreshed is not None
186 assert refreshed.marketplace_domain_id is None
187
188 @pytest.mark.asyncio
189 async def test_mdl14_no_match_repo_reported_by_repo_id(
190 self, db_session: AsyncSession
191 ) -> None:
192 repo = await _db_repo(db_session, domain_id="no-such-category")
193
194 report = await backfill_marketplace_domain_links(db_session, apply=True)
195 await db_session.commit()
196
197 assert repo.repo_id in report.skipped_no_match
198 assert repo.repo_id not in report.linked