Coverage for tests / test_prepare_release.py: 100%

122 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-03 20:19 +0000

1import importlib.util 1abcdefghi

2import sys 1abcdefghi

3from datetime import date 1abcdefghi

4from pathlib import Path 1abcdefghi

5 

6import pytest 1abcdefghi

7from typer.testing import CliRunner 1abcdefghi

8 

9prepare_release_path = Path(__file__).parents[1] / "scripts" / "prepare_release.py" 1abcdefghi

10spec = importlib.util.spec_from_file_location("prepare_release", prepare_release_path) 1abcdefghi

11assert spec is not None 1abcdefghi

12prepare_release = importlib.util.module_from_spec(spec) 1abcdefghi

13sys.modules["prepare_release"] = prepare_release 1abcdefghi

14assert spec.loader is not None 1abcdefghi

15spec.loader.exec_module(prepare_release) 1abcdefghi

16 

17BumpType = prepare_release.BumpType 1abcdefghi

18app = prepare_release.app 1abcdefghi

19bump_version = prepare_release.bump_version 1abcdefghi

20get_release_notes_body = prepare_release.get_release_notes_body 1abcdefghi

21update_json_version_file = prepare_release.update_json_version_file 1abcdefghi

22update_package_lock_file = prepare_release.update_package_lock_file 1abcdefghi

23update_release_notes = prepare_release.update_release_notes 1abcdefghi

24update_version_file = prepare_release.update_version_file 1abcdefghi

25 

26runner = CliRunner() 1abcdefghi

27 

28 

29@pytest.mark.parametrize( 1abcdefghi

30 ("current_version", "bump", "new_version"), 

31 [ 

32 ("0.0.5", "major", "1.0.0"), 

33 ("0.0.5", "minor", "0.1.0"), 

34 ("0.0.5", "patch", "0.0.6"), 

35 ], 

36) 

37def test_bump_version(current_version: str, bump: BumpType, new_version: str) -> None: 1abcdefghi

38 assert bump_version(current_version, bump) == new_version 23b4b5b6b7b8b9b!b#b$b%b'b(b)b*b

39 

40 

41def test_update_version_file() -> None: 1abcdefghi

42 content = '[project]\nname = "library-skills"\nversion = "0.0.5"\n' 1TUVWXYZ01

43 

44 new_content = update_version_file(content, "0.0.6", Path("pyproject.toml")) 1TUVWXYZ01

45 

46 assert new_content == '[project]\nname = "library-skills"\nversion = "0.0.6"\n' 1TUVWXYZ01

47 

48 

49def test_update_version_file_requires_newer_version() -> None: 1abcdefghi

50 content = '[project]\nversion = "0.0.5"\n' 123456789!

51 

52 with pytest.raises(RuntimeError, match="must be greater"): 123456789!

53 update_version_file(content, "0.0.5", Path("pyproject.toml")) 123456789!

54 

55 

56def test_update_json_version_file() -> None: 1abcdefghi

57 content = '{\n "name": "library-skills",\n "version": "0.0.5"\n}\n' 1#$%'()*+,

58 

59 new_content = update_json_version_file(content, "0.0.6", Path("package.json")) 1#$%'()*+,

60 

61 assert new_content == '{\n "name": "library-skills",\n "version": "0.0.6"\n}\n' 1#$%'()*+,

62 

63 

64def test_update_package_lock_file() -> None: 1abcdefghi

65 content = ( 1-./:;=?@[

66 "{\n" 

67 ' "name": "library-skills",\n' 

68 ' "version": "0.0.5",\n' 

69 ' "packages": {\n' 

70 ' "": {\n' 

71 ' "name": "library-skills",\n' 

72 ' "version": "0.0.5"\n' 

73 " }\n" 

74 " }\n" 

75 "}\n" 

76 ) 

77 

78 new_content = update_package_lock_file(content, "0.0.6", Path("package-lock.json")) 1-./:;=?@[

79 

80 assert ( 1-./:;=?@[

81 new_content == "{\n" 

82 ' "name": "library-skills",\n' 

83 ' "version": "0.0.6",\n' 

84 ' "packages": {\n' 

85 ' "": {\n' 

86 ' "name": "library-skills",\n' 

87 ' "version": "0.0.6"\n' 

88 " }\n" 

89 " }\n" 

90 "}\n" 

91 ) 

92 

93 

94def test_update_release_notes() -> None: 1abcdefghi

95 content = """# Release Notes 2] ^ _ ` { | } ~ ab

96 

97## Latest Changes 

98 

99### Fixes 

100 

101* Fix something. 

102 

103## 0.0.5 (2026-05-01) 

104 

105### Fixes 

106 

107* Previous fix. 

108""" 

109 

110 new_content = update_release_notes( 2] ^ _ ` { | } ~ ab

111 content, "0.0.6", date(2026, 6, 20), Path("docs/release-notes.md") 

112 ) 

113 

114 assert ( 2] ^ _ ` { | } ~ ab

115 new_content 

116 == """# Release Notes 

117 

118## Latest Changes 

119 

120## 0.0.6 (2026-06-20) 

121 

122### Fixes 

123 

124* Fix something. 

125 

126## 0.0.5 (2026-05-01) 

127 

128### Fixes 

129 

130* Previous fix. 

131""" 

132 ) 

133 

134 

135def test_update_release_notes_rejects_existing_version() -> None: 1abcdefghi

136 content = """# Release Notes 2bbcbdbebfbgbhbibjb

137 

138## Latest Changes 

139 

140## 0.0.6 (2026-06-20) 

141""" 

142 

143 with pytest.raises(RuntimeError, match="already contain"): 2bbcbdbebfbgbhbibjb

144 update_release_notes( 2bbcbdbebfbgbhbibjb

145 content, "0.0.6", date(2026, 6, 20), Path("docs/release-notes.md") 

146 ) 

147 

148 

149def test_get_release_notes_body_with_dated_heading() -> None: 1abcdefghi

150 content = """# Release Notes 2kblbmbnbobpbqbrbsb

151 

152## Latest Changes 

153 

154## 0.0.6 (2026-06-20) 

155 

156### Fixes 

157 

158* Fix something. 

159 

160## 0.0.5 (2026-05-01) 

161 

162### Fixes 

163 

164* Previous fix. 

165""" 

166 

167 body = get_release_notes_body(content, "0.0.6", Path("docs/release-notes.md")) 2kblbmbnbobpbqbrbsb

168 

169 assert ( 2kblbmbnbobpbqbrbsb

170 body 

171 == """### Fixes 

172 

173* Fix something. 

174""" 

175 ) 

176 

177 

178def test_get_release_notes_body_with_plain_heading() -> None: 1abcdefghi

179 content = """# Release Notes 2tbubvbwbxbybzbAbBb

180 

181## Latest Changes 

182 

183## 0.0.6 

184 

185### Fixes 

186 

187* Fix something. 

188""" 

189 

190 body = get_release_notes_body(content, "0.0.6", Path("docs/release-notes.md")) 2tbubvbwbxbybzbAbBb

191 

192 assert body == "### Fixes\n\n* Fix something.\n" 2tbubvbwbxbybzbAbBb

193 

194 

195def test_get_release_notes_body_allows_non_version_h2_content() -> None: 1abcdefghi

196 content = """# Release Notes 2CbDbEbFbGbHbIbJbKb

197 

198## Latest Changes 

199 

200## 0.0.6 

201 

202## Highlights 

203 

204* Fix something. 

205 

206## 0.0.5 

207 

208* Previous fix. 

209""" 

210 

211 body = get_release_notes_body(content, "0.0.6", Path("docs/release-notes.md")) 2CbDbEbFbGbHbIbJbKb

212 

213 assert body == "## Highlights\n\n* Fix something.\n" 2CbDbEbFbGbHbIbJbKb

214 

215 

216def test_get_release_notes_body_requires_version_section() -> None: 1abcdefghi

217 content = "# Release Notes\n\n## Latest Changes\n" 2LbMbNbObPbQbRbSbTb

218 

219 with pytest.raises(RuntimeError, match="Could not find"): 2LbMbNbObPbQbRbSbTb

220 get_release_notes_body(content, "0.0.6", Path("docs/release-notes.md")) 2LbMbNbObPbQbRbSbTb

221 

222 

223def test_get_release_notes_body_requires_non_empty_section() -> None: 1abcdefghi

224 content = """# Release Notes 2UbVbWbXbYbZb0b1b2b

225 

226## Latest Changes 

227 

228## 0.0.6 

229 

230## 0.0.5 

231 

232* Previous fix. 

233""" 

234 

235 with pytest.raises(RuntimeError, match="is empty"): 2UbVbWbXbYbZb0b1b2b

236 get_release_notes_body(content, "0.0.6", Path("docs/release-notes.md")) 2UbVbWbXbYbZb0b1b2b

237 

238 

239def test_cli_updates_configured_files(tmp_path: Path) -> None: 1abcdefghi

240 version_file = tmp_path / "pyproject.toml" 1stuvwxyzA

241 version_file.write_text('[project]\nversion = "0.0.5"\n', encoding="utf-8") 1stuvwxyzA

242 package_json_file = tmp_path / "package.json" 1stuvwxyzA

243 package_json_file.write_text('{"version": "0.0.5"}\n', encoding="utf-8") 1stuvwxyzA

244 package_lock_file = tmp_path / "package-lock.json" 1stuvwxyzA

245 package_lock_file.write_text( 1stuvwxyzA

246 '{"version": "0.0.5", "packages": {"": {"version": "0.0.5"}}}\n', 

247 encoding="utf-8", 

248 ) 

249 release_notes_file = tmp_path / "release-notes.md" 1stuvwxyzA

250 release_notes_file.write_text( 1stuvwxyzA

251 """# Release Notes 

252 

253## Latest Changes 

254 

255### Fixes 

256 

257* Fix something. 

258""", 

259 encoding="utf-8", 

260 ) 

261 

262 result = runner.invoke( 1stuvwxyzA

263 app, 

264 [ 

265 "prepare", 

266 "patch", 

267 "--version-file", 

268 str(version_file), 

269 "--package-json-file", 

270 str(package_json_file), 

271 "--package-lock-file", 

272 str(package_lock_file), 

273 "--release-notes-file", 

274 str(release_notes_file), 

275 "--date", 

276 "2026-06-20", 

277 ], 

278 ) 

279 

280 assert result.exit_code == 0, result.output 1stuvwxyzA

281 assert "Prepared release 0.0.6 (2026-06-20)" in result.output 1stuvwxyzA

282 assert version_file.read_text(encoding="utf-8") == '[project]\nversion = "0.0.6"\n' 1stuvwxyzA

283 assert '"version": "0.0.6"' in package_json_file.read_text(encoding="utf-8") 1stuvwxyzA

284 assert '"version": "0.0.6"' in package_lock_file.read_text(encoding="utf-8") 1stuvwxyzA

285 assert "## 0.0.6 (2026-06-20)" in release_notes_file.read_text(encoding="utf-8") 1stuvwxyzA

286 

287 

288def test_cli_accepts_env_vars(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: 1abcdefghi

289 version_file = tmp_path / "pyproject.toml" 1jklmnopqr

290 version_file.write_text('[project]\nversion = "0.0.5"\n', encoding="utf-8") 1jklmnopqr

291 package_json_file = tmp_path / "package.json" 1jklmnopqr

292 package_json_file.write_text('{"version": "0.0.5"}\n', encoding="utf-8") 1jklmnopqr

293 package_lock_file = tmp_path / "package-lock.json" 1jklmnopqr

294 package_lock_file.write_text( 1jklmnopqr

295 '{"version": "0.0.5", "packages": {"": {"version": "0.0.5"}}}\n', 

296 encoding="utf-8", 

297 ) 

298 release_notes_file = tmp_path / "docs" / "release-notes.md" 1jklmnopqr

299 release_notes_file.parent.mkdir() 1jklmnopqr

300 release_notes_file.write_text( 1jklmnopqr

301 "# Release Notes\n\n## Latest Changes\n", encoding="utf-8" 

302 ) 

303 monkeypatch.setenv("PREPARE_RELEASE_BUMP", "minor") 1jklmnopqr

304 monkeypatch.setenv("PREPARE_RELEASE_VERSION_FILE", str(version_file)) 1jklmnopqr

305 monkeypatch.setenv("PREPARE_RELEASE_PACKAGE_JSON_FILE", str(package_json_file)) 1jklmnopqr

306 monkeypatch.setenv("PREPARE_RELEASE_PACKAGE_LOCK_FILE", str(package_lock_file)) 1jklmnopqr

307 monkeypatch.setenv("PREPARE_RELEASE_RELEASE_NOTES_FILE", str(release_notes_file)) 1jklmnopqr

308 monkeypatch.setenv("PREPARE_RELEASE_DATE", "2026-06-20") 1jklmnopqr

309 

310 result = runner.invoke(app, ["prepare"]) 1jklmnopqr

311 

312 assert result.exit_code == 0, result.output 1jklmnopqr

313 assert "Prepared release 0.1.0 (2026-06-20)" in result.output 1jklmnopqr

314 assert version_file.read_text(encoding="utf-8") == '[project]\nversion = "0.1.0"\n' 1jklmnopqr

315 assert "0.1.0" in package_json_file.read_text(encoding="utf-8") 1jklmnopqr

316 assert "0.1.0" in package_lock_file.read_text(encoding="utf-8") 1jklmnopqr

317 assert "## 0.1.0 (2026-06-20)" in release_notes_file.read_text(encoding="utf-8") 1jklmnopqr

318 

319 

320def test_cli_prints_current_version(tmp_path: Path) -> None: 1abcdefghi

321 version_file = tmp_path / "pyproject.toml" 1KLMNOPQRS

322 version_file.write_text('[project]\nversion = "0.0.5"\n', encoding="utf-8") 1KLMNOPQRS

323 

324 result = runner.invoke( 1KLMNOPQRS

325 app, 

326 [ 

327 "current-version", 

328 "--version-file", 

329 str(version_file), 

330 ], 

331 ) 

332 

333 assert result.exit_code == 0, result.output 1KLMNOPQRS

334 assert result.output == "0.0.5\n" 1KLMNOPQRS

335 

336 

337def test_cli_prints_release_notes(tmp_path: Path) -> None: 1abcdefghi

338 version_file = tmp_path / "pyproject.toml" 1BCDEFGHIJ

339 version_file.write_text('[project]\nversion = "0.0.6"\n', encoding="utf-8") 1BCDEFGHIJ

340 release_notes_file = tmp_path / "release-notes.md" 1BCDEFGHIJ

341 release_notes_file.write_text( 1BCDEFGHIJ

342 """# Release Notes 

343 

344## Latest Changes 

345 

346## 0.0.6 (2026-06-20) 

347 

348### Fixes 

349 

350* Fix something. 

351""", 

352 encoding="utf-8", 

353 ) 

354 

355 result = runner.invoke( 1BCDEFGHIJ

356 app, 

357 [ 

358 "release-notes", 

359 "--version-file", 

360 str(version_file), 

361 "--release-notes-file", 

362 str(release_notes_file), 

363 ], 

364 ) 

365 

366 assert result.exit_code == 0, result.output 1BCDEFGHIJ

367 assert result.output == "### Fixes\n\n* Fix something.\n" 1BCDEFGHIJ