@@ -3,6 +3,7 @@ package watcher
33import (
44 "context"
55 "fmt"
6+ "path/filepath"
67 "testing"
78 "time"
89
@@ -44,13 +45,53 @@ func (m *mockGitHubClient) GetCombinedStatus(owner, repo, ref string) (*github.C
4445// mockNotifier implements Notifier for testing.
4546type mockNotifier struct {
4647 events []* notify.StatusChangeEvent
48+ err error
4749}
4850
4951func (m * mockNotifier ) Notify (event * notify.StatusChangeEvent ) error {
52+ if m .err != nil {
53+ return m .err
54+ }
5055 m .events = append (m .events , event )
5156 return nil
5257}
5358
59+ func TestWatcherNotificationError (t * testing.T ) {
60+ pr := & github.PullRequest {
61+ Number : 1 ,
62+ Title : "Test PR" ,
63+ }
64+ pr .Head .SHA = "sha123"
65+
66+ client := & mockGitHubClient {
67+ prs : map [string ]* github.PullRequest {
68+ "owner/repo/1" : pr ,
69+ },
70+ statuses : map [string ]* github.CombinedStatus {
71+ "sha123" : {State : "success" , SHA : "sha123" },
72+ },
73+ }
74+
75+ cfg := & config.Config {
76+ WatchedPRs : []config.WatchedPR {
77+ {
78+ Owner : "owner" ,
79+ Repo : "repo" ,
80+ Number : 1 ,
81+ LastKnownState : "pending" ,
82+ },
83+ },
84+ }
85+
86+ notifier := & mockNotifier {err : fmt .Errorf ("notification failed" )}
87+ w := New (client , cfg , notifier )
88+
89+ // Should not return error, just print warning
90+ if err := w .checkPR (& cfg .WatchedPRs [0 ]); err != nil {
91+ t .Errorf ("checkPR should not fail when notification fails: %v" , err )
92+ }
93+ }
94+
5495func TestWatcherNoStatusChange (t * testing.T ) {
5596 pr := & github.PullRequest {
5697 Number : 1 ,
@@ -300,3 +341,136 @@ func TestWatcherUpdateTitle(t *testing.T) {
300341 t .Errorf ("expected title to be updated, got %q" , cfg .WatchedPRs [0 ].Title )
301342 }
302343}
344+
345+ func TestWatcherStatusError (t * testing.T ) {
346+ pr := & github.PullRequest {
347+ Number : 1 ,
348+ Title : "Test PR" ,
349+ }
350+ pr .Head .SHA = "sha123"
351+
352+ client := & mockGitHubClient {
353+ prs : map [string ]* github.PullRequest {
354+ "owner/repo/1" : pr ,
355+ },
356+ statuses : map [string ]* github.CombinedStatus {},
357+ err : fmt .Errorf ("status fetch failed" ),
358+ }
359+
360+ cfg := & config.Config {
361+ WatchedPRs : []config.WatchedPR {
362+ {
363+ Owner : "owner" ,
364+ Repo : "repo" ,
365+ Number : 1 ,
366+ LastKnownState : "success" ,
367+ },
368+ },
369+ }
370+
371+ notifier := & mockNotifier {}
372+ w := New (client , cfg , notifier )
373+
374+ err := w .checkPR (& cfg .WatchedPRs [0 ])
375+ if err == nil {
376+ t .Error ("expected error when status fetch fails" )
377+ }
378+ }
379+
380+ func TestWatcherCheckAllPRs (t * testing.T ) {
381+ pr1 := & github.PullRequest {Number : 1 , Title : "PR1" }
382+ pr1 .Head .SHA = "sha1"
383+ pr2 := & github.PullRequest {Number : 2 , Title : "PR2" }
384+ pr2 .Head .SHA = "sha2"
385+
386+ client := & mockGitHubClient {
387+ prs : map [string ]* github.PullRequest {
388+ "owner/repo/1" : pr1 ,
389+ "owner/repo/2" : pr2 ,
390+ },
391+ statuses : map [string ]* github.CombinedStatus {
392+ "sha1" : {State : "success" , SHA : "sha1" },
393+ "sha2" : {State : "failure" , SHA : "sha2" },
394+ },
395+ }
396+
397+ cfg := & config.Config {
398+ PollIntervalSeconds : 1 ,
399+ WatchedPRs : []config.WatchedPR {
400+ {Owner : "owner" , Repo : "repo" , Number : 1 , LastKnownState : "pending" },
401+ {Owner : "owner" , Repo : "repo" , Number : 2 , LastKnownState : "pending" },
402+ },
403+ }
404+
405+ notifier := & mockNotifier {}
406+ w := New (client , cfg , notifier )
407+
408+ w .checkAllPRs ()
409+
410+ // Both PRs should be notified
411+ if len (notifier .events ) != 2 {
412+ t .Errorf ("expected 2 notifications, got %d" , len (notifier .events ))
413+ }
414+ }
415+
416+ func TestWatcherCheckAllPRsWithError (t * testing.T ) {
417+ // Create a client that will fail for one PR
418+ pr1 := & github.PullRequest {Number : 1 , Title : "PR1" }
419+ pr1 .Head .SHA = "sha1"
420+
421+ client := & mockGitHubClient {
422+ prs : map [string ]* github.PullRequest {
423+ "owner/repo/1" : pr1 ,
424+ },
425+ statuses : map [string ]* github.CombinedStatus {
426+ "sha1" : {State : "success" , SHA : "sha1" },
427+ },
428+ }
429+
430+ tmpDir := t .TempDir ()
431+ configPath := filepath .Join (tmpDir , "nonexistent" , "deep" , "path" , "config.json" )
432+
433+ oldConfigPath := config .ConfigPath
434+ defer func () { config .ConfigPath = oldConfigPath }()
435+ config .ConfigPath = func () (string , error ) {
436+ return configPath , nil
437+ }
438+
439+ cfg := & config.Config {
440+ PollIntervalSeconds : 1 ,
441+ WatchedPRs : []config.WatchedPR {
442+ {Owner : "owner" , Repo : "repo" , Number : 1 , LastKnownState : "pending" },
443+ {Owner : "badowner" , Repo : "badrepo" , Number : 999 , LastKnownState : "pending" },
444+ },
445+ }
446+
447+ notifier := & mockNotifier {}
448+ w := New (client , cfg , notifier )
449+
450+ // This should handle errors gracefully and print warnings
451+ w .checkAllPRs ()
452+
453+ // Only one PR should be successfully checked
454+ if len (notifier .events ) != 1 {
455+ t .Errorf ("expected 1 successful notification, got %d" , len (notifier .events ))
456+ }
457+ }
458+
459+ func TestWatcherNoPRs (t * testing.T ) {
460+ client := & mockGitHubClient {}
461+ cfg := & config.Config {
462+ PollIntervalSeconds : 1 ,
463+ WatchedPRs : []config.WatchedPR {},
464+ }
465+ notifier := & mockNotifier {}
466+ w := New (client , cfg , notifier )
467+
468+ ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
469+ defer cancel ()
470+
471+ err := w .Run (ctx )
472+ // Run returns nil immediately when there are no PRs
473+ if err != nil {
474+ t .Errorf ("expected nil, got %v" , err )
475+ }
476+ }
0 commit comments