Coverage for tests / unit / parsers / test_hadolint_parser.py: 100%

90 statements  

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

1"""Unit tests for hadolint parser.""" 

2 

3from __future__ import annotations 

4 

5import pytest 

6from assertpy import assert_that 

7 

8from lintro.parsers.hadolint.hadolint_parser import parse_hadolint_output 

9 

10 

11@pytest.mark.parametrize( 

12 "output", 

13 [ 

14 "", 

15 " \n \n ", 

16 ], 

17 ids=["empty", "whitespace_only"], 

18) 

19def test_parse_hadolint_output_returns_empty_for_no_content(output: str) -> None: 

20 """Parse empty or whitespace-only output returns empty list. 

21 

22 Args: 

23 output: The hadolint output to parse. 

24 """ 

25 result = parse_hadolint_output(output) 

26 assert_that(result).is_empty() 

27 

28 

29@pytest.mark.parametrize( 

30 "level,code,output_line", 

31 [ 

32 ("error", "DL3006", "Dockerfile:1 DL3006 error: Always tag the version"), 

33 ("warning", "DL3009", "Dockerfile:3 DL3009 warning: Delete apt-get lists"), 

34 ("info", "DL3015", "Dockerfile:5 DL3015 info: Avoid additional packages"), 

35 ("style", "DL3000", "Dockerfile:10 DL3000 style: Use absolute WORKDIR"), 

36 ], 

37) 

38def test_parse_hadolint_output_severity_levels( 

39 level: str, 

40 code: str, 

41 output_line: str, 

42) -> None: 

43 """Parse issues with different severity levels. 

44 

45 Args: 

46 level: The expected severity level. 

47 code: The expected error code. 

48 output_line: The hadolint output line to parse. 

49 """ 

50 result = parse_hadolint_output(output_line) 

51 assert_that(result).is_length(1) 

52 assert_that(result[0].level).is_equal_to(level) 

53 assert_that(result[0].code).is_equal_to(code) 

54 

55 

56def test_parse_hadolint_output_extracts_all_fields() -> None: 

57 """Parse error-level issue extracts all fields correctly.""" 

58 output = "Dockerfile:1 DL3006 error: Always tag the version of an image explicitly" 

59 result = parse_hadolint_output(output) 

60 assert_that(result).is_length(1) 

61 assert_that(result[0].file).is_equal_to("Dockerfile") 

62 assert_that(result[0].line).is_equal_to(1) 

63 assert_that(result[0].code).is_equal_to("DL3006") 

64 assert_that(result[0].level).is_equal_to("error") 

65 assert_that(result[0].message).is_equal_to( 

66 "Always tag the version of an image explicitly", 

67 ) 

68 

69 

70def test_parse_hadolint_output_multiple_issues() -> None: 

71 """Parse multiple issues.""" 

72 output = """Dockerfile:1 DL3006 error: Always tag the version 

73Dockerfile:3 DL3009 warning: Delete apt-get lists 

74Dockerfile:5 DL3015 info: Avoid additional packages""" 

75 result = parse_hadolint_output(output) 

76 assert_that(result).is_length(3) 

77 assert_that(result[0].line).is_equal_to(1) 

78 assert_that(result[1].line).is_equal_to(3) 

79 assert_that(result[2].line).is_equal_to(5) 

80 

81 

82def test_parse_hadolint_output_non_matching_lines_ignored() -> None: 

83 """Non-matching lines are ignored.""" 

84 output = """Some header text 

85Dockerfile:1 DL3006 error: Tag the version 

86Other random output""" 

87 result = parse_hadolint_output(output) 

88 assert_that(result).is_length(1) 

89 

90 

91def test_parse_hadolint_output_column_is_zero() -> None: 

92 """Column is always 0 (hadolint doesn't provide it).""" 

93 output = "Dockerfile:1 DL3006 error: Some error" 

94 result = parse_hadolint_output(output) 

95 assert_that(result[0].column).is_equal_to(0) 

96 

97 

98def test_parse_hadolint_output_file_with_path() -> None: 

99 """Parse file with directory path.""" 

100 output = "docker/prod/Dockerfile:10 DL3006 error: Tag the version" 

101 result = parse_hadolint_output(output) 

102 assert_that(result[0].file).is_equal_to("docker/prod/Dockerfile") 

103 

104 

105def test_parse_hadolint_output_blank_lines_between_issues() -> None: 

106 """Handle blank lines between issues.""" 

107 output = """Dockerfile:1 DL3006 error: Error one 

108 

109Dockerfile:5 DL3009 warning: Warning one""" 

110 result = parse_hadolint_output(output) 

111 assert_that(result).is_length(2) 

112 

113 

114# ============================================================================= 

115# Edge case tests 

116# ============================================================================= 

117 

118 

119def test_parse_hadolint_output_unicode_in_message() -> None: 

120 """Handle Unicode characters in error messages.""" 

121 output = "Dockerfile:1 DL3006 error: Não use versões implícitas" 

122 result = parse_hadolint_output(output) 

123 assert_that(result).is_length(1) 

124 assert_that(result[0].message).contains("Não") 

125 

126 

127def test_parse_hadolint_output_file_path_with_spaces() -> None: 

128 """Handle file paths with spaces (if quoted by tool).""" 

129 output = "my project/Dockerfile:1 DL3006 error: Tag version" 

130 result = parse_hadolint_output(output) 

131 assert_that(result).is_length(1) 

132 assert_that(result[0].file).contains("my project") 

133 

134 

135def test_parse_hadolint_output_very_long_message() -> None: 

136 """Handle extremely long error messages.""" 

137 long_text = "x" * 5000 

138 output = f"Dockerfile:1 DL3006 error: {long_text}" 

139 result = parse_hadolint_output(output) 

140 assert_that(result).is_length(1) 

141 assert_that(len(result[0].message)).is_equal_to(5000) 

142 

143 

144def test_parse_hadolint_output_very_large_line_number() -> None: 

145 """Handle very large line numbers.""" 

146 output = "Dockerfile:999999 DL3006 error: Error on large line" 

147 result = parse_hadolint_output(output) 

148 assert_that(result).is_length(1) 

149 assert_that(result[0].line).is_equal_to(999999) 

150 

151 

152def test_parse_hadolint_output_special_chars_in_message() -> None: 

153 """Handle special characters in error messages.""" 

154 output = 'Dockerfile:1 DL3006 error: Use "quotes" and <brackets>' 

155 result = parse_hadolint_output(output) 

156 assert_that(result).is_length(1) 

157 assert_that(result[0].message).contains("quotes") 

158 assert_that(result[0].message).contains("<brackets>") 

159 

160 

161def test_parse_hadolint_output_colon_in_message() -> None: 

162 """Handle colons in error messages (common in Docker contexts).""" 

163 output = "Dockerfile:1 DL3006 error: FROM should use image:tag format" 

164 result = parse_hadolint_output(output) 

165 assert_that(result).is_length(1) 

166 assert_that(result[0].message).contains("image:tag") 

167 

168 

169def test_parse_hadolint_output_deeply_nested_path() -> None: 

170 """Handle deeply nested file paths.""" 

171 deep_path = "a/b/c/d/e/f/g/h/i/j/Dockerfile" 

172 output = f"{deep_path}:1 DL3006 error: Tag version" 

173 result = parse_hadolint_output(output) 

174 assert_that(result).is_length(1) 

175 assert_that(result[0].file).is_equal_to(deep_path) 

176 

177 

178def test_parse_hadolint_output_ansi_codes_stripped() -> None: 

179 """Strip ANSI escape codes from output for consistent CI/local parsing.""" 

180 # Output with ANSI color codes (common in CI environments) 

181 output = "\x1b[31mDockerfile:1 DL3006 error: Always tag the version\x1b[0m" 

182 result = parse_hadolint_output(output) 

183 assert_that(result).is_length(1) 

184 assert_that(result[0].file).is_equal_to("Dockerfile") 

185 assert_that(result[0].code).is_equal_to("DL3006")