返回 DeepSeek-Reasonix
catalog_test.go
根目录 / internal / taskcatalog / catalog_test.go
1 package taskcatalog
2
3 import (
4 "context"
5 "path/filepath"
6 "testing"
7 "time"
8
9 "reasonix/internal/taskmonitor"
10 )
11
12 func snapshot(id, session string, version uint64, updated time.Time) taskmonitor.TaskSnapshot {
13 return taskmonitor.TaskSnapshot{SchemaVersion: 1, TaskID: id, SessionID: session, State: taskmonitor.TaskStateRunning,
14 RuntimeState: taskmonitor.RuntimeStateAlive, RuntimeLeaseUntil: updated.Add(time.Hour), Version: version,
15 CreatedAt: updated.Add(-time.Minute), UpdatedAt: updated}
16 }
17
18 func TestObservedStoreIndexesSnapshotsAndEvents(t *testing.T) {
19 t.Parallel()
20 ctx := context.Background()
21 projectRoot := t.TempDir()
22 catalog, err := Open(ctx, filepath.Join(t.TempDir(), "tasks.sqlite"))
23 if err != nil {
24 t.Fatal(err)
25 }
26 t.Cleanup(func() { _ = catalog.Close(context.Background()) })
27 project, err := catalog.RegisterProject(ctx, projectRoot, "Demo")
28 if err != nil {
29 t.Fatal(err)
30 }
31 store := catalog.ObservedStore()
32 now := time.Now()
33 if err := store.SaveTask(ctx, projectRoot, snapshot("task-1", "session-1", 1, now)); err != nil {
34 t.Fatal(err)
35 }
36 if err := store.AppendAuditEvent(ctx, projectRoot, taskmonitor.TaskEvent{Timestamp: now, EventType: "state_change", TaskID: "task-1", SessionID: "session-1", State: taskmonitor.TaskStateRunning}); err != nil {
37 t.Fatal(err)
38 }
39 flushCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
40 defer cancel()
41 if err := catalog.Flush(flushCtx); err != nil {
42 t.Fatal(err)
43 }
44 page, err := catalog.ListPage(ctx, PageRequest{ProjectKeys: []string{project.Key}, SessionID: "session-1", Limit: 50})
45 if err != nil || len(page.Items) != 1 || page.Items[0].Task.TaskID != "task-1" {
46 t.Fatalf("page=%#v err=%v", page, err)
47 }
48 events, err := catalog.ListEventPage(ctx, project.Key, "task-1", 0, 50)
49 if err != nil || len(events.Items) != 1 || events.NextSequence != 1 {
50 t.Fatalf("events=%#v err=%v", events, err)
51 }
52 if err := store.AppendAuditEvent(ctx, projectRoot, taskmonitor.TaskEvent{Timestamp: now.Add(time.Second), EventType: "state_change",
53 TaskID: "task-1", SessionID: "session-1", State: taskmonitor.TaskStateSucceeded}); err != nil {
54 t.Fatal(err)
55 }
56 if err := catalog.Flush(flushCtx); err != nil {
57 t.Fatal(err)
58 }
59 events, err = catalog.ListEventPage(ctx, project.Key, "task-1", 1, 50)
60 if err != nil || len(events.Items) != 1 || events.Items[0].Sequence != 2 {
61 t.Fatalf("incremental events=%#v err=%v", events, err)
62 }
63 }
64
65 func TestPageCursorIsRevisionBound(t *testing.T) {
66 t.Parallel()
67 ctx := context.Background()
68 root := t.TempDir()
69 catalog, err := Open(ctx, filepath.Join(t.TempDir(), "tasks.sqlite"))
70 if err != nil {
71 t.Fatal(err)
72 }
73 t.Cleanup(func() { _ = catalog.Close(context.Background()) })
74 project, _ := catalog.RegisterProject(ctx, root, "Demo")
75 store := catalog.ObservedStore()
76 now := time.Now()
77 for i, id := range []string{"a", "b", "c"} {
78 if err := store.SaveTask(ctx, root, snapshot(id, "session", 1, now.Add(time.Duration(i)*time.Minute))); err != nil {
79 t.Fatal(err)
80 }
81 }
82 flushCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
83 defer cancel()
84 _ = catalog.Flush(flushCtx)
85 first, err := catalog.ListPage(ctx, PageRequest{ProjectKeys: []string{project.Key}, Limit: 2})
86 if err != nil || len(first.Items) != 2 || first.NextCursor == "" {
87 t.Fatalf("first=%#v err=%v", first, err)
88 }
89 if err := store.SaveTask(ctx, root, snapshot("d", "session", 1, now.Add(4*time.Minute))); err != nil {
90 t.Fatal(err)
91 }
92 _ = catalog.Flush(flushCtx)
93 stale, err := catalog.ListPage(ctx, PageRequest{ProjectKeys: []string{project.Key}, Cursor: first.NextCursor})
94 if err != nil || !stale.StaleCursor || len(stale.Items) != 0 {
95 t.Fatalf("stale=%#v err=%v", stale, err)
96 }
97 }
98
98 lines GO