@@ -124,6 +124,44 @@ def test_parse_timestamp_reports_invalid_values():
124124 module .parse_timestamp ("invalid" )
125125
126126
127+ def test_auto_close_request_json_reports_urlerror (monkeypatch ):
128+ module = load_module ("auto_close_duplicate_issues.py" )
129+
130+ monkeypatch .setattr (module , "github_headers" , lambda : {})
131+ monkeypatch .setattr (
132+ module .urllib .request ,
133+ "urlopen" ,
134+ lambda * args , ** kwargs : (_ for _ in ()).throw (
135+ module .urllib .error .URLError ("boom" )
136+ ),
137+ )
138+
139+ with pytest .raises (RuntimeError , match = "GET /test failed" ):
140+ module .request_json ("/test" )
141+
142+
143+ def test_auto_close_request_json_reports_invalid_json (monkeypatch ):
144+ module = load_module ("auto_close_duplicate_issues.py" )
145+ monkeypatch .setattr (module , "github_headers" , lambda : {})
146+
147+ class DummyResponse :
148+ def __enter__ (self ):
149+ return self
150+
151+ def __exit__ (self , exc_type , exc , tb ):
152+ return False
153+
154+ def read (self ):
155+ return b"not-json"
156+
157+ monkeypatch .setattr (
158+ module .urllib .request , "urlopen" , lambda * args , ** kwargs : DummyResponse ()
159+ )
160+
161+ with pytest .raises (RuntimeError , match = "Failed to parse JSON from /test" ):
162+ module .request_json ("/test" )
163+
164+
127165def test_has_reaction_from_user_ignores_missing_user_ids ():
128166 module = load_module ("auto_close_duplicate_issues.py" )
129167 reactions = [
@@ -382,6 +420,66 @@ def test_auto_close_main_closes_old_duplicate(monkeypatch, capsys):
382420 assert closed == [("OpenHands/agent-sdk" , 123 , 45 , False )]
383421
384422
423+ def test_auto_close_main_skips_malformed_issue_data (monkeypatch , capsys ):
424+ module = load_module ("auto_close_duplicate_issues.py" )
425+
426+ monkeypatch .setattr (
427+ module ,
428+ "parse_args" ,
429+ lambda : argparse .Namespace (
430+ repository = "OpenHands/agent-sdk" , close_after_days = 3 , dry_run = False
431+ ),
432+ )
433+ monkeypatch .setattr (
434+ module , "list_open_issues" , lambda repository : [{"number" : 123 }]
435+ )
436+
437+ assert module .main () == 0
438+
439+ summary = json .loads (capsys .readouterr ().out )
440+ assert summary == {"repository" : "OpenHands/agent-sdk" , "results" : []}
441+
442+
443+ def test_auto_close_main_skips_malformed_duplicate_comment (monkeypatch , capsys ):
444+ module = load_module ("auto_close_duplicate_issues.py" )
445+ now = datetime .now (UTC )
446+ old_timestamp = iso_timestamp (now - timedelta (days = 5 ))
447+ issue = {
448+ "number" : 123 ,
449+ "created_at" : old_timestamp ,
450+ "labels" : [{"name" : module .DUPLICATE_CANDIDATE_LABEL }],
451+ "user" : {"id" : 7 },
452+ }
453+ comments = [
454+ {
455+ "body" : "<!-- openhands-duplicate-check canonical=45 auto-close=true -->" ,
456+ "created_at" : old_timestamp ,
457+ }
458+ ]
459+
460+ monkeypatch .setattr (
461+ module ,
462+ "parse_args" ,
463+ lambda : argparse .Namespace (
464+ repository = "OpenHands/agent-sdk" , close_after_days = 3 , dry_run = False
465+ ),
466+ )
467+ monkeypatch .setattr (module , "list_open_issues" , lambda repository : [issue ])
468+ monkeypatch .setattr (
469+ module , "list_issue_comments" , lambda repository , number : comments
470+ )
471+ monkeypatch .setattr (
472+ module ,
473+ "close_issue_as_duplicate" ,
474+ lambda * args , ** kwargs : pytest .fail ("close_issue_as_duplicate should not run" ),
475+ )
476+
477+ assert module .main () == 0
478+
479+ summary = json .loads (capsys .readouterr ().out )
480+ assert summary == {"repository" : "OpenHands/agent-sdk" , "results" : []}
481+
482+
385483def test_auto_close_main_removes_label_when_newer_comment_exists (monkeypatch , capsys ):
386484 module = load_module ("auto_close_duplicate_issues.py" )
387485 now = datetime .now (UTC )
@@ -500,6 +598,21 @@ def test_normalize_result_promotes_actionable_duplicates():
500598 assert normalized ["summary" ] == "duplicate summary"
501599
502600
601+ def test_issue_duplicate_request_json_reports_urlerror (monkeypatch ):
602+ module = load_module ("issue_duplicate_check_openhands.py" )
603+
604+ monkeypatch .setattr (
605+ module .urllib .request ,
606+ "urlopen" ,
607+ lambda * args , ** kwargs : (_ for _ in ()).throw (
608+ module .urllib .error .URLError ("boom" )
609+ ),
610+ )
611+
612+ with pytest .raises (RuntimeError , match = "GET https://example.test/path failed" ):
613+ module .request_json ("https://example.test" , "/path" )
614+
615+
503616def test_normalize_result_lowercases_classification ():
504617 module = load_module ("issue_duplicate_check_openhands.py" )
505618 normalized = module .normalize_result (
0 commit comments