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
« 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."""
3from __future__ import annotations
5import subprocess
6from pathlib import Path
7from typing import Any
9import pytest
10from assertpy import assert_that
13def _fake_completed(stdout: str = "") -> subprocess.CompletedProcess[str]:
14 """Return a fake subprocess.CompletedProcess with the given stdout.
16 Args:
17 stdout: Standard output string with trailing whitespace stripped.
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 )
30@pytest.fixture(autouse=True)
31def _ensure_repo_root_on_path(monkeypatch: pytest.MonkeyPatch) -> None:
32 """Ensure script imports resolve from repository root.
34 This avoids issues when tests are invoked from temp directories.
36 Args:
37 monkeypatch: pytest.MonkeyPatch instance.
38 """
39 repo_root = Path(__file__).resolve().parents[2]
40 monkeypatch.chdir(repo_root)
43def test_run_git_describe_allowed(monkeypatch: pytest.MonkeyPatch) -> None:
44 """run_git should allow safe describe invocation.
46 Args:
47 monkeypatch: pytest.MonkeyPatch instance.
48 """
49 from scripts.ci.maintenance import semantic_release_compute_next as mod
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 )
58 out = mod.run_git("describe", "--tags", "--abbrev=0", "--match", "v*")
59 assert_that(out).is_equal_to("v1.2.3")
62def test_run_git_rev_parse_head_allowed(monkeypatch: pytest.MonkeyPatch) -> None:
63 """run_git should allow 'rev-parse HEAD'.
65 Args:
66 monkeypatch: pytest.MonkeyPatch instance.
67 """
68 from scripts.ci.maintenance import semantic_release_compute_next as mod
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 )
77 out = mod.run_git("rev-parse", "HEAD")
78 assert_that(out).is_equal_to("abcd123")
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.
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
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]
104 out = mod.run_git(*args)
105 assert_that(out).is_equal_to("ok")
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.
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
129 monkeypatch.setattr(mod.shutil, "which", lambda *_: "/usr/bin/git") # type: ignore[attr-defined]
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")
138 monkeypatch.setattr(mod.subprocess, "run", _should_not_run) # type: ignore[attr-defined]
140 with pytest.raises(ValueError):
141 mod.run_git(*args)