package storage import ( "encoding/json" "testing" "time" "github.com/thepeterstone/claudomator/internal/event" "github.com/thepeterstone/claudomator/internal/task" ) // makeTaskForEvent creates a minimal task row so events can satisfy the FK. func makeTaskForEvent(t *testing.T, db *DB, id string) { t.Helper() now := time.Now().UTC().Truncate(time.Second) tk := &task.Task{ ID: id, Name: id, Agent: task.AgentConfig{Type: "claude", Instructions: "noop"}, Priority: task.PriorityNormal, State: task.StatePending, CreatedAt: now, UpdatedAt: now, } if err := db.CreateTask(tk); err != nil { t.Fatalf("creating task: %v", err) } } func TestCreateEvent_AssignsSeqAndDefaults(t *testing.T) { db := testDB(t) makeTaskForEvent(t, db, "task-1") e := &event.Event{ TaskID: "task-1", Kind: event.KindStateChange, Actor: event.ActorSystem, } if err := db.CreateEvent(e); err != nil { t.Fatalf("CreateEvent: %v", err) } if e.ID == "" { t.Error("expected ID populated") } if e.Seq != 1 { t.Errorf("expected seq=1, got %d", e.Seq) } if e.Timestamp.IsZero() { t.Error("expected Timestamp populated") } if string(e.Payload) != "{}" { t.Errorf("expected default payload {}, got %s", e.Payload) } } func TestCreateEvent_SeqIsMonotonicPerTask(t *testing.T) { db := testDB(t) makeTaskForEvent(t, db, "task-A") makeTaskForEvent(t, db, "task-B") // 3 events on task-A, 2 on task-B, interleaved. for _, taskID := range []string{"task-A", "task-A", "task-B", "task-A", "task-B"} { e := &event.Event{ TaskID: taskID, Kind: event.KindAgentMessage, Actor: event.ActorAgent, } if err := db.CreateEvent(e); err != nil { t.Fatalf("CreateEvent: %v", err) } } a, err := db.ListEvents("task-A", 0) if err != nil { t.Fatalf("ListEvents A: %v", err) } if len(a) != 3 { t.Fatalf("expected 3 events on task-A, got %d", len(a)) } for i, e := range a { want := int64(i + 1) if e.Seq != want { t.Errorf("task-A event %d: seq=%d want %d", i, e.Seq, want) } } b, err := db.ListEvents("task-B", 0) if err != nil { t.Fatalf("ListEvents B: %v", err) } if len(b) != 2 { t.Fatalf("expected 2 events on task-B, got %d", len(b)) } for i, e := range b { want := int64(i + 1) if e.Seq != want { t.Errorf("task-B event %d: seq=%d want %d", i, e.Seq, want) } } } func TestListEvents_SinceSeq(t *testing.T) { db := testDB(t) makeTaskForEvent(t, db, "task-1") for i := 0; i < 5; i++ { e := &event.Event{ TaskID: "task-1", Kind: event.KindAgentMessage, Actor: event.ActorAgent, } if err := db.CreateEvent(e); err != nil { t.Fatalf("CreateEvent: %v", err) } } got, err := db.ListEvents("task-1", 3) if err != nil { t.Fatalf("ListEvents: %v", err) } if len(got) != 2 { t.Fatalf("expected 2 events with seq>3, got %d", len(got)) } if got[0].Seq != 4 || got[1].Seq != 5 { t.Errorf("expected seqs 4,5; got %d,%d", got[0].Seq, got[1].Seq) } } func TestListEvents_PreservesPayloadAndKind(t *testing.T) { db := testDB(t) makeTaskForEvent(t, db, "task-1") payload := json.RawMessage(`{"question":"which env?","options":["dev","prod"]}`) written := &event.Event{ TaskID: "task-1", Kind: event.KindClarificationRequest, Actor: event.ActorAgent, Payload: payload, } if err := db.CreateEvent(written); err != nil { t.Fatalf("CreateEvent: %v", err) } got, err := db.ListEvents("task-1", 0) if err != nil { t.Fatalf("ListEvents: %v", err) } if len(got) != 1 { t.Fatalf("expected 1 event, got %d", len(got)) } e := got[0] if e.Kind != event.KindClarificationRequest { t.Errorf("kind: got %v want %v", e.Kind, event.KindClarificationRequest) } if e.Actor != event.ActorAgent { t.Errorf("actor: got %v want %v", e.Actor, event.ActorAgent) } if string(e.Payload) != string(payload) { t.Errorf("payload: got %s want %s", e.Payload, payload) } } func TestCreateEvent_RequiresFields(t *testing.T) { db := testDB(t) cases := []struct { name string e *event.Event }{ {"nil", nil}, {"empty taskID", &event.Event{Kind: event.KindAgentMessage, Actor: event.ActorAgent}}, {"empty kind", &event.Event{TaskID: "t", Actor: event.ActorAgent}}, {"empty actor", &event.Event{TaskID: "t", Kind: event.KindAgentMessage}}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if err := db.CreateEvent(tc.e); err == nil { t.Errorf("expected error, got nil") } }) } }