Coverage for tests / scripts / test_merge_pr_comment.py: 100%

117 statements  

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

1"""Unit tests for merge_pr_comment utilities. 

2 

3Tests cover: 

4- Basic merge functionality 

5- History extraction and flattening 

6- Maximum history limit enforcement 

7- Timestamp extraction 

8""" 

9 

10from __future__ import annotations 

11 

12import sys 

13from pathlib import Path 

14 

15import pytest 

16from assertpy import assert_that 

17 

18# Add scripts directory to path for imports 

19sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts" / "utils")) 

20 

21from merge_pr_comment import ( 

22 MAX_HISTORY_RUNS, 

23 _extract_details_blocks, 

24 _extract_timestamp_from_details, 

25 merge_comment_bodies, 

26) 

27 

28# ============================================================================= 

29# Tests for _extract_details_blocks 

30# ============================================================================= 

31 

32 

33def test_extract_details_blocks_no_blocks() -> None: 

34 """Content without details blocks returns content and empty list.""" 

35 content = "Some content without details" 

36 

37 remaining, blocks = _extract_details_blocks(content) 

38 

39 assert_that(remaining).is_equal_to(content) 

40 assert_that(blocks).is_empty() 

41 

42 

43def test_extract_details_blocks_single_block() -> None: 

44 """Extract single details block from content.""" 

45 content = """Main content 

46 

47<details> 

48<summary>Previous run</summary> 

49 

50Historical content 

51</details> 

52 

53After details""" 

54 

55 remaining, blocks = _extract_details_blocks(content) 

56 

57 assert_that(remaining).contains("Main content") 

58 assert_that(remaining).contains("After details") 

59 assert_that(blocks).is_length(1) 

60 assert_that(blocks[0]).contains("Previous run") 

61 assert_that(blocks[0]).contains("Historical content") 

62 

63 

64def test_extract_details_blocks_multiple_blocks() -> None: 

65 """Extract multiple details blocks from content.""" 

66 content = """Main content 

67 

68<details> 

69<summary>Run #2</summary> 

70Content 2 

71</details> 

72 

73<details> 

74<summary>Run #1</summary> 

75Content 1 

76</details>""" 

77 

78 remaining, blocks = _extract_details_blocks(content) 

79 

80 assert_that(remaining).contains("Main content") 

81 assert_that(blocks).is_length(2) 

82 assert_that(blocks[0]).contains("Run #2") 

83 assert_that(blocks[1]).contains("Run #1") 

84 

85 

86def test_extract_details_blocks_no_newline_after_tag() -> None: 

87 """Extract details block when no newline follows opening tag.""" 

88 content = """Main content 

89 

90<details><summary>Previous run (2026-01-25)</summary> 

91 

92Inner content 

93</details> 

94 

95After details""" 

96 

97 remaining, blocks = _extract_details_blocks(content) 

98 

99 assert_that(remaining).contains("Main content") 

100 assert_that(remaining).contains("After details") 

101 assert_that(blocks).is_length(1) 

102 assert_that(blocks[0]).contains("Previous run") 

103 assert_that(blocks[0]).contains("Inner content") 

104 

105 

106def test_extract_details_blocks_preserves_non_history_blocks() -> None: 

107 """Non-history details blocks are preserved in remaining content.""" 

108 content = """Main content 

109 

110<details> 

111<summary>Click to expand</summary> 

112 

113User-created collapsible content 

114</details> 

115 

116<details> 

117<summary>Previous run (2026-01-25)</summary> 

118 

119Historical content 

120</details> 

121 

122After details""" 

123 

124 remaining, blocks = _extract_details_blocks(content) 

125 

126 # Non-history block should be in remaining content 

127 assert_that(remaining).contains("Click to expand") 

128 assert_that(remaining).contains("User-created collapsible content") 

129 # History block should be extracted 

130 assert_that(blocks).is_length(1) 

131 assert_that(blocks[0]).contains("Previous run") 

132 assert_that(blocks[0]).contains("Historical content") 

133 

134 

135# ============================================================================= 

136# Tests for _extract_timestamp_from_details 

137# ============================================================================= 

138 

139 

140@pytest.mark.parametrize( 

141 ("block", "expected"), 

142 [ 

143 pytest.param( 

144 "<details>\n<summary>Previous run (2026-01-25 19:00:00 UTC)</summary>", 

145 "2026-01-25 19:00:00 UTC", 

146 id="standard_format", 

147 ), 

148 pytest.param( 

149 "<details>\n<summary>📜 Run #2 (2026-01-25 18:00:00 UTC)</summary>", 

150 "2026-01-25 18:00:00 UTC", 

151 id="run_number_format", 

152 ), 

153 ], 

154) 

155def test_extract_timestamp_from_details_valid(block: str, expected: str) -> None: 

156 """Extract timestamp from details block with valid timestamp.""" 

157 result = _extract_timestamp_from_details(block) 

158 

159 assert_that(result).is_equal_to(expected) 

160 

161 

162def test_extract_timestamp_from_details_no_timestamp() -> None: 

163 """Return None when no timestamp is found.""" 

164 block = "<details>\n<summary>Some content without timestamp</summary>" 

165 

166 result = _extract_timestamp_from_details(block) 

167 

168 assert_that(result).is_none() 

169 

170 

171# ============================================================================= 

172# Tests for merge_comment_bodies - basic merge 

173# ============================================================================= 

174 

175 

176def test_merge_no_previous_body() -> None: 

177 """First run creates simple merged body with marker.""" 

178 marker = "<!-- lintro-report -->" 

179 new_body = "## Results\nAll good!" 

180 

181 result = merge_comment_bodies( 

182 marker=marker, 

183 previous_body=None, 

184 new_body=new_body, 

185 ) 

186 

187 assert_that(result).starts_with(marker) 

188 assert_that(result).contains("All good!") 

189 assert_that(result).does_not_contain("<details>") 

190 

191 

192def test_merge_second_run_creates_history() -> None: 

193 """Second run wraps previous content in collapsed section.""" 

194 marker = "<!-- lintro-report -->" 

195 previous = "<!-- lintro-report -->\n\n## First Run\nFirst results" 

196 new_body = "## Second Run\nNew results" 

197 

198 result = merge_comment_bodies( 

199 marker=marker, 

200 previous_body=previous, 

201 new_body=new_body, 

202 ) 

203 

204 assert_that(result).starts_with(marker) 

205 assert_that(result).contains("## Second Run") 

206 assert_that(result).contains("<details>") 

207 assert_that(result).contains("First results") 

208 assert_that(result).contains("Previous run") 

209 

210 

211# ============================================================================= 

212# Tests for merge_comment_bodies - content placement 

213# ============================================================================= 

214 

215 

216def test_merge_new_content_above_history() -> None: 

217 """New content appears above historical sections by default.""" 

218 marker = "<!-- test -->" 

219 previous = "<!-- test -->\n\nOld content" 

220 new_body = "New content" 

221 

222 result = merge_comment_bodies( 

223 marker=marker, 

224 previous_body=previous, 

225 new_body=new_body, 

226 ) 

227 

228 new_pos = result.find("New content") 

229 details_pos = result.find("<details>") 

230 

231 assert_that(new_pos).is_less_than(details_pos) 

232 

233 

234def test_merge_place_new_below() -> None: 

235 """New content can be placed below history when specified.""" 

236 marker = "<!-- test -->" 

237 previous = "<!-- test -->\n\nOld content" 

238 new_body = "New content" 

239 

240 result = merge_comment_bodies( 

241 marker=marker, 

242 previous_body=previous, 

243 new_body=new_body, 

244 place_new_above=False, 

245 ) 

246 

247 new_pos = result.find("New content") 

248 details_pos = result.find("<details>") 

249 

250 assert_that(details_pos).is_less_than(new_pos) 

251 

252 

253# ============================================================================= 

254# Tests for merge_comment_bodies - history management 

255# ============================================================================= 

256 

257 

258def test_merge_preserves_existing_history_blocks() -> None: 

259 """Multiple runs preserve all historical sections in flat structure.""" 

260 marker = "<!-- test -->" 

261 previous = """<!-- test --> 

262 

263## Run 2 

264Current content 

265 

266<details> 

267<summary>📜 Previous run (2026-01-25 18:00:00 UTC)</summary> 

268 

269## Run 1 

270First content 

271</details>""" 

272 

273 new_body = "## Run 3\nLatest content" 

274 

275 result = merge_comment_bodies( 

276 marker=marker, 

277 previous_body=previous, 

278 new_body=new_body, 

279 ) 

280 

281 assert_that(result).contains("## Run 3") 

282 # Should have 2 separate details blocks (not nested) 

283 details_count = result.count("<details>") 

284 assert_that(details_count).is_equal_to(2) 

285 assert_that(result).contains("Run 2") 

286 assert_that(result).contains("Run 1") 

287 

288 

289def test_merge_history_limit_enforced() -> None: 

290 """History is limited to MAX_HISTORY_RUNS entries.""" 

291 marker = "<!-- test -->" 

292 

293 # Create previous body with MAX_HISTORY_RUNS history blocks 

294 # Use "Run #N" format to match the history pattern 

295 history_blocks = "\n\n".join( 

296 f"<details>\n<summary>Run #{i} (2026-01-25 0{i}:00:00 UTC)</summary>\nContent {i}\n</details>" 

297 for i in range(MAX_HISTORY_RUNS) 

298 ) 

299 previous = f"<!-- test -->\n\nCurrent content\n\n{history_blocks}" 

300 new_body = "Latest content" 

301 

302 result = merge_comment_bodies( 

303 marker=marker, 

304 previous_body=previous, 

305 new_body=new_body, 

306 ) 

307 

308 # Previous current becomes history, oldest history is dropped 

309 details_count = result.count("<details>") 

310 assert_that(details_count).is_equal_to(MAX_HISTORY_RUNS) 

311 

312 

313# ============================================================================= 

314# Tests for merge_comment_bodies - marker handling 

315# ============================================================================= 

316 

317 

318def test_merge_marker_only_appears_once() -> None: 

319 """Marker appears exactly once at the top of merged body.""" 

320 marker = "<!-- lintro-report -->" 

321 # New body contains the marker (which should be removed) 

322 new_body = "<!-- lintro-report -->\n\n## Results" 

323 previous = "<!-- lintro-report -->\n\nOld results" 

324 

325 result = merge_comment_bodies( 

326 marker=marker, 

327 previous_body=previous, 

328 new_body=new_body, 

329 ) 

330 

331 marker_count = result.count(marker) 

332 assert_that(marker_count).is_equal_to(1) 

333 assert_that(result).starts_with(marker) 

334 

335 

336# ============================================================================= 

337# Tests for merge_comment_bodies - newline normalization 

338# ============================================================================= 

339 

340 

341def test_merge_normalizes_newlines() -> None: 

342 """Windows-style newlines are normalized to Unix-style.""" 

343 marker = "<!-- test -->" 

344 new_body = "Line 1\r\nLine 2\rLine 3" 

345 

346 result = merge_comment_bodies( 

347 marker=marker, 

348 previous_body=None, 

349 new_body=new_body, 

350 ) 

351 

352 assert_that(result).does_not_contain("\r\n") 

353 assert_that(result).does_not_contain("\r") 

354 assert_that(result).contains("Line 1\nLine 2\nLine 3")