Coverage for tests / unit / ai / test_sarif_artifact.py: 100%

86 statements  

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

1"""Tests for artifact side-channel output (#723 item 11).""" 

2 

3from __future__ import annotations 

4 

5import json 

6from pathlib import Path 

7from unittest.mock import MagicMock 

8 

9import pytest 

10from assertpy import assert_that 

11 

12from lintro.enums.action import Action 

13from lintro.models.core.tool_result import ToolResult 

14from lintro.utils.tool_executor import _write_artifacts 

15 

16 

17def _make_config(*, artifacts: list[str] | None = None) -> MagicMock: 

18 """Build a minimal LintroConfig-like mock.""" 

19 cfg = MagicMock() 

20 cfg.execution.artifacts = artifacts or [] 

21 return cfg 

22 

23 

24def _make_logger() -> MagicMock: 

25 return MagicMock() 

26 

27 

28def _call_write( 

29 results: list[ToolResult], 

30 config: MagicMock, 

31 logger: MagicMock, 

32) -> None: 

33 _write_artifacts( 

34 results, 

35 config, 

36 logger, 

37 action=Action.CHECK, 

38 total_issues=0, 

39 total_fixed=0, 

40 ) 

41 

42 

43def test_no_artifacts_when_disabled( 

44 tmp_path: Path, 

45 monkeypatch: pytest.MonkeyPatch, 

46) -> None: 

47 """No files are produced when artifacts is empty and not in GHA.""" 

48 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

49 monkeypatch.chdir(tmp_path) 

50 

51 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

52 _call_write(results, _make_config(), _make_logger()) 

53 

54 artifacts_dir = tmp_path / ".lintro" / "artifacts" 

55 assert_that(artifacts_dir.exists()).is_false() 

56 

57 

58def test_sarif_artifact_written_when_configured( 

59 tmp_path: Path, 

60 monkeypatch: pytest.MonkeyPatch, 

61) -> None: 

62 """SARIF file is produced when 'sarif' is in execution.artifacts.""" 

63 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

64 monkeypatch.chdir(tmp_path) 

65 

66 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

67 _call_write(results, _make_config(artifacts=["sarif"]), _make_logger()) 

68 

69 sarif_path = tmp_path / ".lintro" / "artifacts" / "sarif" / "results.sarif.json" 

70 assert_that(sarif_path.exists()).is_true() 

71 

72 data = json.loads(sarif_path.read_text()) 

73 assert_that(data["version"]).is_equal_to("2.1.0") 

74 

75 

76def test_json_artifact_written_when_configured( 

77 tmp_path: Path, 

78 monkeypatch: pytest.MonkeyPatch, 

79) -> None: 

80 """JSON artifact file is produced when 'json' is in execution.artifacts.""" 

81 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

82 monkeypatch.chdir(tmp_path) 

83 

84 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

85 _call_write(results, _make_config(artifacts=["json"]), _make_logger()) 

86 

87 json_path = tmp_path / ".lintro" / "artifacts" / "json" / "results.json" 

88 assert_that(json_path.exists()).is_true() 

89 

90 data = json.loads(json_path.read_text()) 

91 assert_that(data).contains_key("summary") 

92 

93 

94def test_csv_artifact_written_when_configured( 

95 tmp_path: Path, 

96 monkeypatch: pytest.MonkeyPatch, 

97) -> None: 

98 """CSV artifact file is produced when 'csv' is in execution.artifacts.""" 

99 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

100 monkeypatch.chdir(tmp_path) 

101 

102 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

103 _call_write(results, _make_config(artifacts=["csv"]), _make_logger()) 

104 

105 csv_path = tmp_path / ".lintro" / "artifacts" / "csv" / "results.csv" 

106 assert_that(csv_path.exists()).is_true() 

107 assert_that(csv_path.read_text()).contains("tool") 

108 

109 

110def test_multiple_artifacts_written( 

111 tmp_path: Path, 

112 monkeypatch: pytest.MonkeyPatch, 

113) -> None: 

114 """Multiple artifact formats can be emitted simultaneously.""" 

115 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

116 monkeypatch.chdir(tmp_path) 

117 

118 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

119 _call_write( 

120 results, 

121 _make_config(artifacts=["json", "csv", "markdown"]), 

122 _make_logger(), 

123 ) 

124 

125 assert_that( 

126 (tmp_path / ".lintro" / "artifacts" / "json" / "results.json").exists(), 

127 ).is_true() 

128 assert_that( 

129 (tmp_path / ".lintro" / "artifacts" / "csv" / "results.csv").exists(), 

130 ).is_true() 

131 assert_that( 

132 (tmp_path / ".lintro" / "artifacts" / "markdown" / "results.md").exists(), 

133 ).is_true() 

134 

135 

136def test_sarif_auto_emits_in_github_actions( 

137 tmp_path: Path, 

138 monkeypatch: pytest.MonkeyPatch, 

139) -> None: 

140 """SARIF file is auto-emitted when GITHUB_ACTIONS=true.""" 

141 monkeypatch.setenv("GITHUB_ACTIONS", "true") 

142 monkeypatch.chdir(tmp_path) 

143 

144 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

145 _call_write(results, _make_config(), _make_logger()) 

146 

147 sarif_path = tmp_path / ".lintro" / "artifacts" / "sarif" / "results.sarif.json" 

148 assert_that(sarif_path.exists()).is_true() 

149 

150 

151def test_unknown_artifact_format_warns( 

152 tmp_path: Path, 

153 monkeypatch: pytest.MonkeyPatch, 

154) -> None: 

155 """Unknown artifact format logs a warning and is skipped.""" 

156 monkeypatch.delenv("GITHUB_ACTIONS", raising=False) 

157 monkeypatch.chdir(tmp_path) 

158 

159 logger = _make_logger() 

160 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

161 _call_write(results, _make_config(artifacts=["xlsx"]), logger) 

162 

163 logger.console_output.assert_called_once() 

164 call_arg = logger.console_output.call_args[0][0] 

165 assert_that(call_arg).contains("Unknown artifact format") 

166 

167 

168def test_artifact_logs_warning_on_write_failure( 

169 tmp_path: Path, 

170 monkeypatch: pytest.MonkeyPatch, 

171) -> None: 

172 """A write failure logs a warning instead of crashing.""" 

173 monkeypatch.setenv("GITHUB_ACTIONS", "true") 

174 # Block directory creation by placing a file where the dir should be 

175 blocker = tmp_path / ".lintro" / "artifacts" 

176 blocker.parent.mkdir(parents=True, exist_ok=True) 

177 blocker.write_text("not a directory") 

178 monkeypatch.chdir(tmp_path) 

179 

180 logger = _make_logger() 

181 results = [ToolResult(name="ruff", success=True, issues_count=0)] 

182 _call_write(results, _make_config(), logger) 

183 

184 logger.console_output.assert_called_once() 

185 call_arg = logger.console_output.call_args[0][0] 

186 assert_that(call_arg).contains("sarif artifact")