Coverage for tests / unit / formatters / styles / test_style_github.py: 100%
62 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"""Unit tests for GitHubStyle formatter.
3Tests verify GitHubStyle emits correct GitHub Actions annotation commands
4with proper severity mapping, escaping, and edge-case handling.
5"""
7from __future__ import annotations
9from assertpy import assert_that
11from lintro.formatters.styles.github import GitHubStyle
14def test_github_style_single_error_annotation(github_style: GitHubStyle) -> None:
15 """GitHubStyle emits an ::error annotation for error severity.
17 Args:
18 github_style: The GitHubStyle formatter instance.
19 """
20 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
21 rows = [["src/main.py", "10", "5", "E501", "Line too long", "error"]]
23 result = github_style.format(columns, rows, tool_name="ruff")
25 assert_that(result).starts_with("::error ")
26 assert_that(result).contains("file=src/main.py")
27 assert_that(result).contains("line=10")
28 assert_that(result).contains("col=5")
29 assert_that(result).contains("title=ruff(E501)")
30 assert_that(result).contains("::Line too long")
33def test_github_style_warning_severity(github_style: GitHubStyle) -> None:
34 """GitHubStyle maps warning severity to ::warning.
36 Args:
37 github_style: The GitHubStyle formatter instance.
38 """
39 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
40 rows = [["a.py", "1", "-", "W001", "Unused import", "warning"]]
42 result = github_style.format(columns, rows, tool_name="tool")
44 assert_that(result).starts_with("::warning ")
47def test_github_style_info_severity_maps_to_notice(github_style: GitHubStyle) -> None:
48 """GitHubStyle maps info severity to ::notice.
50 Args:
51 github_style: The GitHubStyle formatter instance.
52 """
53 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
54 rows = [["a.py", "1", "-", "I001", "Info msg", "info"]]
56 result = github_style.format(columns, rows, tool_name="tool")
58 assert_that(result).starts_with("::notice ")
61def test_github_style_alias_severity(github_style: GitHubStyle) -> None:
62 """GitHubStyle normalizes alias severities (e.g. HIGH → error).
64 Args:
65 github_style: The GitHubStyle formatter instance.
66 """
67 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
68 rows = [["a.py", "1", "-", "B001", "Hardcoded password", "HIGH"]]
70 result = github_style.format(columns, rows, tool_name="bandit")
72 assert_that(result).starts_with("::error ")
75def test_github_style_missing_severity_defaults_to_warning(
76 github_style: GitHubStyle,
77) -> None:
78 """GitHubStyle defaults to ::warning when severity column is empty.
80 Args:
81 github_style: The GitHubStyle formatter instance.
82 """
83 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
84 rows = [["a.py", "1", "-", "X", "Some issue", ""]]
86 result = github_style.format(columns, rows, tool_name="tool")
88 assert_that(result).starts_with("::warning ")
91def test_github_style_escapes_special_characters(github_style: GitHubStyle) -> None:
92 """GitHubStyle escapes %, CR, and LF in messages.
94 Args:
95 github_style: The GitHubStyle formatter instance.
96 """
97 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
98 rows = [["a.py", "1", "-", "X", "100% done\r\nNext", "error"]]
100 result = github_style.format(columns, rows, tool_name="tool")
102 assert_that(result).contains("100%25 done%0D%0ANext")
103 assert_that(result).does_not_contain("\r")
104 assert_that(result).does_not_contain("\n")
107def test_github_style_empty_rows_returns_empty_string(
108 github_style: GitHubStyle,
109) -> None:
110 """GitHubStyle returns empty string for no rows.
112 Args:
113 github_style: The GitHubStyle formatter instance.
114 """
115 result = github_style.format(["File", "Message"], [], tool_name="tool")
117 assert_that(result).is_equal_to("")
120def test_github_style_multiple_rows(github_style: GitHubStyle) -> None:
121 """GitHubStyle emits one annotation per row.
123 Args:
124 github_style: The GitHubStyle formatter instance.
125 """
126 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
127 rows = [
128 ["a.py", "1", "-", "E1", "First", "error"],
129 ["b.py", "2", "3", "W1", "Second", "warning"],
130 ]
132 result = github_style.format(columns, rows, tool_name="tool")
133 annotation_lines = result.split("\n")
135 assert_that(annotation_lines).is_length(2)
136 assert_that(annotation_lines[0]).starts_with("::error ")
137 assert_that(annotation_lines[1]).starts_with("::warning ")
140def test_github_style_no_tool_name(github_style: GitHubStyle) -> None:
141 """GitHubStyle works without a tool_name (uses code as title).
143 Args:
144 github_style: The GitHubStyle formatter instance.
145 """
146 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
147 rows = [["a.py", "1", "-", "E501", "Too long", "error"]]
149 result = github_style.format(columns, rows, tool_name=None)
151 assert_that(result).contains("title=E501")
154def test_github_style_dash_line_column_omitted(github_style: GitHubStyle) -> None:
155 """GitHubStyle omits line and col when they are dashes.
157 Args:
158 github_style: The GitHubStyle formatter instance.
159 """
160 columns = ["File", "Line", "Column", "Code", "Message", "Severity"]
161 rows = [["a.py", "-", "-", "X", "Msg", "error"]]
163 result = github_style.format(columns, rows, tool_name="tool")
165 assert_that(result).does_not_contain("line=")
166 assert_that(result).does_not_contain("col=")