22
33import importlib .util
44import re
5- import subprocess
65from pathlib import Path
76
87import pytest
1312README = ROOT / "README.rst"
1413PUBLISH_WORKFLOW = ROOT / ".github" / "workflows" / "publish-pypi.yml"
1514PYPROJECT = ROOT / "pyproject.toml"
15+ SYNC_CITATION_SCRIPT = ROOT / "tools" / "release" / "sync_citation.py"
1616ZENODO_SCRIPT = ROOT / "tools" / "release" / "publish_zenodo.py"
1717
1818
@@ -26,39 +26,6 @@ def _citation_scalar(key):
2626 return match .group (1 )
2727
2828
29- def _latest_release_tag ():
30- """
31- Return the latest release tag and tag date from the local git checkout.
32- """
33- try :
34- tag_result = subprocess .run (
35- ["git" , "tag" , "--sort=-v:refname" ],
36- check = True ,
37- cwd = ROOT ,
38- capture_output = True ,
39- text = True ,
40- )
41- except (FileNotFoundError , subprocess .CalledProcessError ) as exc :
42- pytest .skip (f"Could not inspect git tags: { exc } " )
43- tags = [tag for tag in tag_result .stdout .splitlines () if tag .startswith ("v" )]
44- if not tags :
45- pytest .skip ("No release tags found in this checkout" )
46- tag = tags [0 ]
47- date_result = subprocess .run (
48- [
49- "git" ,
50- "for-each-ref" ,
51- f"refs/tags/{ tag } " ,
52- "--format=%(creatordate:short)" ,
53- ],
54- check = True ,
55- cwd = ROOT ,
56- capture_output = True ,
57- text = True ,
58- )
59- return tag .removeprefix ("v" ), date_result .stdout .strip ()
60-
61-
6229def _load_publish_zenodo ():
6330 """
6431 Import the Zenodo release helper directly from the repo checkout.
@@ -71,13 +38,37 @@ def _load_publish_zenodo():
7138 return module
7239
7340
74- def test_release_metadata_matches_latest_git_tag ():
41+ def _load_sync_citation ():
7542 """
76- Citation metadata should track the latest tagged release .
43+ Import the citation sync helper directly from the repo checkout .
7744 """
78- version , release_date = _latest_release_tag ()
79- assert _citation_scalar ("version" ) == version
80- assert _citation_scalar ("date-released" ) == release_date
45+ spec = importlib .util .spec_from_file_location ("sync_citation" , SYNC_CITATION_SCRIPT )
46+ if spec is None or spec .loader is None :
47+ raise ImportError (f"Could not load sync_citation from { SYNC_CITATION_SCRIPT } " )
48+ module = importlib .util .module_from_spec (spec )
49+ spec .loader .exec_module (module )
50+ return module
51+
52+
53+ def test_sync_citation_updates_release_metadata (tmp_path ):
54+ """
55+ Release automation should be able to sync CITATION.cff from a tag.
56+ """
57+ sync_citation = _load_sync_citation ()
58+ citation = tmp_path / "CITATION.cff"
59+ citation .write_text (CITATION_CFF .read_text (encoding = "utf-8" ), encoding = "utf-8" )
60+
61+ changed = sync_citation .sync_citation (
62+ citation ,
63+ tag = "v9.9.9" ,
64+ release_date = "2030-01-02" ,
65+ repo_root = ROOT ,
66+ )
67+
68+ text = citation .read_text (encoding = "utf-8" )
69+ assert changed is True
70+ assert 'version: "9.9.9"' in text
71+ assert 'date-released: "2030-01-02"' in text
8172
8273
8374def test_zenodo_release_metadata_is_built_from_repository_sources ():
@@ -96,6 +87,34 @@ def test_zenodo_release_metadata_is_built_from_repository_sources():
9687 assert metadata ["creators" ][0 ]["orcid" ] == "0000-0001-9862-8936"
9788
9889
90+ def test_zenodo_uploads_use_octet_stream (tmp_path , monkeypatch ):
91+ """
92+ Zenodo bucket uploads should use a generic binary content type.
93+ """
94+ publish_zenodo = _load_publish_zenodo ()
95+ calls = []
96+
97+ def fake_api_request (method , url , ** kwargs ):
98+ calls .append ((method , url , kwargs ))
99+ return None
100+
101+ monkeypatch .setattr (publish_zenodo , "api_request" , fake_api_request )
102+ (tmp_path / "ultraplot-2.1.5.tar.gz" ).write_bytes (b"sdist" )
103+ (tmp_path / "ultraplot-2.1.5-py3-none-any.whl" ).write_bytes (b"wheel" )
104+
105+ publish_zenodo .upload_dist_files (
106+ {"id" : 18492463 , "links" : {"bucket" : "https://zenodo.example/files/bucket" }},
107+ "token" ,
108+ tmp_path ,
109+ )
110+
111+ assert len (calls ) == 2
112+ assert all (method == "PUT" for method , _ , _ in calls )
113+ assert all (
114+ kwargs ["content_type" ] == "application/octet-stream" for _ , _ , kwargs in calls
115+ )
116+
117+
99118def test_zenodo_json_is_not_committed ():
100119 """
101120 Zenodo metadata should no longer be duplicated in a separate committed file.
@@ -114,10 +133,12 @@ def test_readme_citation_section_uses_repository_metadata():
114133
115134def test_publish_workflow_creates_github_release_and_pushes_to_zenodo ():
116135 """
117- Release tags should create a GitHub release and publish the same dist to Zenodo.
136+ Release tags should sync citation metadata, create a GitHub release, and
137+ publish the same dist to Zenodo.
118138 """
119139 text = PUBLISH_WORKFLOW .read_text (encoding = "utf-8" )
120140 assert 'tags: ["v*"]' in text
141+ assert text .count ("tools/release/sync_citation.py --tag" ) >= 2
121142 assert "softprops/action-gh-release@v2" in text
122143 assert "publish-zenodo:" in text
123144 assert "ZENODO_ACCESS_TOKEN" in text
0 commit comments