Skip to content

Commit 4615cac

Browse files
committed
test(extensions): cover historical and bundled version validation
Assisted-by: OpenAI Codex (model: GPT-6 Sol, autonomous) Signed-off-by: 李永祺 <doribelove@gmail.com>
1 parent b21d936 commit 4615cac

1 file changed

Lines changed: 147 additions & 13 deletions

File tree

‎tests/specify_cli/extensions/test_catalog_versions.py‎

Lines changed: 147 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from __future__ import annotations
44

5-
from io import BytesIO
65
import hashlib
7-
from pathlib import Path
86
import zipfile
7+
from io import BytesIO
8+
from pathlib import Path
99

1010
import pytest
1111
import yaml
@@ -20,12 +20,12 @@
2020
)
2121

2222

23-
def _archive(tmp_path: Path, version: str) -> Path:
24-
path = tmp_path / f"demo-{version}.zip"
23+
def _archive(tmp_path: Path, version: str, extension_id: str = "demo-history") -> Path:
24+
path = tmp_path / f"{extension_id}-{version}.zip"
2525
manifest = {
2626
"schema_version": "1.0",
2727
"extension": {
28-
"id": "demo-history",
28+
"id": extension_id,
2929
"name": "Demo History",
3030
"version": version,
3131
"description": "Historical release test",
@@ -74,6 +74,18 @@ def _catalog(
7474
return ExtensionCatalog(project)
7575

7676

77+
class _ArchiveResponse(BytesIO):
78+
def __init__(self, data: bytes, url: str):
79+
super().__init__(data)
80+
self.url = url
81+
82+
def geturl(self):
83+
return self.url
84+
85+
def getheader(self, _name):
86+
return "application/zip"
87+
88+
7789
def test_legacy_entry_still_selects_its_current_release(tmp_path, monkeypatch):
7890
entry = {
7991
"id": "legacy",
@@ -171,16 +183,9 @@ def test_selected_release_download_uses_its_url_without_relookup(tmp_path, monke
171183
selected = catalog.get_extension_info("demo-history", "0.4.12")
172184
requested = []
173185

174-
class Response(BytesIO):
175-
def geturl(self):
176-
return requested[-1]
177-
178-
def getheader(self, _name):
179-
return "application/zip"
180-
181186
def open_url(url, **_kwargs):
182187
requested.append(url)
183-
return Response(archive.read_bytes())
188+
return _ArchiveResponse(archive.read_bytes(), url)
184189

185190
monkeypatch.setattr(catalog, "_open_url", open_url)
186191
monkeypatch.setattr(
@@ -228,6 +233,94 @@ def test_exact_cli_install_rejects_wrong_archive_before_writing(tmp_path, monkey
228233
).exists()
229234

230235

236+
def test_exact_cli_install_rejects_wrong_archive_id_before_writing(
237+
tmp_path, monkeypatch
238+
):
239+
project = tmp_path / "project"
240+
(project / ".specify").mkdir(parents=True)
241+
old_archive = _archive(tmp_path, "0.4.12")
242+
wrong_id_archive = _archive(tmp_path, "0.4.12", "another-extension")
243+
_catalog(monkeypatch, project, _entry(old_archive))
244+
monkeypatch.chdir(project)
245+
monkeypatch.setattr(
246+
ExtensionCatalog,
247+
"download_extension_info",
248+
lambda self, _info: wrong_id_archive,
249+
)
250+
251+
result = CliRunner().invoke(
252+
app, ["extension", "add", "demo-history", "--version", "0.4.12"]
253+
)
254+
255+
assert result.exit_code == 1
256+
assert "declares ID 'another-extension', expected 'demo-history'" in " ".join(
257+
result.output.split()
258+
)
259+
assert not (project / ".specify" / "extensions" / "another-extension").exists()
260+
assert not (project / ".specify" / "extensions" / "demo-history").exists()
261+
262+
263+
def test_exact_cli_install_rejects_wrong_historical_digest_before_writing(
264+
tmp_path, monkeypatch
265+
):
266+
project = tmp_path / "project"
267+
(project / ".specify").mkdir(parents=True)
268+
old_archive = _archive(tmp_path, "0.4.12")
269+
entry = _entry(old_archive)
270+
entry["releases"]["0.4.12"]["sha256"] = "0" * 64
271+
_catalog(monkeypatch, project, entry)
272+
monkeypatch.chdir(project)
273+
requested = []
274+
275+
def open_url(self, url, **_kwargs):
276+
requested.append(url)
277+
return _ArchiveResponse(old_archive.read_bytes(), url)
278+
279+
monkeypatch.setattr(ExtensionCatalog, "_open_url", open_url)
280+
281+
result = CliRunner().invoke(
282+
app, ["extension", "add", "demo-history", "--version", "0.4.12"]
283+
)
284+
285+
assert result.exit_code == 1
286+
assert requested == ["https://example.com/demo-0.4.12.zip"]
287+
assert "Integrity check failed for 'demo-history'" in " ".join(
288+
result.output.split()
289+
)
290+
assert not (project / ".specify" / "extensions" / "demo-history").exists()
291+
292+
293+
def test_unqualified_cli_install_uses_current_release_with_history(
294+
tmp_path, monkeypatch
295+
):
296+
project = tmp_path / "project"
297+
(project / ".specify").mkdir(parents=True)
298+
old_archive = _archive(tmp_path, "0.4.12")
299+
current_archive = _archive(tmp_path, "0.5.1")
300+
entry = _entry(old_archive)
301+
entry["sha256"] = hashlib.sha256(current_archive.read_bytes()).hexdigest()
302+
_catalog(monkeypatch, project, entry)
303+
monkeypatch.chdir(project)
304+
requested = []
305+
306+
def open_url(self, url, **_kwargs):
307+
requested.append(url)
308+
return _ArchiveResponse(current_archive.read_bytes(), url)
309+
310+
monkeypatch.setattr(ExtensionCatalog, "_open_url", open_url)
311+
312+
result = CliRunner().invoke(app, ["extension", "add", "demo-history"])
313+
314+
assert result.exit_code == 0, result.output
315+
assert requested == ["https://example.com/demo-0.5.1.zip"]
316+
installed = yaml.safe_load(
317+
(
318+
project / ".specify" / "extensions" / "demo-history" / "extension.yml"
319+
).read_text(encoding="utf-8")
320+
)
321+
assert installed["extension"]["version"] == "0.5.1"
322+
323+
231324
def test_exact_cli_install_historical_release(tmp_path, monkeypatch):
232325
project = tmp_path / "project"
233326
(project / ".specify").mkdir(parents=True)
@@ -341,3 +434,44 @@ def test_exact_cli_does_not_bypass_discovery_policy_via_bundled_copy(
341434
assert not (
342435
project / ".specify" / "extensions" / "agent-context" / "extension.yml"
343436
).exists()
437+
438+
439+
@pytest.mark.parametrize("matches_package", [True, False])
440+
def test_exact_cli_bundled_version_must_match_packaged_manifest(
441+
tmp_path, monkeypatch, matches_package
442+
):
443+
from specify_cli._assets import _locate_bundled_extension
444+
445+
bundled = _locate_bundled_extension("agent-context")
446+
assert bundled is not None
447+
packaged_version = ExtensionManifest(bundled / "extension.yml").version
448+
requested_version = packaged_version if matches_package else "9999.0.0"
449+
project = tmp_path / "project"
450+
(project / ".specify").mkdir(parents=True)
451+
entry = {
452+
"id": "agent-context",
453+
"name": "Agent Context",
454+
"version": requested_version,
455+
"bundled": True,
456+
"_catalog_name": "trusted",
457+
"_install_allowed": True,
458+
}
459+
_catalog(monkeypatch, project, entry)
460+
monkeypatch.chdir(project)
461+
462+
result = CliRunner().invoke(
463+
app, ["extension", "add", "agent-context", "--version", requested_version]
464+
)
465+
466+
installed_manifest = (
467+
project / ".specify" / "extensions" / "agent-context" / "extension.yml"
468+
)
469+
if matches_package:
470+
assert result.exit_code == 0, result.output
471+
assert ExtensionManifest(installed_manifest).version == packaged_version
472+
else:
473+
assert result.exit_code == 1
474+
assert f"version {requested_version} is not shipped" in " ".join(
475+
result.output.split()
476+
)
477+
assert not installed_manifest.exists()

0 commit comments

Comments
 (0)