Coverage for tests/test_cli.py: 99.32%

104 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-02 00:26 +0200

1"""Tests for the `cli` module.""" 

2 

3# IMPORTANT: Do not call `git_changelog.cli.main()` 

4# without passing a config file path, otherwise 

5# it will use its own config file and possibly modify 

6# the CHANGELOG! 

7 

8from __future__ import annotations 

9 

10import os 

11import sys 

12from textwrap import dedent 

13from typing import TYPE_CHECKING, Any, Iterator 

14 

15import pytest 

16import tomli_w 

17 

18from git_changelog import cli, debug 

19 

20if TYPE_CHECKING: 

21 from pathlib import Path 

22 

23 from tests.helpers import GitRepo 

24 

25 

26if sys.version_info >= (3, 11): 

27 from contextlib import chdir 

28else: 

29 # TODO: remove once support for Python 3.10 is dropped 

30 from contextlib import contextmanager 

31 

32 @contextmanager 

33 def chdir(path: str) -> Iterator[None]: # noqa: D103 33 ↛ 32line 33 didn't jump to line 32

34 old_wd = os.getcwd() 

35 os.chdir(path) 

36 try: 

37 yield 

38 finally: 

39 os.chdir(old_wd) 

40 

41 

42# IMPORTANT: See top module comment. 

43def test_main(tmp_path: Path) -> None: 

44 """Basic CLI test. 

45 

46 Parameters: 

47 tmp_path: A temporary path to write an empty config to. 

48 """ 

49 assert cli.main(["--config-file", str(tmp_path / "conf.toml")]) == 0 

50 

51 

52# IMPORTANT: See top module comment. 

53def test_show_help(capsys: pytest.CaptureFixture) -> None: 

54 """Show help. 

55 

56 Parameters: 

57 capsys: Pytest fixture to capture output. 

58 """ 

59 with pytest.raises(SystemExit): 

60 cli.main(["-h"]) 

61 captured = capsys.readouterr() 

62 assert "git-changelog" in captured.out 

63 

64 

65def test_get_version() -> None: 

66 """Get self version.""" 

67 assert cli.get_version() 

68 

69 

70@pytest.mark.parametrize( 

71 "args", 

72 [ 

73 (".", "-s", "feat,fix"), 

74 ("-s", "feat,fix", "."), 

75 ], 

76) 

77def test_passing_repository_and_sections(tmp_path: Path, args: tuple[str]) -> None: 

78 """Render the changelog of given repository, choosing sections. 

79 

80 Parameters: 

81 tmp_path: A temporary path to write the changelog into. 

82 args: Command line arguments. 

83 """ 

84 ch = tmp_path.joinpath("ch.md") 

85 parsed_settings = cli.parse_settings([*args, "-o", ch.as_posix(), "-c", "angular"]) 

86 

87 assert parsed_settings["output"] == str(ch.as_posix()) 

88 assert parsed_settings["sections"] == ["feat", "fix"] 

89 assert parsed_settings["repository"] == "." 

90 assert parsed_settings["convention"] == "angular" 

91 

92 

93@pytest.mark.parametrize("is_pyproject", [True, False, None]) 

94@pytest.mark.parametrize( 

95 ("sections", "sections_value"), 

96 [ 

97 (None, None), 

98 ("", None), 

99 (",,", None), 

100 ("a, b, ", ["a", "b"]), 

101 ("a, , ", ["a"]), 

102 ("a, b, c", ["a", "b", "c"]), 

103 (["a", "b", "c"], ["a", "b", "c"]), 

104 # Uncomment if None/null is once allowed as a value 

105 # ("none", None), 

106 # ("none, none, none", None), 

107 ], 

108) 

109@pytest.mark.parametrize("parse_refs", [None, False, True]) 

110def test_config_reading( 

111 tmp_path: Path, 

112 is_pyproject: bool | None, 

113 sections: str | list[str] | None, 

114 sections_value: list | None, 

115 parse_refs: bool | None, 

116) -> None: 

117 """Check settings files are correctly interpreted. 

118 

119 Parameters: 

120 tmp_path: A temporary path to write the settings file into. 

121 is_pyproject: Controls whether a `pyproject.toml` (`True`), 

122 a `.git-changelog.toml` (`False`) or a custom file (`None`) is being tested. 

123 sections: A `sections` config to override defaults. 

124 sections_value: The expectation for `sections` after reading the config file. 

125 parse_refs: An explicit override of the `parse_refs` of the config (if boolean) 

126 or skip writing the override into the test config file (`None`). 

127 """ 

128 with chdir(str(tmp_path)): 

129 config_content: dict[str, Any] = {} 

130 

131 if sections is not None: 

132 config_content["sections"] = sections 

133 

134 if parse_refs is not None: 

135 config_content["parse_refs"] = parse_refs 

136 

137 config_fname = "custom-file.toml" if is_pyproject is None else ".git-changelog.toml" 

138 config_fname = "pyproject.toml" if is_pyproject else config_fname 

139 tmp_path.joinpath(config_fname).write_text( 

140 tomli_w.dumps( 

141 config_content if not is_pyproject else {"tool": {"git-changelog": config_content}}, 

142 ), 

143 ) 

144 

145 settings = cli.read_config(tmp_path / config_fname) if config_fname == "custom-file.toml" else cli.read_config() 

146 

147 ground_truth: dict[str, Any] = cli.DEFAULT_SETTINGS.copy() 

148 ground_truth["sections"] = sections_value 

149 ground_truth["parse_refs"] = bool(parse_refs) 

150 

151 assert settings == ground_truth 

152 

153 

154@pytest.mark.parametrize("value", [None, False, True]) 

155def test_settings_warning( 

156 tmp_path: Path, 

157 value: bool, 

158) -> None: 

159 """Check warning on bump_latest. 

160 

161 Parameters: 

162 tmp_path: A temporary path to write the settings file into. 

163 """ 

164 with chdir(str(tmp_path)): 

165 args: list[str] = [] 

166 if value is not None: 

167 (tmp_path / ".git-changelog.toml").write_text( 

168 tomli_w.dumps({"bump_latest": value}), 

169 ) 

170 else: 

171 args = ["--bump-latest"] 

172 

173 with pytest.warns(FutureWarning) as record: 

174 cli.parse_settings(args) 

175 

176 solution = "is deprecated in favor of" # Warning comes from CLI parsing. 

177 if value is not None: # Warning is issued when parsing the config file. 

178 solution = "remove" if not value else "auto" 

179 

180 assert len(record) == 1 

181 assert solution in str(record[0].message) 

182 

183 # If setting is in config file AND passed by CLI, two FutureWarnings are issued. 

184 if (tmp_path / ".git-changelog.toml").exists(): 

185 with pytest.warns(FutureWarning) as record: 

186 cli.parse_settings(["--bump-latest"]) 

187 

188 assert len(record) == 2 

189 

190 

191# IMPORTANT: See top module comment. 

192def test_show_version(capsys: pytest.CaptureFixture) -> None: 

193 """Show version. 

194 

195 Parameters: 

196 capsys: Pytest fixture to capture output. 

197 """ 

198 with pytest.raises(SystemExit): 

199 cli.main(["-V"]) 

200 captured = capsys.readouterr() 

201 assert debug.get_version() in captured.out 

202 

203 

204# IMPORTANT: See top module comment. 

205def test_show_debug_info(capsys: pytest.CaptureFixture) -> None: 

206 """Show debug information. 

207 

208 Parameters: 

209 capsys: Pytest fixture to capture output. 

210 """ 

211 with pytest.raises(SystemExit): 

212 cli.main(["--debug-info"]) 

213 captured = capsys.readouterr().out.lower() 

214 assert "python" in captured 

215 assert "system" in captured 

216 assert "environment" in captured 

217 assert "packages" in captured 

218 

219 

220# IMPORTANT: See top module comment. 

221def test_jinja_context(repo: GitRepo) -> None: 

222 """Render template with custom template variables. 

223 

224 Parameters: 

225 repo: Temporary Git repository (fixture). 

226 """ 

227 repo.path.joinpath("conf.toml").write_text( 

228 dedent( 

229 """ 

230 [jinja_context] 

231 k1 = "ignored" 

232 k2 = "v2" 

233 k3 = "v3" 

234 """, 

235 ), 

236 ) 

237 

238 template = repo.path.joinpath(".custom_template.md.jinja") 

239 template.write_text("{% for key, val in jinja_context.items() %}{{ key }} = {{ val }}\n{% endfor %}") 

240 

241 exit_code = cli.main( 

242 [ 

243 "--config-file", 

244 str(repo.path / "conf.toml"), 

245 "-o", 

246 str(repo.path / "CHANGELOG.md"), 

247 "-t", 

248 f"path:{template}", 

249 "--jinja-context", 

250 "k1=v1", 

251 "-j", 

252 "k3=v3", 

253 str(repo.path), 

254 ], 

255 ) 

256 

257 assert exit_code == 0 

258 

259 contents = repo.path.joinpath("CHANGELOG.md").read_text() 

260 assert contents == "k1 = v1\nk2 = v2\nk3 = v3\n" 

261 

262 

263# IMPORTANT: See top module comment. 

264def test_versioning(repo: GitRepo) -> None: 

265 """Use a specific versioning scheme. 

266 

267 Parameters: 

268 repo: Temporary Git repository (fixture). 

269 """ 

270 repo.commit("feat: Feature") 

271 repo.tag("1.0.0") 

272 repo.commit("fix: Fix") 

273 with repo.enter(): 

274 assert cli.main(["-cconventional", "-nsemver", "-Bauto"]) == 0 

275 assert cli.main(["-cconventional", "-npep440", "-Bauto"]) == 0 

276 assert cli.main(["-cconventional", "-nsemver", "-B1.1.0"]) == 0 

277 assert cli.main(["-cconventional", "-npep440", "-B1.1.0"]) == 0 

278 assert cli.main(["-cconventional", "-nsemver", "-Bunknown"]) == 1 

279 assert cli.main(["-cconventional", "-npep440", "-Bunknown"]) == 1 

280 assert cli.main(["-cconventional", "-npep440", "-Balpha"]) == 1