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

1from datetime import date 1abcdefghijklm

2from pathlib import Path 1abcdefghijklm

3 

4import pytest 1abcdefghijklm

5from typer.testing import CliRunner 1abcdefghijklm

6 

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) 

15 

16runner = CliRunner() 1abcdefghijklm

17 

18 

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

29 

30 

31def test_update_version_file() -> None: 1abcdefghijklm

32 content = '__version__ = "0.0.17"\n\nfrom ._main import syncify as syncify\n' 1%'()*+,-./:;=

33 

34 new_content = update_version_file(content, "0.0.18", Path("asyncer/__init__.py")) 1%'()*+,-./:;=

35 

36 assert ( 1%'()*+,-./:;=

37 new_content 

38 == '__version__ = "0.0.18"\n\nfrom ._main import syncify as syncify\n' 

39 ) 

40 

41 

42def test_update_version_file_requires_newer_version() -> None: 1abcdefghijklm

43 content = '__version__ = "0.0.17"\n' 2? @ [ ] ^ _ ` { | } ~ abbb

44 

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

47 

48 

49def test_update_release_notes() -> None: 1abcdefghijklm

50 content = """# Release Notes 2cbdbebfbgbhbibjbkblbmbnbob

51 

52## Latest Changes 

53 

54### Fixes 

55 

56* Fix something. 

57 

58## 0.0.17 (2026-02-21) 

59 

60### Fixes 

61 

62* Previous fix. 

63""" 

64 

65 new_content = update_release_notes( 2cbdbebfbgbhbibjbkblbmbnbob

66 content, "0.0.18", date(2026, 5, 30), Path("docs/release-notes.md") 

67 ) 

68 

69 assert ( 2cbdbebfbgbhbibjbkblbmbnbob

70 new_content 

71 == """# Release Notes 

72 

73## Latest Changes 

74 

75## 0.0.18 (2026-05-30) 

76 

77### Fixes 

78 

79* Fix something. 

80 

81## 0.0.17 (2026-02-21) 

82 

83### Fixes 

84 

85* Previous fix. 

86""" 

87 ) 

88 

89 

90def test_update_release_notes_rejects_existing_version() -> None: 1abcdefghijklm

91 content = """# Release Notes 2pbqbrbsbtbubvbwbxbybzbAbBb

92 

93## Latest Changes 

94 

95## 0.0.18 (2026-05-30) 

96""" 

97 

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 ) 

102 

103 

104def test_get_release_notes_body_with_dated_heading() -> None: 1abcdefghijklm

105 content = """# Release Notes 2CbDbEbFbGbHbIbJbKbLbMbNbOb

106 

107## Latest Changes 

108 

109## 0.0.18 (2026-05-30) 

110 

111### Fixes 

112 

113* Fix something. 

114 

115## 0.0.17 (2026-02-21) 

116 

117### Fixes 

118 

119* Previous fix. 

120""" 

121 

122 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2CbDbEbFbGbHbIbJbKbLbMbNbOb

123 

124 assert ( 2CbDbEbFbGbHbIbJbKbLbMbNbOb

125 body 

126 == """### Fixes 

127 

128* Fix something. 

129""" 

130 ) 

131 

132 

133def test_get_release_notes_body_with_plain_heading() -> None: 1abcdefghijklm

134 content = """# Release Notes 2PbQbRbSbTbUbVbWbXbYbZb0b1b

135 

136## Latest Changes 

137 

138## 0.0.18 

139 

140### Fixes 

141 

142* Fix something. 

143""" 

144 

145 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 2PbQbRbSbTbUbVbWbXbYbZb0b1b

146 

147 assert body == "### Fixes\n\n* Fix something.\n" 2PbQbRbSbTbUbVbWbXbYbZb0b1b

148 

149 

150def test_get_release_notes_body_allows_non_version_h2_content() -> None: 1abcdefghijklm

151 content = """# Release Notes 22b3b4b5b6b7b8b9b!b#b$b%b'b

152 

153## Latest Changes 

154 

155## 0.0.18 

156 

157## Highlights 

158 

159* Fix something. 

160 

161## 0.0.17 

162 

163* Previous fix. 

164""" 

165 

166 body = get_release_notes_body(content, "0.0.18", Path("docs/release-notes.md")) 22b3b4b5b6b7b8b9b!b#b$b%b'b

167 

168 assert body == "## Highlights\n\n* Fix something.\n" 22b3b4b5b6b7b8b9b!b#b$b%b'b

169 

170 

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

173 

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

176 

177 

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

180 

181## Latest Changes 

182 

183## 0.0.18 

184 

185## 0.0.17 

186 

187* Previous fix. 

188""" 

189 

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

192 

193 

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 

201 

202## Latest Changes 

203 

204### Fixes 

205 

206* Fix something. 

207""" 

208 ) 

209 

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 ) 

223 

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

228 

229 

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

241 

242 result = runner.invoke(app, ["prepare"]) 1ABCDEFGHIJKLM

243 

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

248 

249 

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!#$

254 

255 result = runner.invoke( 10123456789!#$

256 app, 

257 [ 

258 "current-version", 

259 "--version-file", 

260 str(version_file), 

261 ], 

262 ) 

263 

264 assert result.exit_code == 0, result.output 10123456789!#$

265 assert result.output == "0.0.17\n" 10123456789!#$

266 

267 

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

275 

276## Latest Changes 

277 

278### Fixes 

279 

280* Unreleased fix. 

281 

282## 0.0.18 (2026-05-30) 

283 

284### Fixes 

285 

286* Fix something. 

287 

288## 0.0.17 (2026-05-23) 

289 

290### Fixes 

291 

292* Previous fix. 

293""" 

294 release_notes_file.write_text(notes_content) 1nopqrstuvwxyz

295 

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 ) 

306 

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