summaryrefslogtreecommitdiff
path: root/internal/storage/db_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/storage/db_test.go')
-rw-r--r--internal/storage/db_test.go372
1 files changed, 371 insertions, 1 deletions
diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go
index 752c5b1..0e67e02 100644
--- a/internal/storage/db_test.go
+++ b/internal/storage/db_test.go
@@ -41,7 +41,6 @@ func TestCreateTask_AndGetTask(t *testing.T) {
Type: "claude",
Model: "sonnet",
Instructions: "do it",
- ProjectDir: "/tmp",
MaxBudgetUSD: 2.5,
},
Priority: task.PriorityHigh,
@@ -990,6 +989,128 @@ func TestAppendTaskInteraction_NotFound(t *testing.T) {
}
}
+func TestCreateTask_Project_RoundTrip(t *testing.T) {
+ db := testDB(t)
+ now := time.Now().UTC().Truncate(time.Second)
+
+ tk := &task.Task{
+ ID: "proj-1",
+ Name: "Project Task",
+ Project: "my-project",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "do it"},
+ Priority: task.PriorityNormal,
+ Tags: []string{},
+ DependsOn: []string{},
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ State: task.StatePending,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ if err := db.CreateTask(tk); err != nil {
+ t.Fatalf("creating task: %v", err)
+ }
+
+ got, err := db.GetTask("proj-1")
+ if err != nil {
+ t.Fatalf("getting task: %v", err)
+ }
+ if got.Project != "my-project" {
+ t.Errorf("project: want %q, got %q", "my-project", got.Project)
+ }
+}
+
+// ── Push subscription tests ───────────────────────────────────────────────────
+
+func TestPushSubscription_SaveAndList(t *testing.T) {
+ db := testDB(t)
+
+ sub := PushSubscription{
+ ID: "sub-1",
+ Endpoint: "https://push.example.com/endpoint1",
+ P256DHKey: "p256dhkey1",
+ AuthKey: "authkey1",
+ }
+ if err := db.SavePushSubscription(sub); err != nil {
+ t.Fatalf("SavePushSubscription: %v", err)
+ }
+
+ subs, err := db.ListPushSubscriptions()
+ if err != nil {
+ t.Fatalf("ListPushSubscriptions: %v", err)
+ }
+ if len(subs) != 1 {
+ t.Fatalf("want 1 subscription, got %d", len(subs))
+ }
+ if subs[0].Endpoint != sub.Endpoint {
+ t.Errorf("endpoint: want %q, got %q", sub.Endpoint, subs[0].Endpoint)
+ }
+ if subs[0].P256DHKey != sub.P256DHKey {
+ t.Errorf("p256dh_key: want %q, got %q", sub.P256DHKey, subs[0].P256DHKey)
+ }
+ if subs[0].AuthKey != sub.AuthKey {
+ t.Errorf("auth_key: want %q, got %q", sub.AuthKey, subs[0].AuthKey)
+ }
+}
+
+func TestPushSubscription_Delete(t *testing.T) {
+ db := testDB(t)
+
+ sub := PushSubscription{
+ ID: "sub-del",
+ Endpoint: "https://push.example.com/todelete",
+ P256DHKey: "key",
+ AuthKey: "auth",
+ }
+ if err := db.SavePushSubscription(sub); err != nil {
+ t.Fatalf("SavePushSubscription: %v", err)
+ }
+
+ if err := db.DeletePushSubscription(sub.Endpoint); err != nil {
+ t.Fatalf("DeletePushSubscription: %v", err)
+ }
+
+ subs, err := db.ListPushSubscriptions()
+ if err != nil {
+ t.Fatalf("ListPushSubscriptions: %v", err)
+ }
+ if len(subs) != 0 {
+ t.Errorf("want 0 subscriptions after delete, got %d", len(subs))
+ }
+}
+
+func TestPushSubscription_UniqueEndpoint(t *testing.T) {
+ db := testDB(t)
+
+ sub := PushSubscription{
+ ID: "sub-uq",
+ Endpoint: "https://push.example.com/unique",
+ P256DHKey: "key1",
+ AuthKey: "auth1",
+ }
+ if err := db.SavePushSubscription(sub); err != nil {
+ t.Fatalf("SavePushSubscription first: %v", err)
+ }
+
+ // Save again with same endpoint — should update or replace, not error.
+ sub2 := PushSubscription{
+ ID: "sub-uq2",
+ Endpoint: "https://push.example.com/unique",
+ P256DHKey: "key2",
+ AuthKey: "auth2",
+ }
+ if err := db.SavePushSubscription(sub2); err != nil {
+ t.Fatalf("SavePushSubscription second (upsert): %v", err)
+ }
+
+ subs, err := db.ListPushSubscriptions()
+ if err != nil {
+ t.Fatalf("ListPushSubscriptions: %v", err)
+ }
+ if len(subs) != 1 {
+ t.Errorf("want 1 subscription after upsert, got %d", len(subs))
+ }
+}
+
func TestExecution_StoreAndRetrieveChangestats(t *testing.T) {
db := testDB(t)
now := time.Now().UTC().Truncate(time.Second)
@@ -1032,3 +1153,252 @@ func TestExecution_StoreAndRetrieveChangestats(t *testing.T) {
}
}
+func TestCreateProject(t *testing.T) {
+ db := testDB(t)
+ defer db.Close()
+
+ p := &task.Project{
+ ID: "proj-1",
+ Name: "claudomator",
+ RemoteURL: "/bare/claudomator.git",
+ LocalPath: "/workspace/claudomator",
+ Type: "web",
+ }
+ if err := db.CreateProject(p); err != nil {
+ t.Fatalf("CreateProject: %v", err)
+ }
+ got, err := db.GetProject("proj-1")
+ if err != nil {
+ t.Fatalf("GetProject: %v", err)
+ }
+ if got.Name != "claudomator" {
+ t.Errorf("Name: want claudomator, got %q", got.Name)
+ }
+ if got.LocalPath != "/workspace/claudomator" {
+ t.Errorf("LocalPath: want /workspace/claudomator, got %q", got.LocalPath)
+ }
+}
+
+func TestListProjects(t *testing.T) {
+ db := testDB(t)
+ defer db.Close()
+
+ for _, p := range []*task.Project{
+ {ID: "p1", Name: "alpha", Type: "web"},
+ {ID: "p2", Name: "beta", Type: "android"},
+ } {
+ if err := db.CreateProject(p); err != nil {
+ t.Fatalf("CreateProject: %v", err)
+ }
+ }
+ list, err := db.ListProjects()
+ if err != nil {
+ t.Fatalf("ListProjects: %v", err)
+ }
+ if len(list) != 2 {
+ t.Errorf("want 2 projects, got %d", len(list))
+ }
+}
+
+func TestUpdateProject(t *testing.T) {
+ db := testDB(t)
+ defer db.Close()
+
+ p := &task.Project{ID: "p1", Name: "original", Type: "web"}
+ if err := db.CreateProject(p); err != nil {
+ t.Fatalf("CreateProject: %v", err)
+ }
+ p.Name = "updated"
+ if err := db.UpdateProject(p); err != nil {
+ t.Fatalf("UpdateProject: %v", err)
+ }
+ got, _ := db.GetProject("p1")
+ if got.Name != "updated" {
+ t.Errorf("Name after update: want updated, got %q", got.Name)
+ }
+}
+
+func TestCreateStory(t *testing.T) {
+ db := testDB(t)
+ st := &task.Story{
+ ID: "story-1",
+ Name: "My Story",
+ Status: task.StoryPending,
+ }
+ if err := db.CreateStory(st); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+}
+
+func TestGetStory(t *testing.T) {
+ db := testDB(t)
+ st := &task.Story{
+ ID: "story-2",
+ Name: "Get Story",
+ ProjectID: "proj-1",
+ Status: task.StoryPending,
+ }
+ if err := db.CreateStory(st); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+ got, err := db.GetStory("story-2")
+ if err != nil {
+ t.Fatalf("GetStory: %v", err)
+ }
+ if got.Name != "Get Story" {
+ t.Errorf("Name: want 'Get Story', got %q", got.Name)
+ }
+ if got.ProjectID != "proj-1" {
+ t.Errorf("ProjectID: want 'proj-1', got %q", got.ProjectID)
+ }
+ if got.Status != task.StoryPending {
+ t.Errorf("Status: want PENDING, got %q", got.Status)
+ }
+}
+
+func TestListStories(t *testing.T) {
+ db := testDB(t)
+ for _, name := range []string{"A", "B", "C"} {
+ if err := db.CreateStory(&task.Story{ID: name, Name: name, Status: task.StoryPending}); err != nil {
+ t.Fatalf("CreateStory %s: %v", name, err)
+ }
+ }
+ stories, err := db.ListStories()
+ if err != nil {
+ t.Fatalf("ListStories: %v", err)
+ }
+ if len(stories) != 3 {
+ t.Errorf("want 3 stories, got %d", len(stories))
+ }
+}
+
+func TestUpdateStoryStatus(t *testing.T) {
+ db := testDB(t)
+ st := &task.Story{ID: "story-upd", Name: "Upd", Status: task.StoryPending}
+ if err := db.CreateStory(st); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+ if err := db.UpdateStoryStatus("story-upd", task.StoryInProgress); err != nil {
+ t.Fatalf("UpdateStoryStatus: %v", err)
+ }
+ got, _ := db.GetStory("story-upd")
+ if got.Status != task.StoryInProgress {
+ t.Errorf("Status: want IN_PROGRESS, got %q", got.Status)
+ }
+}
+
+func TestListTasksByStory(t *testing.T) {
+ db := testDB(t)
+ now := time.Now().UTC()
+
+ if err := db.CreateStory(&task.Story{ID: "story-tasks", Name: "S", Status: task.StoryPending}); err != nil {
+ t.Fatalf("CreateStory: %v", err)
+ }
+
+ makeTask := func(id string) *task.Task {
+ return &task.Task{
+ ID: id,
+ Name: id,
+ StoryID: "story-tasks",
+ Agent: task.AgentConfig{Type: "claude"},
+ Priority: task.PriorityNormal,
+ Tags: []string{},
+ DependsOn: []string{},
+ Retry: task.RetryConfig{MaxAttempts: 1},
+ State: task.StatePending,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ }
+
+ if err := db.CreateTask(makeTask("t1")); err != nil {
+ t.Fatal(err)
+ }
+ if err := db.CreateTask(makeTask("t2")); err != nil {
+ t.Fatal(err)
+ }
+
+ tasks, err := db.ListTasksByStory("story-tasks")
+ if err != nil {
+ t.Fatalf("ListTasksByStory: %v", err)
+ }
+ if len(tasks) != 2 {
+ t.Errorf("want 2 tasks, got %d", len(tasks))
+ }
+ for _, tk := range tasks {
+ if tk.StoryID != "story-tasks" {
+ t.Errorf("task %s: StoryID want 'story-tasks', got %q", tk.ID, tk.StoryID)
+ }
+ }
+}
+
+func TestUpdateTaskCheckerReport(t *testing.T) {
+ db := testDB(t)
+ tk := &task.Task{
+ ID: "cr-1", Name: "orig", RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "x"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(tk); err != nil {
+ t.Fatalf("CreateTask: %v", err)
+ }
+ if err := db.UpdateTaskCheckerReport("cr-1", "Tests failed: missing endpoint"); err != nil {
+ t.Fatalf("UpdateTaskCheckerReport: %v", err)
+ }
+ got, err := db.GetTask("cr-1")
+ if err != nil {
+ t.Fatalf("GetTask: %v", err)
+ }
+ if got.CheckerReport != "Tests failed: missing endpoint" {
+ t.Errorf("expected checker report, got %q", got.CheckerReport)
+ }
+}
+
+func TestGetCheckerTask(t *testing.T) {
+ db := testDB(t)
+ checked := &task.Task{
+ ID: "chk-orig", Name: "orig", RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "x"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(checked); err != nil {
+ t.Fatalf("CreateTask checked: %v", err)
+ }
+ checker := &task.Task{
+ ID: "chk-checker", Name: "Check: orig", CheckerForTaskID: "chk-orig",
+ RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "validate"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(checker); err != nil {
+ t.Fatalf("CreateTask checker: %v", err)
+ }
+
+ // Should find the checker task.
+ got, err := db.GetCheckerTask("chk-orig")
+ if err != nil {
+ t.Fatalf("GetCheckerTask: %v", err)
+ }
+ if got == nil || got.ID != "chk-checker" {
+ t.Errorf("expected checker task ID chk-checker, got %v", got)
+ }
+
+ // Should return nil when no checker exists.
+ none, err := db.GetCheckerTask("nonexistent")
+ if err != nil {
+ t.Fatalf("GetCheckerTask nonexistent: %v", err)
+ }
+ if none != nil {
+ t.Errorf("expected nil for task with no checker, got %v", none)
+ }
+}
+