Skip to content

Commit af2ef57

Browse files
committed
test(release): cover CITATION.cff in the version stamping guards
- add the third stamped file to both parametrized guard tests - compare against the release tool's own read, not a UTF-8 read - name all four stamped files in the release workflow comment
1 parent e349225 commit af2ef57

2 files changed

Lines changed: 35 additions & 17 deletions

File tree

‎.github/workflows/on-release-main.yml‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Automated release: python-semantic-release analyzes the conventional
22
# commits on main, and when a release is due it bumps the static version in
3-
# pyproject.toml and CITATION.cff, updates CHANGELOG.md, relocks uv.lock,
4-
# commits, tags vX.Y.Z, pushes and creates the GitHub release. Publishing
5-
# happens in this same run: pushes made with GITHUB_TOKEN do not retrigger
6-
# workflows, so a tag-triggered publish would never fire.
3+
# pyproject.toml, CITATION.cff, the osl-tasks skill and the Claude Code
4+
# plugin manifest, updates CHANGELOG.md, relocks uv.lock, commits, tags
5+
# vX.Y.Z, pushes and creates the GitHub release. Publishing happens in this
6+
# same run: pushes made with GITHUB_TOKEN do not retrigger workflows, so a
7+
# tag-triggered publish would never fire.
78
#
89
# Filename and environment name are load-bearing: the PyPI trusted
910
# publisher (OIDC) references workflow "on-release-main.yml" and

‎tests/test_cli_ops_skill.py‎

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
33
tests/test_cli.py only exercises osw.cli.ops through the typer command tree
44
(runner.invoke), so this module calls the operation function directly instead.
5+
6+
The last three tests cover the release version stamping of the skill and of
7+
the two other files that carry a copy of the osw version.
58
"""
69

710
from __future__ import annotations
@@ -30,8 +33,10 @@
3033
REPO_ROOT = Path(__file__).resolve().parent.parent
3134
PYPROJECT_PATH = REPO_ROOT / "pyproject.toml"
3235
PLUGIN_JSON_PATH = REPO_ROOT / ".claude-plugin" / "plugin.json"
36+
CITATION_PATH = REPO_ROOT / "CITATION.cff"
3337
SKILL_VERSION_ENTRY = "src/osw/skills/osl-tasks/SKILL.md:version"
3438
PLUGIN_VERSION_ENTRY = ".claude-plugin/plugin.json:version"
39+
CITATION_VERSION_ENTRY = "CITATION.cff:version"
3540

3641

3742
def _skill_version() -> str:
@@ -44,6 +49,10 @@ def _plugin_version() -> str:
4449
return json.loads(PLUGIN_JSON_PATH.read_text(encoding="utf-8"))["version"]
4550

4651

52+
def _citation_version() -> str:
53+
return yaml.safe_load(CITATION_PATH.read_text(encoding="utf-8"))["version"]
54+
55+
4756
def _pyproject() -> dict:
4857
return tomllib.loads(PYPROJECT_PATH.read_text(encoding="utf-8"))
4958

@@ -104,19 +113,21 @@ def test_install_skill_is_registered_on_the_cli_surface_only():
104113

105114

106115
@pytest.mark.parametrize(
107-
"read_version", [_skill_version, _plugin_version], ids=["skill", "plugin"]
116+
"read_version",
117+
[_skill_version, _plugin_version, _citation_version],
118+
ids=["skill", "plugin", "citation"],
108119
)
109120
def test_stamped_version_matches_the_package_version(read_version):
110-
"""The skill and the plugin manifest carry the osw version they ship with.
121+
"""Three files carry a copy of the osw version they ship with.
111122
112-
python-semantic-release rewrites all three in the release commit, so they
113-
can only differ when one of them was edited by hand.
123+
python-semantic-release rewrites all of them in the release commit, so
124+
they can only differ when one of them was edited by hand.
114125
"""
115126
assert read_version() == _pyproject()["project"]["version"]
116127

117128

118129
def test_stamped_files_are_registered_for_the_release_version_bump():
119-
"""Without these entries a release bumps pyproject.toml but neither file.
130+
"""Without these entries a release bumps pyproject.toml but not the file.
120131
121132
The version check above would only fail after that release, so this
122133
catches a removed entry at once.
@@ -125,19 +136,21 @@ def test_stamped_files_are_registered_for_the_release_version_bump():
125136

126137
assert SKILL_VERSION_ENTRY in variables
127138
assert PLUGIN_VERSION_ENTRY in variables
139+
assert CITATION_VERSION_ENTRY in variables
128140

129141

130142
@pytest.mark.parametrize(
131-
("entry", "path", "changed_line"),
143+
("entry", "changed_line"),
132144
[
133-
(SKILL_VERSION_ENTRY, SKILL_MD_PATH, ' version: "99.99.99"'),
134-
(PLUGIN_VERSION_ENTRY, PLUGIN_JSON_PATH, ' "version": "99.99.99",'),
145+
(SKILL_VERSION_ENTRY, ' version: "99.99.99"'),
146+
(PLUGIN_VERSION_ENTRY, ' "version": "99.99.99",'),
147+
# CITATION.cff also holds 'cff-version', which the pattern's negative
148+
# lookbehind must keep out of the replacement.
149+
(CITATION_VERSION_ENTRY, "version: 99.99.99"),
135150
],
136-
ids=["skill", "plugin"],
151+
ids=["skill", "plugin", "citation"],
137152
)
138-
def test_release_bump_rewrites_only_the_version_line(
139-
entry, path, changed_line, monkeypatch
140-
):
153+
def test_release_bump_rewrites_only_the_version_line(entry, changed_line, monkeypatch):
141154
"""python-semantic-release rewrites every match of its pattern in the file.
142155
143156
A second line such as 'version: 1.2.3' or 'osw version 1.2.3' would be
@@ -153,7 +166,11 @@ def test_release_bump_rewrites_only_the_version_line(
153166
# tool runs.
154167
monkeypatch.chdir(REPO_ROOT)
155168
declaration = PatternVersionDeclaration.from_string_definition(entry, "v{version}")
156-
before = path.read_text(encoding="utf-8").splitlines()
169+
# Read the file the way the release tool does. It uses the locale
170+
# encoding, which on Windows decodes a UTF-8 umlaut as two characters,
171+
# and a comparison against our own UTF-8 read would report that line as
172+
# changed.
173+
before = declaration.content.splitlines()
157174
after = declaration.replace(Version.parse("99.99.99")).splitlines()
158175

159176
assert len(after) == len(before)

0 commit comments

Comments
 (0)