Files
stack/tests/lead/test_release_publish.py
kert 86b2ef5240
Some checks failed
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / notebooks-smoke (push) Has been cancelled
Deploy / changes (push) Has been cancelled
Deploy / notebooks (push) Has been cancelled
Deploy / zotero (push) Has been cancelled
Deploy / docs (push) Has been cancelled
Deploy / api (push) Has been cancelled
Deploy / bcda (push) Has been cancelled
Deploy / llm (push) Has been cancelled
Deploy / mc (push) Has been cancelled
Deploy / report (push) Has been cancelled
Infra CI / api (push) Has been cancelled
Infra CI / notebooks (push) Has been cancelled
Infra CI / zotero (push) Has been cancelled
Infra CI / docs (push) Has been cancelled
Infra CI / bcda (push) Has been cancelled
Infra CI / llm (push) Has been cancelled
Infra CI / mc (push) Has been cancelled
fix(lead): release publish resumes an identical bag; archive via rc pipe
The mc container's rootfs is read-only, so docker cp failed after the
bag was already served; the tarball now streams in through
'rc pipe'. Re-publishing the identical bag (same tag manifest) runs the
remaining steps; a different bag under a published version is still
refused. v0.1 is out: reach /releases, rustfs lead-releases, Gitea
release lead-review-v0.1, bib GI94D3XZ.
2026-11-11 15:18:33 -05:00

219 lines
6.8 KiB
Python

"""stack lead release publish — serve, archive, announce a verified bag."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from lead.release.bagit import write_bag
from lead.release.publish import publish, read_bag_info, write_index
def _bag(root: Path, version: str = "0.1") -> Path:
bag = root / f"lead-review-v{version}"
(bag / "data").mkdir(parents=True)
(bag / "data" / "protocol.md").write_text("# protocol\n")
(bag / "data" / "ro-crate-metadata.json").write_text(
json.dumps(
{
"@context": "https://w3id.org/ro/crate/1.2/context",
"@graph": [
{
"@id": "ro-crate-metadata.json",
"@type": "CreativeWork",
"about": {"@id": "./"},
},
{
"@id": "./",
"@type": "Dataset",
"name": "LEAD living review",
"description": "test release",
"datePublished": "2026-11-11",
"license": {
"@id": "https://creativecommons.org/licenses/by/4.0/"
},
"hasPart": [{"@id": "protocol.md"}],
},
{
"@id": "protocol.md",
"@type": "File",
"sha256": __import__("hashlib")
.sha256(b"# protocol\n")
.hexdigest(),
"contentSize": "11",
},
],
}
)
)
write_bag(
bag,
[
("Version", version),
("Git-Commit", "a" * 40),
("Bagging-Date", "2026-11-11"),
("Internal-Sender-Description", "first release"),
],
)
return bag
class Calls:
def __init__(self) -> None:
self.archived: list[tuple[Path, str]] = []
self.releases: list[dict] = []
self.bib: list[dict] = []
def archive(self, bag: Path, name: str) -> None:
self.archived.append((bag, name))
def gitea_release(self, **kw) -> str:
self.releases.append(kw)
return "https://git.test/rel/1"
def bib_item(self, **kw) -> str:
self.bib.append(kw)
return "KEY12345"
def test_read_bag_info_joins_continuation_lines(tmp_path):
bag = _bag(tmp_path)
info = read_bag_info(bag)
assert info["Version"] == "0.1" and info["Git-Commit"] == "a" * 40
assert info["Internal-Sender-Description"] == "first release"
def test_publish_serves_archives_and_announces(tmp_path):
bag = _bag(tmp_path / "build")
serve = tmp_path / "serve"
calls = Calls()
out = publish(
bag,
serve_root=serve,
base_url="https://reach.test/releases",
archive=calls.archive,
gitea_release=calls.gitea_release,
bib_item=calls.bib_item,
)
served = serve / bag.name
assert (served / "bagit.txt").exists()
assert calls.archived == [(served, bag.name)]
assert calls.releases[0]["tag"] == "lead-review-v0.1"
assert calls.releases[0]["commit"] == "a" * 40
assert calls.bib[0]["url"] == "https://reach.test/releases/lead-review-v0.1/"
index = json.loads((serve / "index.json").read_text())
assert index["releases"][0]["version"] == "0.1"
assert out["url"] == "https://reach.test/releases/lead-review-v0.1/"
def test_publish_refuses_a_bag_that_does_not_verify(tmp_path):
bag = _bag(tmp_path / "build")
(bag / "data" / "protocol.md").write_text("tampered\n")
calls = Calls()
with pytest.raises(ValueError, match="does not verify"):
publish(
bag,
serve_root=tmp_path / "serve",
base_url="https://reach.test/releases",
archive=calls.archive,
gitea_release=calls.gitea_release,
bib_item=calls.bib_item,
)
assert not calls.archived and not calls.releases
def test_publish_never_overwrites_a_published_version(tmp_path):
from lead.release.bagit import write_bag
calls = Calls()
kw = dict(
serve_root=tmp_path / "serve",
base_url="https://reach.test/releases",
archive=calls.archive,
gitea_release=calls.gitea_release,
bib_item=calls.bib_item,
)
publish(_bag(tmp_path / "a"), **kw)
other = _bag(tmp_path / "b")
write_bag(other, [("Version", "0.1"), ("Git-Commit", "b" * 40)])
with pytest.raises(FileExistsError):
publish(other, **kw)
def test_republishing_the_identical_bag_resumes(tmp_path):
# A publish that died after serving (the archive step failed) must be
# finishable: the same bag again runs the remaining steps.
calls = Calls()
kw = dict(
serve_root=tmp_path / "serve",
base_url="https://reach.test/releases",
gitea_release=calls.gitea_release,
bib_item=calls.bib_item,
)
bag = _bag(tmp_path / "a")
def boom(bag, name):
raise RuntimeError("rustfs down")
with pytest.raises(RuntimeError):
publish(bag, archive=boom, **kw)
publish(bag, archive=calls.archive, **kw)
assert len(calls.archived) == 1 and len(calls.releases) == 1
def test_index_lists_every_served_release_newest_first(tmp_path):
serve = tmp_path / "serve"
for v in ("0.1", "0.2"):
bag = _bag(tmp_path / f"b{v}", v)
(serve).mkdir(exist_ok=True)
bag.rename(serve / bag.name)
write_index(serve, base_url="https://reach.test/releases")
index = json.loads((serve / "index.json").read_text())
assert [r["version"] for r in index["releases"]] == ["0.2", "0.1"]
assert (serve / "index.html").read_text().count("lead-review-v") >= 2
def test_cli_publish_offline(tmp_path):
from typer.testing import CliRunner
from cli.lead import app
bag = _bag(tmp_path / "build")
res = CliRunner().invoke(
app,
[
"release",
"publish",
str(bag),
"--serve-root",
str(tmp_path / "serve"),
"--base-url",
"https://reach.test/releases",
"--no-archive",
"--no-gitea",
"--no-bib",
],
)
assert res.exit_code == 0, res.output
assert "url\thttps://reach.test/releases/lead-review-v0.1/" in res.output
from lead.release.bagit import write_bag
other = _bag(tmp_path / "other")
write_bag(other, [("Version", "0.1"), ("Git-Commit", "c" * 40)])
again = CliRunner().invoke(
app,
[
"release",
"publish",
str(other),
"--serve-root",
str(tmp_path / "serve"),
"--no-archive",
"--no-gitea",
"--no-bib",
],
)
assert again.exit_code == 1 and "already published" in again.output