nomadcode/services/core/internal/gitoevents/client_test.go
toki 0109c89eb9 update: agent-ops-mcp-control-plane 로드맵 정리 및 gitoevents 클라이언트 개선
- 마일스톤 문서 archive로 이동 (gito-branch-event-sync-bridge, plane-work-item-webhook-intake)
- PHASE.md 및 ROADMAP.md 갱신
- gitoevents/client.go: 클라이언트 로직 개선
- gitoevents/client_test.go: 테스트 커버리지 추가
2026-06-16 21:05:31 +09:00

189 lines
4.5 KiB
Go

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")
}
}