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
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for merge_pr_comment utilities.
3Tests cover:
4- Basic merge functionality
5- History extraction and flattening
6- Maximum history limit enforcement
7- Timestamp extraction
8"""
10from __future__ import annotations
12import sys
13from pathlib import Path
15import pytest
16from assertpy import assert_that
18# Add scripts directory to path for imports
19sys.path.insert(0, str(Path(__file__).parent.parent.parent / "scripts" / "utils"))
21from merge_pr_comment import (
22 MAX_HISTORY_RUNS,
23 _extract_details_blocks,
24 _extract_timestamp_from_details,
25 merge_comment_bodies,
26)
28# =============================================================================
29# Tests for _extract_details_blocks
30# =============================================================================
33def test_extract_details_blocks_no_blocks() -> None:
34 """Content without details blocks returns content and empty list."""
35 content = "Some content without details"
37 remaining, blocks = _extract_details_blocks(content)
39 assert_that(remaining).is_equal_to(content)
40 assert_that(blocks).is_empty()
43def test_extract_details_blocks_single_block() -> None:
44 """Extract single details block from content."""
45 content = """Main content
47<details>
48<summary>Previous run</summary>
50Historical content
51</details>
53After details"""
55 remaining, blocks = _extract_details_blocks(content)
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")
64def test_extract_details_blocks_multiple_blocks() -> None:
65 """Extract multiple details blocks from content."""
66 content = """Main content
68<details>
69<summary>Run #2</summary>
70Content 2
71</details>
73<details>
74<summary>Run #1</summary>
75Content 1
76</details>"""
78 remaining, blocks = _extract_details_blocks(content)
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")
86def test_extract_details_blocks_no_newline_after_tag() -> None:
87 """Extract details block when no newline follows opening tag."""
88 content = """Main content
90<details><summary>Previous run (2026-01-25)</summary>
92Inner content
93</details>
95After details"""
97 remaining, blocks = _extract_details_blocks(content)
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")
106def test_extract_details_blocks_preserves_non_history_blocks() -> None:
107 """Non-history details blocks are preserved in remaining content."""
108 content = """Main content
110<details>
111<summary>Click to expand</summary>
113User-created collapsible content
114</details>
116<details>
117<summary>Previous run (2026-01-25)</summary>
119Historical content
120</details>
122After details"""
124 remaining, blocks = _extract_details_blocks(content)
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")
135# =============================================================================
136# Tests for _extract_timestamp_from_details
137# =============================================================================
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)
159 assert_that(result).is_equal_to(expected)
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>"
166 result = _extract_timestamp_from_details(block)
168 assert_that(result).is_none()
171# =============================================================================
172# Tests for merge_comment_bodies - basic merge
173# =============================================================================
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!"
181 result = merge_comment_bodies(
182 marker=marker,
183 previous_body=None,
184 new_body=new_body,
185 )
187 assert_that(result).starts_with(marker)
188 assert_that(result).contains("All good!")
189 assert_that(result).does_not_contain("<details>")
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"
198 result = merge_comment_bodies(
199 marker=marker,
200 previous_body=previous,
201 new_body=new_body,
202 )
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")
211# =============================================================================
212# Tests for merge_comment_bodies - content placement
213# =============================================================================
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"
222 result = merge_comment_bodies(
223 marker=marker,
224 previous_body=previous,
225 new_body=new_body,
226 )
228 new_pos = result.find("New content")
229 details_pos = result.find("<details>")
231 assert_that(new_pos).is_less_than(details_pos)
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"
240 result = merge_comment_bodies(
241 marker=marker,
242 previous_body=previous,
243 new_body=new_body,
244 place_new_above=False,
245 )
247 new_pos = result.find("New content")
248 details_pos = result.find("<details>")
250 assert_that(details_pos).is_less_than(new_pos)
253# =============================================================================
254# Tests for merge_comment_bodies - history management
255# =============================================================================
258def test_merge_preserves_existing_history_blocks() -> None:
259 """Multiple runs preserve all historical sections in flat structure."""
260 marker = "<!-- test -->"
261 previous = """<!-- test -->
263## Run 2
264Current content
266<details>
267<summary>📜 Previous run (2026-01-25 18:00:00 UTC)</summary>
269## Run 1
270First content
271</details>"""
273 new_body = "## Run 3\nLatest content"
275 result = merge_comment_bodies(
276 marker=marker,
277 previous_body=previous,
278 new_body=new_body,
279 )
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")
289def test_merge_history_limit_enforced() -> None:
290 """History is limited to MAX_HISTORY_RUNS entries."""
291 marker = "<!-- test -->"
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"
302 result = merge_comment_bodies(
303 marker=marker,
304 previous_body=previous,
305 new_body=new_body,
306 )
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)
313# =============================================================================
314# Tests for merge_comment_bodies - marker handling
315# =============================================================================
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"
325 result = merge_comment_bodies(
326 marker=marker,
327 previous_body=previous,
328 new_body=new_body,
329 )
331 marker_count = result.count(marker)
332 assert_that(marker_count).is_equal_to(1)
333 assert_that(result).starts_with(marker)
336# =============================================================================
337# Tests for merge_comment_bodies - newline normalization
338# =============================================================================
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"
346 result = merge_comment_bodies(
347 marker=marker,
348 previous_body=None,
349 new_body=new_body,
350 )
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")