mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
str.splitlines() also splits on Unicode line separators (U+2028, U+2029) which are valid inside JSON string values without escaping. When an open GitHub issue title or body contained one of these characters, the duplicate-issue check script broke the gh output mid-string and crashed with 'Unterminated string ...' before the auto-close path could run. gh api --paginate for an array endpoint already concatenates pages into a single JSON array, so just parse the whole stdout once. Add a regression test covering U+2028/U+2029 and the existing PR-filter path. Co-authored-by: Krrish Dholakia <krrish-berri-2@users.noreply.github.com>
89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
"""Tests for the duplicate-issue close script.
|
|
|
|
Run with: python -m pytest .github/scripts/test_close_duplicate_issues.py
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import types
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
|
|
import close_duplicate_issues as mod
|
|
|
|
|
|
def _make_completed_process(stdout: str):
|
|
return types.SimpleNamespace(stdout=stdout, returncode=0, stderr="")
|
|
|
|
|
|
def test_fetch_open_issues_handles_unicode_line_separator():
|
|
"""Regression: U+2028 / U+2029 inside JSON strings must not break parsing.
|
|
|
|
Python's str.splitlines() treats these characters as line breaks, but they
|
|
are valid inside JSON string values without escaping, so any line-based
|
|
parsing of gh's compact JSON output would split mid-string.
|
|
"""
|
|
issues = [
|
|
{
|
|
"number": 1,
|
|
"title": "title with line\u2028separator",
|
|
"body": "paragraph\u2029separator and line\u2028break",
|
|
},
|
|
{"number": 2, "title": "plain", "body": "plain"},
|
|
]
|
|
raw = json.dumps(issues, ensure_ascii=False)
|
|
assert "\u2028" in raw and "\u2029" in raw
|
|
|
|
with patch.object(mod.subprocess, "run", return_value=_make_completed_process(raw)):
|
|
result = mod.fetch_open_issues("owner/repo")
|
|
|
|
assert [i["number"] for i in result] == [1, 2]
|
|
assert result[0]["title"] == "title with line\u2028separator"
|
|
|
|
|
|
def test_fetch_open_issues_filters_pull_requests():
|
|
payload = [
|
|
{"number": 1, "title": "real issue"},
|
|
{"number": 2, "title": "a PR", "pull_request": {"url": "..."}},
|
|
]
|
|
raw = json.dumps(payload)
|
|
|
|
with patch.object(mod.subprocess, "run", return_value=_make_completed_process(raw)):
|
|
result = mod.fetch_open_issues("owner/repo")
|
|
|
|
assert [i["number"] for i in result] == [1]
|
|
|
|
|
|
def test_fetch_open_issues_empty_response():
|
|
with patch.object(mod.subprocess, "run", return_value=_make_completed_process("")):
|
|
assert mod.fetch_open_issues("owner/repo") == []
|
|
|
|
with patch.object(
|
|
mod.subprocess, "run", return_value=_make_completed_process("[]")
|
|
):
|
|
assert mod.fetch_open_issues("owner/repo") == []
|
|
|
|
|
|
def test_find_duplicate_normalizes_prefixes_and_case():
|
|
target = {"number": 10, "title": "[Bug] Crash on startup"}
|
|
older = [
|
|
{"number": 1, "title": "feature request: add streaming"},
|
|
{"number": 2, "title": "crash on startup"},
|
|
]
|
|
dup = mod.find_duplicate(target, older, threshold=0.85)
|
|
assert dup is not None
|
|
assert dup["number"] == 2
|
|
|
|
|
|
def test_find_duplicate_returns_none_below_threshold():
|
|
target = {"number": 10, "title": "Streaming hangs on Anthropic"}
|
|
older = [{"number": 1, "title": "Completely unrelated topic"}]
|
|
assert mod.find_duplicate(target, older, threshold=0.85) is None
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(pytest.main([__file__, "-v"]))
|