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

1"""Unit tests for GitHubStyle formatter. 

2 

3Tests verify GitHubStyle emits correct GitHub Actions annotation commands 

4with proper severity mapping, escaping, and edge-case handling. 

5""" 

6 

7from __future__ import annotations 

8 

9from assertpy import assert_that 

10 

11from lintro.formatters.styles.github import GitHubStyle 

12 

13 

14def test_github_style_single_error_annotation(github_style: GitHubStyle) -> None: 

15 """GitHubStyle emits an ::error annotation for error severity. 

16 

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"]] 

22 

23 result = github_style.format(columns, rows, tool_name="ruff") 

24 

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") 

31 

32 

33def test_github_style_warning_severity(github_style: GitHubStyle) -> None: 

34 """GitHubStyle maps warning severity to ::warning. 

35 

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"]] 

41 

42 result = github_style.format(columns, rows, tool_name="tool") 

43 

44 assert_that(result).starts_with("::warning ") 

45 

46 

47def test_github_style_info_severity_maps_to_notice(github_style: GitHubStyle) -> None: 

48 """GitHubStyle maps info severity to ::notice. 

49 

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"]] 

55 

56 result = github_style.format(columns, rows, tool_name="tool") 

57 

58 assert_that(result).starts_with("::notice ") 

59 

60 

61def test_github_style_alias_severity(github_style: GitHubStyle) -> None: 

62 """GitHubStyle normalizes alias severities (e.g. HIGH → error). 

63 

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"]] 

69 

70 result = github_style.format(columns, rows, tool_name="bandit") 

71 

72 assert_that(result).starts_with("::error ") 

73 

74 

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. 

79 

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", ""]] 

85 

86 result = github_style.format(columns, rows, tool_name="tool") 

87 

88 assert_that(result).starts_with("::warning ") 

89 

90 

91def test_github_style_escapes_special_characters(github_style: GitHubStyle) -> None: 

92 """GitHubStyle escapes %, CR, and LF in messages. 

93 

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"]] 

99 

100 result = github_style.format(columns, rows, tool_name="tool") 

101 

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") 

105 

106 

107def test_github_style_empty_rows_returns_empty_string( 

108 github_style: GitHubStyle, 

109) -> None: 

110 """GitHubStyle returns empty string for no rows. 

111 

112 Args: 

113 github_style: The GitHubStyle formatter instance. 

114 """ 

115 result = github_style.format(["File", "Message"], [], tool_name="tool") 

116 

117 assert_that(result).is_equal_to("") 

118 

119 

120def test_github_style_multiple_rows(github_style: GitHubStyle) -> None: 

121 """GitHubStyle emits one annotation per row. 

122 

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 ] 

131 

132 result = github_style.format(columns, rows, tool_name="tool") 

133 annotation_lines = result.split("\n") 

134 

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 ") 

138 

139 

140def test_github_style_no_tool_name(github_style: GitHubStyle) -> None: 

141 """GitHubStyle works without a tool_name (uses code as title). 

142 

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"]] 

148 

149 result = github_style.format(columns, rows, tool_name=None) 

150 

151 assert_that(result).contains("title=E501") 

152 

153 

154def test_github_style_dash_line_column_omitted(github_style: GitHubStyle) -> None: 

155 """GitHubStyle omits line and col when they are dashes. 

156 

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"]] 

162 

163 result = github_style.format(columns, rows, tool_name="tool") 

164 

165 assert_that(result).does_not_contain("line=") 

166 assert_that(result).does_not_contain("col=")