nomadcode/services/core/internal/scheduler/river_test.go
toki 32fda2a844 feat: milestone work-item creation sync 및 roadmap sync pipeline 구현
- Plane 프로젝트의 work-item 생성 시 Orchestrator로 동기화
- scheduler에 roadmap sync job 등록 및 river client 지원
- roadmapsycpipeline adapter 패키지 추가
- 관련 테스트 파일 및 브리지 문서 추가
2026-06-14 12:39:31 +09:00

120 lines
4 KiB
Go

package scheduler
import (
"context"
"errors"
"testing"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/riverqueue/river"
"github.com/nomadcode/nomadcode-core/internal/roadmapsync"
"github.com/nomadcode/nomadcode-core/internal/roadmapsyncpipeline"
"github.com/nomadcode/nomadcode-core/internal/workitem"
)
// fakeCreationSyncRunner records the input it was driven with so a worker test
// can assert the job args were mapped into the orchestrator input.
type fakeCreationSyncRunner struct {
called bool
input roadmapsyncpipeline.SyncCreationInput
result roadmapsyncpipeline.SyncCreationResult
err error
}
func (f *fakeCreationSyncRunner) SyncCreation(_ context.Context, in roadmapsyncpipeline.SyncCreationInput) (roadmapsyncpipeline.SyncCreationResult, error) {
f.called = true
f.input = in
return f.result, f.err
}
func sampleCreationSyncArgs() RoadmapCreationSyncJobArgs {
identity := roadmapsync.Identity{
Shape: roadmapsync.ShapeMilestone,
RoadmapMilestonePath: "agent-roadmap/phase/p1/milestones/m1.md",
Provider: "plane",
Tenant: "general",
Project: "nomad",
WorkItemID: "NOMAD-1",
}
return RoadmapCreationSyncJobArgs{
Expected: identity,
Ref: workitem.Ref{Provider: "plane", ID: "NOMAD-1"},
OriginalBody: "<p>body</p>",
TodoStateID: "todo-state",
MilestoneMarkdown: "# m",
ProviderRevision: "rev-1",
RoadmapRevision: "rev-1",
}
}
func TestRoadmapCreationSyncWorkerCallsService(t *testing.T) {
runner := &fakeCreationSyncRunner{
result: roadmapsyncpipeline.SyncCreationResult{Action: roadmapsyncpipeline.SyncActionProjected},
}
worker := &RoadmapCreationSyncWorker{Sync: runner}
args := sampleCreationSyncArgs()
err := worker.Work(context.Background(), &river.Job[RoadmapCreationSyncJobArgs]{Args: args})
if err != nil {
t.Fatalf("Work returned error: %v", err)
}
if !runner.called {
t.Fatal("expected orchestrator to be called")
}
if runner.input.Expected.WorkItemID != "NOMAD-1" {
t.Errorf("unexpected work item id: %q", runner.input.Expected.WorkItemID)
}
if runner.input.MilestoneMarkdown != "# m" || runner.input.TodoStateID != "todo-state" {
t.Errorf("job args not mapped into orchestrator input: %#v", runner.input)
}
}
func TestRoadmapCreationSyncWorkerReturnsServiceError(t *testing.T) {
wantErr := errors.New("provider failed")
runner := &fakeCreationSyncRunner{err: wantErr}
worker := &RoadmapCreationSyncWorker{Sync: runner}
err := worker.Work(context.Background(), &river.Job[RoadmapCreationSyncJobArgs]{Args: sampleCreationSyncArgs()})
if !errors.Is(err, wantErr) {
t.Fatalf("expected service error to propagate (so River retries), got %v", err)
}
}
// lazyPool builds a pgxpool without dialing. pgxpool.New is lazy and only
// connects on first acquire, so it is safe for constructing the scheduler in a
// unit test that never starts the client.
func lazyPool(t *testing.T) *pgxpool.Pool {
t.Helper()
pool, err := pgxpool.New(context.Background(), "postgres://user:pass@127.0.0.1:1/db")
if err != nil {
t.Fatalf("build lazy pool: %v", err)
}
t.Cleanup(pool.Close)
return pool
}
func TestSchedulerRegistersRoadmapSyncWorkerWhenConfigured(t *testing.T) {
runner := &fakeCreationSyncRunner{}
client, err := New(lazyPool(t), nil, nil, nil, nil, runner, 0, nil)
if err != nil {
t.Fatalf("New with creation sync runner: %v", err)
}
if client == nil {
t.Fatal("expected non-nil client")
}
// The enqueue seam exists regardless of registration; the worker registration
// is exercised by New succeeding with a non-nil runner (River rejects an
// unknown job kind only at insert/work time, so construction succeeding plus
// the worker unit test above cover the wiring without a live DB).
}
func TestSchedulerWithoutRoadmapSyncKeepsTaskWorkerOnlyBehavior(t *testing.T) {
client, err := New(lazyPool(t), nil, nil, nil, nil, nil, 0, nil)
if err != nil {
t.Fatalf("New without creation sync runner: %v", err)
}
if client == nil {
t.Fatal("expected non-nil client")
}
}