Coverage for tests/test_prepare_release.py: 100%
97 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-11 11:00 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-11 11:00 +0000
1from datetime import date 1abcdefghijklm
2from pathlib import Path 1abcdefghijklm
4import pytest 1abcdefghijklm
5from typer.testing import CliRunner 1abcdefghijklm
7from scripts.prepare_release import ( 1abcdefghijklm
8 BumpType,
9 app,
10 bump_version,
11 get_release_notes_body,
12 update_release_notes,
13 update_version_file,
14)
16runner = CliRunner() 1abcdefghijklm
19@pytest.mark.parametrize( 1abcdefghijklm
20 ("current_version", "bump", "new_version"),
21 [
22 ("0.0.17", "major", "1.0.0"),
23 ("0.0.17", "minor", "0.1.0"),
24 ("0.0.17", "patch", "0.0.18"),
25 ],
26)
27def test_bump_version(current_version: str, bump: BumpType, new_version: str) -> None: 1abcdefghijklm
28 assert bump_version(current_version, bump) == new_version 2ecfcgchcicjckclcmcncocpcqc
31def test_update_version_file() -> None: 1abcdefghijklm
32 content = '__version__ = "0.0.17"\n\nfrom ._main import syncify as syncify\n' 1%'()*+,-./:;=
34 new_content = update_version_file(content, "0.0.18", Path("asyncer/__init__.py")) 1%'()*+,-./:;=
36 assert ( 1%'()*+,-./:;=
37 new_content
38 == '__version__ = "0.0.18"\n\nfrom ._main import syncify as syncify\n'
39 )
42def test_update_version_file_requires_newer_version() -> None: 1abcdefghijklm
43 content = '__version__ = "0.0.17"\n' 2? @ [ ] ^ _ ` { | } ~ abbb
45 with pytest.raises(RuntimeError, match="must be greater"): 2? @ [ ] ^ _ ` { | } ~ abbb
46 update_version_file(content, "0.0.17", Path("asyncer/__init__.py")) 2? @ [ ] ^ _ ` { | } ~ abbb
49def test_update_release_notes() -> None: 1abcdefghijklm
50 content = """# Release Notes 2cbdbebfbgbhbibjbkblbmbnbob
52## Latest Changes
54### Fixes
56* Fix something.
58## 0.0.17 (2026-02-21)
60### Fixes
62* Previous fix.
63"""
65 new_content = update_release_notes( 2cbdbebfbgbhbibjbkblbmbnbob
66 content, "0.0.18", date(2026, 5, 30), Path("docs/release-notes.md")
67 )
69 assert ( 2cbdbebfbgbhbibjbkblbmbnbob
70 new_content
71 == """# Release Notes
73## Latest Changes
75## 0.0.18 (2026-05-30)
77### Fixes
79* Fix something.
81## 0.0.17 (2026-02-21)
83### Fixes
85* Previous fix.
86"""
87 )
90def test_update_release_notes_rejects_existing_version() -> None: 1abcdefghijklm
91 content = """# Release Notes 2pbqbrbsbtbubvbwbxbybzbAbBb
93## Latest Changes
95## 0.0.18 (2026-05-30)
96"""
98 with pytest.raises(RuntimeError, match="already contain"): 2pbqbrbsbtbubvbwbxbybzbAbBb
99 update_release_notes( 2pbqbrbsbtbubvbwbxbybzbAbBb
100 content, "0.0.18", date(2026, 5, 30), Path("docs/release-notes.md")
101 )
104def test_get_release_notes_body_with_dated_heading() -> None: 1abcdefghijklm
105 content = """# Release Notes 2CbDbEbFbGbHbIbJbKbLbMbNbOb
107## Latest Changes
109## 0.0.18 (2026-05-30)
111### Fixes
113* Fix something.
115## 0.0.17 (2026-02-21)
117### Fixes
119* Previous fix.
120"""
122 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2CbDbEbFbGbHbIbJbKbLbMbNbOb
124 assert ( 2CbDbEbFbGbHbIbJbKbLbMbNbOb
125 body
126 == """### Fixes
128* Fix something.
129"""
130 )
133def test_get_release_notes_body_with_plain_heading() -> None: 1abcdefghijklm
134 content = """# Release Notes 2PbQbRbSbTbUbVbWbXbYbZb0b1b
136## Latest Changes
138## 0.0.18
140### Fixes
142* Fix something.
143"""
145 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2PbQbRbSbTbUbVbWbXbYbZb0b1b
147 assert body == "### Fixes\n\n* Fix something.\n" 2PbQbRbSbTbUbVbWbXbYbZb0b1b
150def test_get_release_notes_body_allows_non_version_h2_content() -> None: 1abcdefghijklm
151 content = """# Release Notes 22b3b4b5b6b7b8b9b!b#b$b%b'b
153## Latest Changes
155## 0.0.18
157## Highlights
159* Fix something.
161## 0.0.17
163* Previous fix.
164"""
166 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 22b3b4b5b6b7b8b9b!b#b$b%b'b
168 assert body == "## Highlights\n\n* Fix something.\n" 22b3b4b5b6b7b8b9b!b#b$b%b'b
171def test_get_release_notes_body_requires_version_section() -> None: 1abcdefghijklm
172 content = "# Release Notes\n\n## Latest Changes\n" 2(b)b*b+b,b-b.b/b:b;b=b?b@b
174 with pytest.raises(RuntimeError, match="Could not find"): 2(b)b*b+b,b-b.b/b:b;b=b?b@b
175 get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2(b)b*b+b,b-b.b/b:b;b=b?b@b
178def test_get_release_notes_body_requires_non_empty_section() -> None: 1abcdefghijklm
179 content = """# Release Notes 2[b]b^b_b`b{b|b}b~bacbcccdc
181## Latest Changes
183## 0.0.18
185## 0.0.17
187* Previous fix.
188"""
190 with pytest.raises(RuntimeError, match="is empty"): 2[b]b^b_b`b{b|b}b~bacbcccdc
191 get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2[b]b^b_b`b{b|b}b~bacbcccdc
194def test_cli_updates_configured_files(tmp_path: Path) -> None: 1abcdefghijklm
195 version_file = tmp_path / "asyncer" / "__init__.py" 1NOPQRSTUVWXYZ
196 version_file.parent.mkdir() 1NOPQRSTUVWXYZ
197 version_file.write_text('__version__ = "0.0.17"\n') 1NOPQRSTUVWXYZ
198 release_notes_file = tmp_path / "release-notes.md" 1NOPQRSTUVWXYZ
199 release_notes_file.write_text( 1NOPQRSTUVWXYZ
200 """# Release Notes
202## Latest Changes
204### Fixes
206* Fix something.
207"""
208 )
210 result = runner.invoke( 1NOPQRSTUVWXYZ
211 app,
212 [
213 "prepare",
214 "patch",
215 "--version-file",
216 str(version_file),
217 "--release-notes-file",
218 str(release_notes_file),
219 "--date",
220 "2026-05-30",
221 ],
222 )
224 assert result.exit_code == 0, result.output 1NOPQRSTUVWXYZ
225 assert "Prepared release 0.0.18 (2026-05-30)" in result.output 1NOPQRSTUVWXYZ
226 assert version_file.read_text() == '__version__ = "0.0.18"\n' 1NOPQRSTUVWXYZ
227 assert "## 0.0.18 (2026-05-30)" in release_notes_file.read_text() 1NOPQRSTUVWXYZ
230def test_cli_accepts_env_vars(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: 1abcdefghijklm
231 version_file = tmp_path / "asyncer" / "__init__.py" 1ABCDEFGHIJKLM
232 version_file.parent.mkdir() 1ABCDEFGHIJKLM
233 version_file.write_text('__version__ = "0.0.17"\n') 1ABCDEFGHIJKLM
234 release_notes_file = tmp_path / "docs" / "release-notes.md" 1ABCDEFGHIJKLM
235 release_notes_file.parent.mkdir() 1ABCDEFGHIJKLM
236 release_notes_file.write_text("# Release Notes\n\n## Latest Changes\n") 1ABCDEFGHIJKLM
237 monkeypatch.setenv("PREPARE_RELEASE_BUMP", "minor") 1ABCDEFGHIJKLM
238 monkeypatch.setenv("PREPARE_RELEASE_VERSION_FILE", str(version_file)) 1ABCDEFGHIJKLM
239 monkeypatch.setenv("PREPARE_RELEASE_RELEASE_NOTES_FILE", str(release_notes_file)) 1ABCDEFGHIJKLM
240 monkeypatch.setenv("PREPARE_RELEASE_DATE", "2026-05-30") 1ABCDEFGHIJKLM
242 result = runner.invoke(app, ["prepare"]) 1ABCDEFGHIJKLM
244 assert result.exit_code == 0, result.output 1ABCDEFGHIJKLM
245 assert "Prepared release 0.1.0 (2026-05-30)" in result.output 1ABCDEFGHIJKLM
246 assert version_file.read_text() == '__version__ = "0.1.0"\n' 1ABCDEFGHIJKLM
247 assert "## 0.1.0 (2026-05-30)" in release_notes_file.read_text() 1ABCDEFGHIJKLM
250def test_cli_prints_current_version(tmp_path: Path) -> None: 1abcdefghijklm
251 version_file = tmp_path / "asyncer" / "__init__.py" 10123456789!#$
252 version_file.parent.mkdir() 10123456789!#$
253 version_file.write_text('__version__ = "0.0.17"\n') 10123456789!#$
255 result = runner.invoke( 10123456789!#$
256 app,
257 [
258 "current-version",
259 "--version-file",
260 str(version_file),
261 ],
262 )
264 assert result.exit_code == 0, result.output 10123456789!#$
265 assert result.output == "0.0.17\n" 10123456789!#$
268def test_cli_prints_release_notes_with_agent_skills_note(tmp_path: Path) -> None: 1abcdefghijklm
269 version_file = tmp_path / "asyncer" / "__init__.py" 1nopqrstuvwxyz
270 version_file.parent.mkdir() 1nopqrstuvwxyz
271 version_content = '__version__ = "0.0.18"\n' 1nopqrstuvwxyz
272 version_file.write_text(version_content) 1nopqrstuvwxyz
273 release_notes_file = tmp_path / "release-notes.md" 1nopqrstuvwxyz
274 notes_content = """# Release Notes 1nopqrstuvwxyz
276## Latest Changes
278### Fixes
280* Unreleased fix.
282## 0.0.18 (2026-05-30)
284### Fixes
286* Fix something.
288## 0.0.17 (2026-05-23)
290### Fixes
292* Previous fix.
293"""
294 release_notes_file.write_text(notes_content) 1nopqrstuvwxyz
296 result = runner.invoke( 1nopqrstuvwxyz
297 app,
298 [
299 "release-notes",
300 "--version-file",
301 str(version_file),
302 "--release-notes-file",
303 str(release_notes_file),
304 ],
305 )
307 assert result.exit_code == 0, result.output 1nopqrstuvwxyz
308 note, separator, body = result.output.partition("\n\n") 1nopqrstuvwxyz
309 assert note.startswith("> Asyncer includes an official skill") 1nopqrstuvwxyz
310 assert note.count("`uvx library-skills`") == 1 1nopqrstuvwxyz
311 assert "[Develop with AI](https://asyncer.tiangolo.com/ai-development/)" in note 1nopqrstuvwxyz
312 assert separator 1nopqrstuvwxyz
313 assert body == "### Fixes\n\n* Fix something.\n" 1nopqrstuvwxyz
314 assert release_notes_file.read_text() == notes_content 1nopqrstuvwxyz
315 assert version_file.read_text() == version_content 1nopqrstuvwxyz