package gitoevents import ( "context" "errors" "io" "sync" "testing" "github.com/nomadcode/nomadcode-core/internal/protosocket" ) // fakeTransport is a scripted Transport for driving Client in tests. type fakeTransport struct { mu sync.Mutex connected bool closed bool written []protosocket.Envelope inbound []protosocket.Envelope readIdx int } func (f *fakeTransport) Connect(context.Context) error { f.mu.Lock() defer f.mu.Unlock() f.connected = true return nil } func (f *fakeTransport) Write(_ context.Context, env protosocket.Envelope) error { f.mu.Lock() defer f.mu.Unlock() f.written = append(f.written, env) return nil } func (f *fakeTransport) Read(context.Context) (protosocket.Envelope, error) { f.mu.Lock() defer f.mu.Unlock() if f.readIdx >= len(f.inbound) { return protosocket.Envelope{}, io.EOF } env := f.inbound[f.readIdx] f.readIdx++ return env, nil } func (f *fakeTransport) Close() error { f.mu.Lock() defer f.mu.Unlock() f.closed = true return nil } func TestClientSendsSubscribeEnvelope(t *testing.T) { ft := &fakeTransport{} client, err := NewClient(ft, Options{RepoID: "nomadcode", Branch: "develop"}) if err != nil { t.Fatalf("NewClient: %v", err) } if err := client.Run(context.Background()); !errors.Is(err, io.EOF) { t.Fatalf("Run: got %v, want io.EOF", err) } if !ft.connected { t.Fatal("expected transport to be connected") } if !ft.closed { t.Fatal("expected transport to be closed") } if len(ft.written) != 1 { t.Fatalf("written envelopes: got %d, want 1", len(ft.written)) } sub := ft.written[0] if sub.Channel != EventChannel || sub.Action != SubscribeAction { t.Fatalf("subscribe envelope: got %q/%q", sub.Channel, sub.Action) } if sub.Payload["repo_id"] != "nomadcode" || sub.Payload["branch"] != "develop" { t.Fatalf("subscribe payload: got %v", sub.Payload) } } func TestClientForwardsTargetEventToHandler(t *testing.T) { ft := &fakeTransport{ inbound: []protosocket.Envelope{branchUpdatedEnvelope()}, } var got []BranchUpdatedEvent client, err := NewClient(ft, Options{ RepoID: "nomadcode", Branch: "develop", Handler: func(_ context.Context, ev BranchUpdatedEvent) { got = append(got, ev) }, }) if err != nil { t.Fatalf("NewClient: %v", err) } if err := client.Run(context.Background()); !errors.Is(err, io.EOF) { t.Fatalf("Run: got %v, want io.EOF", err) } if len(got) != 1 { t.Fatalf("handler events: got %d, want 1", len(got)) } if got[0].RepoID != "nomadcode" || got[0].After != "new-sha" { t.Fatalf("forwarded event: got %+v", got[0]) } } func TestClientDropsOffTargetAndMalformedEvents(t *testing.T) { offRepo := branchUpdatedEnvelope() offRepo.Payload["repo_id"] = "other-repo" offBranch := branchUpdatedEnvelope() offBranch.Payload["branch"] = "main" wrongAction := branchUpdatedEnvelope() wrongAction.Action = "task.status.changed" malformed := branchUpdatedEnvelope() malformed.Payload["changed_files"] = "not-a-list" noMilestone := branchUpdatedEnvelope() noMilestone.Payload["changed_files"] = []any{ map[string]any{ "path": "README.md", "change_type": "modified", }, } onTarget := branchUpdatedEnvelope() ft := &fakeTransport{ inbound: []protosocket.Envelope{offRepo, offBranch, wrongAction, malformed, noMilestone, onTarget}, } var got []BranchUpdatedEvent client, err := NewClient(ft, Options{ RepoID: "nomadcode", Branch: "develop", Handler: func(_ context.Context, ev BranchUpdatedEvent) { got = append(got, ev) }, }) if err != nil { t.Fatalf("NewClient: %v", err) } if err := client.Run(context.Background()); !errors.Is(err, io.EOF) { t.Fatalf("Run: got %v, want io.EOF", err) } if len(got) != 1 { t.Fatalf("handler events: got %d, want only the on-target event", len(got)) } } func TestClientStopsOnContextCancel(t *testing.T) { ft := &fakeTransport{ inbound: []protosocket.Envelope{branchUpdatedEnvelope()}, } ctx, cancel := context.WithCancel(context.Background()) cancel() client, err := NewClient(ft, Options{RepoID: "nomadcode", Branch: "develop"}) if err != nil { t.Fatalf("NewClient: %v", err) } if err := client.Run(ctx); err != nil { t.Fatalf("Run with cancelled context: got %v, want nil", err) } } func TestNewClientRequiresRepoID(t *testing.T) { if _, err := NewClient(&fakeTransport{}, Options{}); err == nil { t.Fatal("expected error when repo_id is empty") } } func TestNewClientRequiresTransport(t *testing.T) { if _, err := NewClient(nil, Options{RepoID: "nomadcode"}); err == nil { t.Fatal("expected error when transport is nil") } }