Skip to content

Commit 34b732f

Browse files
committed
fix: update existing PR comments to add all-clear message
Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
1 parent bd031a3 commit 34b732f

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

socket_basics/core/notification/github_pr_notifier.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def notify(self, facts: Dict[str, Any]) -> None:
5959
pr_number = self._get_pr_number()
6060
if pr_number:
6161
self._reconcile_pr_labels(pr_number, [])
62+
self._replace_existing_sections_with_all_clear(pr_number)
6263
else:
6364
logger.warning('GithubPRNotifier: unable to determine PR number for label reconciliation')
6465
logger.info('GithubPRNotifier: no notifications present; skipping comments')
@@ -288,6 +289,25 @@ def _extract_section_markers(self, content: str) -> Optional[Dict[str, str]]:
288289

289290
return None
290291

292+
def _extract_all_section_types(self, comment_body: str) -> List[str]:
293+
"""Extract all managed section markers from a comment body."""
294+
import re
295+
296+
pattern = r'<!-- ([a-zA-Z0-9\-_]+) start -->'
297+
return re.findall(pattern, comment_body or '')
298+
299+
def _extract_section_title(self, section_content: str) -> str:
300+
"""Extract the display title from a wrapped PR comment section."""
301+
import re
302+
303+
for line in (section_content or '').splitlines():
304+
stripped = line.strip()
305+
if stripped.startswith('## '):
306+
title = stripped[3:].strip()
307+
title = re.sub(r'<img[^>]+>\s*', '', title).strip()
308+
return title or 'Socket Security'
309+
return 'Socket Security'
310+
291311
def _find_comment_with_section(self, comments: List[Dict[str, Any]], section_type: str) -> Optional[Dict[str, Any]]:
292312
"""Find an existing comment that contains the given section type."""
293313
import re
@@ -313,6 +333,49 @@ def _update_section_in_comment(self, comment_body: str, section_type: str, new_s
313333

314334
return updated_body
315335

336+
def _build_all_clear_section(self, section_type: str, existing_section_content: str) -> str:
337+
"""Build an all-clear replacement for an existing managed section."""
338+
from socket_basics.core.notification import github_pr_helpers as helpers
339+
340+
title = self._extract_section_title(existing_section_content)
341+
body = "✅ Socket Basics found no active findings in the latest run."
342+
return helpers.wrap_pr_comment_section(section_type, title, body, self.full_scan_url)
343+
344+
def _replace_existing_sections_with_all_clear(self, pr_number: int) -> None:
345+
"""Rewrite existing managed PR comment sections to an all-clear state."""
346+
existing_comments = self._get_pr_comments(pr_number)
347+
for comment in existing_comments:
348+
original_body = comment.get('body', '')
349+
if not original_body:
350+
continue
351+
352+
updated_body = original_body
353+
changed = False
354+
for section_type in self._extract_all_section_types(original_body):
355+
section_match = self._extract_section_markers(updated_body)
356+
if not section_match or section_match.get('type') != section_type:
357+
import re
358+
pattern = rf'<!-- {re.escape(section_type)} start -->.*?<!-- {re.escape(section_type)} end -->'
359+
match = re.search(pattern, updated_body, re.DOTALL)
360+
if not match:
361+
continue
362+
section_content = match.group(0)
363+
else:
364+
section_content = section_match['content']
365+
366+
all_clear_section = self._build_all_clear_section(section_type, section_content)
367+
next_body = self._update_section_in_comment(updated_body, section_type, all_clear_section)
368+
if next_body != updated_body:
369+
updated_body = next_body
370+
changed = True
371+
372+
if changed:
373+
success = self._update_comment(pr_number, comment['id'], updated_body)
374+
if success:
375+
logger.info('GithubPRNotifier: updated existing comment %s to all-clear state', comment['id'])
376+
else:
377+
logger.error('GithubPRNotifier: failed to update comment %s to all-clear state', comment['id'])
378+
316379
def _truncate_comment_if_needed(self, comment_body: str, full_scan_url: Optional[str] = None) -> str:
317380
"""Truncate comment if it exceeds GitHub's character limit.
318381

tests/test_github_pr_notifier.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,35 @@ def test_notify_reconciles_labels_even_when_notifications_are_empty(monkeypatch)
104104
notifier.notify({'notifications': []})
105105

106106
assert reconciled == [(123, [])]
107+
108+
109+
def test_notify_rewrites_existing_section_to_all_clear_when_notifications_are_empty(monkeypatch):
110+
notifier = GithubPRNotifier(
111+
{
112+
'repository': 'SocketDev/socket-basics',
113+
'pr_labels_enabled': True,
114+
}
115+
)
116+
117+
comment_body = """<!-- sast-javascript start -->
118+
## <img src="https://example.test/logo.png" width="24" height="24"> Socket SAST JavaScript
119+
120+
### Summary
121+
🟡 Medium: 1
122+
<!-- sast-javascript end -->"""
123+
updated_bodies: list[str] = []
124+
125+
monkeypatch.setattr(notifier, '_get_pr_number', lambda: 123)
126+
monkeypatch.setattr(notifier, '_reconcile_pr_labels', lambda pr_number, labels: True)
127+
monkeypatch.setattr(notifier, '_get_pr_comments', lambda pr_number: [{'id': 99, 'body': comment_body}])
128+
monkeypatch.setattr(
129+
notifier,
130+
'_update_comment',
131+
lambda pr_number, comment_id, body: updated_bodies.append(body) or True,
132+
)
133+
134+
notifier.notify({'notifications': []})
135+
136+
assert len(updated_bodies) == 1
137+
assert 'No active findings remain for this scanner in the latest run.' in updated_bodies[0]
138+
assert '<!-- sast-javascript start -->' in updated_bodies[0]

0 commit comments

Comments
 (0)