|
| 1 | +package trace |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "path/filepath" |
| 7 | + "strings" |
| 8 | + "testing" |
| 9 | + "time" |
| 10 | + |
| 11 | + "github.com/LAA-Software-Engineering/agentic-control-plane/internal/state" |
| 12 | + "github.com/LAA-Software-Engineering/agentic-control-plane/internal/state/sqlite" |
| 13 | +) |
| 14 | + |
| 15 | +func TestRecorder_Append_increasingSeqPerRunID(t *testing.T) { |
| 16 | + ctx := context.Background() |
| 17 | + st, err := sqlite.Open(ctx, filepath.Join(t.TempDir(), "trace.db")) |
| 18 | + if err != nil { |
| 19 | + t.Fatal(err) |
| 20 | + } |
| 21 | + t.Cleanup(func() { _ = st.Close() }) |
| 22 | + |
| 23 | + started := time.Date(2026, 4, 11, 9, 0, 0, 0, time.UTC) |
| 24 | + if err := st.StartRun(ctx, state.Run{ |
| 25 | + RunID: "run-a", |
| 26 | + WorkflowName: "wf", |
| 27 | + Env: "dev", |
| 28 | + Status: "running", |
| 29 | + StartedAt: started, |
| 30 | + InputJSON: `{}`, |
| 31 | + TotalCostUSD: 0, |
| 32 | + }); err != nil { |
| 33 | + t.Fatal(err) |
| 34 | + } |
| 35 | + |
| 36 | + fixed := started.Add(time.Minute) |
| 37 | + rec := NewRecorder(st) |
| 38 | + rec.Clock = func() time.Time { return fixed } |
| 39 | + |
| 40 | + seq1, err := rec.Append(ctx, "run-a", "s1", EventStepStarted, map[string]any{"x": 1}) |
| 41 | + if err != nil { |
| 42 | + t.Fatal(err) |
| 43 | + } |
| 44 | + seq2, err := rec.Append(ctx, "run-a", "s1", EventStepFinished, map[string]any{"ok": true}) |
| 45 | + if err != nil { |
| 46 | + t.Fatal(err) |
| 47 | + } |
| 48 | + if seq1 != 1 || seq2 != 2 { |
| 49 | + t.Fatalf("seq = %d, %d want 1, 2", seq1, seq2) |
| 50 | + } |
| 51 | + |
| 52 | + rd := NewReader(st) |
| 53 | + events, err := rd.ListByRunID(ctx, "run-a") |
| 54 | + if err != nil { |
| 55 | + t.Fatal(err) |
| 56 | + } |
| 57 | + if len(events) != 2 || events[0].Seq != 1 || events[1].Seq != 2 { |
| 58 | + t.Fatalf("events = %+v", events) |
| 59 | + } |
| 60 | + if events[0].DataJSON != `{"x":1}` || events[1].DataJSON != `{"ok":true}` { |
| 61 | + t.Fatalf("data json = %q, %q", events[0].DataJSON, events[1].DataJSON) |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +func TestRecorder_Append_missingRunFailsWithErrRunNotFound(t *testing.T) { |
| 66 | + ctx := context.Background() |
| 67 | + st, err := sqlite.Open(ctx, filepath.Join(t.TempDir(), "trace2.db")) |
| 68 | + if err != nil { |
| 69 | + t.Fatal(err) |
| 70 | + } |
| 71 | + t.Cleanup(func() { _ = st.Close() }) |
| 72 | + |
| 73 | + rec := NewRecorder(st) |
| 74 | + _, err = rec.Append(ctx, "missing-run", "", EventRunStarted, nil) |
| 75 | + if err == nil { |
| 76 | + t.Fatal("expected error") |
| 77 | + } |
| 78 | + if !errors.Is(err, ErrRunNotFound) { |
| 79 | + t.Fatalf("want ErrRunNotFound in chain, got %v", err) |
| 80 | + } |
| 81 | + if !strings.Contains(err.Error(), "missing-run") { |
| 82 | + t.Fatalf("expected clear error mentioning run id, got: %v", err) |
| 83 | + } |
| 84 | +} |
0 commit comments