Coverage for tests / scripts / test_semantic_release_compute_next.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Tests for the semantic_release_compute_next utility.""" 

2 

3from __future__ import annotations 

4 

5import subprocess 

6from pathlib import Path 

7from typing import Any 

8 

9import pytest 

10from assertpy import assert_that 

11 

12 

13def _fake_completed(stdout: str = "") -> subprocess.CompletedProcess[str]: 

14 """Return a fake subprocess.CompletedProcess with the given stdout. 

15 

16 Args: 

17 stdout: Standard output string with trailing whitespace stripped. 

18 

19 Returns: 

20 subprocess.CompletedProcess: Fake subprocess.CompletedProcess with stdout. 

21 """ 

22 return subprocess.CompletedProcess( 

23 args=["git"], 

24 returncode=0, 

25 stdout=stdout, 

26 stderr="", 

27 ) 

28 

29 

30@pytest.fixture(autouse=True) 

31def _ensure_repo_root_on_path(monkeypatch: pytest.MonkeyPatch) -> None: 

32 """Ensure script imports resolve from repository root. 

33 

34 This avoids issues when tests are invoked from temp directories. 

35 

36 Args: 

37 monkeypatch: pytest.MonkeyPatch instance. 

38 """ 

39 repo_root = Path(__file__).resolve().parents[2] 

40 monkeypatch.chdir(repo_root) 

41 

42 

43def test_run_git_describe_allowed(monkeypatch: pytest.MonkeyPatch) -> None: 

44 """run_git should allow safe describe invocation. 

45 

46 Args: 

47 monkeypatch: pytest.MonkeyPatch instance. 

48 """ 

49 from scripts.ci.maintenance import semantic_release_compute_next as mod 

50 

51 monkeypatch.setattr(mod.shutil, "which", lambda *_: "/usr/bin/git") # type: ignore[attr-defined] 

52 monkeypatch.setattr( 

53 mod.subprocess, # type: ignore[attr-defined] 

54 "run", 

55 lambda *_, **__: _fake_completed("v1.2.3\n"), 

56 ) 

57 

58 out = mod.run_git("describe", "--tags", "--abbrev=0", "--match", "v*") 

59 assert_that(out).is_equal_to("v1.2.3") 

60 

61 

62def test_run_git_rev_parse_head_allowed(monkeypatch: pytest.MonkeyPatch) -> None: 

63 """run_git should allow 'rev-parse HEAD'. 

64 

65 Args: 

66 monkeypatch: pytest.MonkeyPatch instance. 

67 """ 

68 from scripts.ci.maintenance import semantic_release_compute_next as mod 

69 

70 monkeypatch.setattr(mod.shutil, "which", lambda *_: "/usr/bin/git") # type: ignore[attr-defined] 

71 monkeypatch.setattr( 

72 mod.subprocess, # type: ignore[attr-defined] 

73 "run", 

74 lambda *_, **__: _fake_completed("abcd123\n"), 

75 ) 

76 

77 out = mod.run_git("rev-parse", "HEAD") 

78 assert_that(out).is_equal_to("abcd123") 

79 

80 

81@pytest.mark.parametrize( 

82 "args", 

83 [ 

84 ("log", "HEAD", "--pretty=%s"), 

85 ("log", "HEAD", "--pretty=%B"), 

86 ("log", "v1.2.3..HEAD", "--pretty=%s"), 

87 ], 

88) 

89def test_run_git_log_allowed( 

90 monkeypatch: pytest.MonkeyPatch, 

91 args: tuple[str, ...], 

92) -> None: 

93 """run_git should allow the specific log forms used by the module. 

94 

95 Args: 

96 monkeypatch: pytest.MonkeyPatch instance. 

97 args: Tuple of arguments to pass to run_git. 

98 """ 

99 from scripts.ci.maintenance import semantic_release_compute_next as mod 

100 

101 monkeypatch.setattr(mod.shutil, "which", lambda *_: "/usr/bin/git") # type: ignore[attr-defined] 

102 monkeypatch.setattr(mod.subprocess, "run", lambda *_, **__: _fake_completed("ok\n")) # type: ignore[attr-defined] 

103 

104 out = mod.run_git(*args) 

105 assert_that(out).is_equal_to("ok") 

106 

107 

108@pytest.mark.parametrize( 

109 "args", 

110 [ 

111 ("status",), 

112 ("log", "--since=yesterday"), 

113 ("log", "HEAD; rm -rf /"), 

114 ("rev-parse", "main"), 

115 ], 

116) 

117def test_run_git_rejects_unsupported_or_unsafe( 

118 monkeypatch: pytest.MonkeyPatch, 

119 args: tuple[str, ...], 

120) -> None: 

121 """run_git should reject commands/args outside the strict allowlist. 

122 

123 Args: 

124 monkeypatch: pytest.MonkeyPatch instance. 

125 args: Tuple of arguments to pass to run_git. 

126 """ 

127 from scripts.ci.maintenance import semantic_release_compute_next as mod 

128 

129 monkeypatch.setattr(mod.shutil, "which", lambda *_: "/usr/bin/git") # type: ignore[attr-defined] 

130 

131 # subprocess.run should not be called; keep a guard that would fail if it is 

132 def _should_not_run( 

133 *_a: Any, 

134 **_k: Any, 

135 ) -> subprocess.CompletedProcess[str]: # pragma: no cover 

136 raise AssertionError("subprocess.run must not be invoked for rejected args") 

137 

138 monkeypatch.setattr(mod.subprocess, "run", _should_not_run) # type: ignore[attr-defined] 

139 

140 with pytest.raises(ValueError): 

141 mod.run_git(*args)