- gitoevents: Gito 이벤트 클라이언트 및 이벤트 모델 구현 - gitosync: 브리지, 러너, 스캐너 구현으로 브랜치 이벤트 및 작업 항목 동기화 - config: 새 설정 항목 추가 및 테스트 - roadmapsync: plane projection 수정 및 테스트 - agent-task: G07 관련 계획 및 코드 리뷰 로그 추가 - roadmap: PHASE 및 마일스톤 업데이트 - contracts: Flutter core API 후보 노트 업데이트
181 lines
4.3 KiB
Go
181 lines
4.3 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"
|
|
|
|
onTarget := branchUpdatedEnvelope()
|
|
|
|
ft := &fakeTransport{
|
|
inbound: []protosocket.Envelope{offRepo, offBranch, wrongAction, malformed, 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")
|
|
}
|
|
}
|