diff --git a/daemon_test.go b/daemon_test.go new file mode 100644 index 0000000..0172a3d --- /dev/null +++ b/daemon_test.go @@ -0,0 +1,179 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + "time" +) + +func TestRegistryUpdate(t *testing.T) { + reg := NewRegistry() + + info := SessionInfo{ + PID: 1234, + SessionID: "sess-1", + Cwd: "/home/user/project", + State: "Working", + } + + reg.Update(info) + + list := reg.List() + if len(list) != 1 { + t.Fatalf("list len = %d, want 1", len(list)) + } + if list[0].SessionID != "sess-1" { + t.Errorf("session_id = %q, want %q", list[0].SessionID, "sess-1") + } +} + +func TestRegistryWaitingSince(t *testing.T) { + reg := NewRegistry() + + // Session starts Working + reg.Update(SessionInfo{ + PID: 1234, + SessionID: "sess-1", + State: "Working", + }) + + list := reg.List() + if list[0].WaitingSince != nil { + t.Error("WaitingSince should be nil when Working") + } + + // Session transitions to NeedsInput + reg.Update(SessionInfo{ + PID: 1234, + SessionID: "sess-1", + State: "Needs Input", + }) + + list = reg.List() + if list[0].WaitingSince == nil { + t.Fatal("WaitingSince should be set when NeedsInput") + } + + waitStart := *list[0].WaitingSince + + // Session goes back to Working -> WaitingSince reset + reg.Update(SessionInfo{ + PID: 1234, + SessionID: "sess-1", + State: "Working", + }) + + list = reg.List() + if list[0].WaitingSince != nil { + t.Errorf("WaitingSince should be nil after returning to Working, got %v", list[0].WaitingSince) + } + + _ = waitStart +} + +func TestRegistryRemoveStale(t *testing.T) { + reg := NewRegistry() + + reg.Update(SessionInfo{SessionID: "sess-1", State: "Working"}) + reg.Update(SessionInfo{SessionID: "sess-2", State: "Working"}) + + // Only sess-1 is still active + active := map[string]bool{"sess-1": true} + reg.RemoveStale(active) + + list := reg.List() + if len(list) != 1 { + t.Fatalf("list len = %d, want 1", len(list)) + } + if list[0].SessionID != "sess-1" { + t.Errorf("session_id = %q, want %q", list[0].SessionID, "sess-1") + } +} + +func TestLabelStoreSetGet(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "labels.json") + + ls, err := NewLabelStore(path) + if err != nil { + t.Fatalf("new: %v", err) + } + + if err := ls.Set("sess-1", "review MR"); err != nil { + t.Fatalf("set: %v", err) + } + + got := ls.Get("sess-1") + if got != "review MR" { + t.Errorf("get = %q, want %q", got, "review MR") + } + + // Non-existent key returns empty + if got := ls.Get("unknown"); got != "" { + t.Errorf("get unknown = %q, want empty", got) + } +} + +func TestLabelStorePersistence(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "labels.json") + + ls, err := NewLabelStore(path) + if err != nil { + t.Fatalf("new: %v", err) + } + if err := ls.Set("sess-1", "review MR"); err != nil { + t.Fatalf("set: %v", err) + } + + // Create a new LabelStore from the same file + ls2, err := NewLabelStore(path) + if err != nil { + t.Fatalf("new2: %v", err) + } + + got := ls2.Get("sess-1") + if got != "review MR" { + t.Errorf("persisted get = %q, want %q", got, "review MR") + } +} + +func TestLabelStoreLoadMissing(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "nonexistent", "labels.json") + + ls, err := NewLabelStore(path) + if err != nil { + t.Fatalf("should not error on missing file: %v", err) + } + + if got := ls.Get("anything"); got != "" { + t.Errorf("get = %q, want empty", got) + } +} + +func TestRegistryUpdateTimestamp(t *testing.T) { + reg := NewRegistry() + + before := time.Now() + reg.Update(SessionInfo{ + SessionID: "sess-1", + State: "Needs Input", + }) + after := time.Now() + + list := reg.List() + if list[0].WaitingSince == nil { + t.Fatal("WaitingSince should be set") + } + ws := *list[0].WaitingSince + if ws.Before(before) || ws.After(after) { + t.Errorf("WaitingSince %v not between %v and %v", ws, before, after) + } +} + +// Placeholder to verify file exists +func init() { + _ = os.TempDir() +} diff --git a/protocol_test.go b/protocol_test.go new file mode 100644 index 0000000..21afa3b --- /dev/null +++ b/protocol_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "encoding/json" + "testing" + "time" +) + +func TestRequestMarshal(t *testing.T) { + req := Request{Action: "list"} + + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("marshal: %v", err) + } + + var got Request + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + if got.Action != "list" { + t.Errorf("action = %q, want %q", got.Action, "list") + } +} + +func TestRequestMarshalWithArgs(t *testing.T) { + args, _ := json.Marshal(LabelArgs{SessionID: "abc", Label: "review MR"}) + req := Request{Action: "label", Args: args} + + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("marshal: %v", err) + } + + var got Request + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + if got.Action != "label" { + t.Errorf("action = %q, want %q", got.Action, "label") + } + + var la LabelArgs + if err := json.Unmarshal(got.Args, &la); err != nil { + t.Fatalf("unmarshal args: %v", err) + } + if la.SessionID != "abc" || la.Label != "review MR" { + t.Errorf("args = %+v, want {abc, review MR}", la) + } +} + +func TestResponseWithSessions(t *testing.T) { + now := time.Now() + resp := Response{ + OK: true, + Sessions: []SessionInfo{ + { + PID: 1234, + SessionID: "sess-1", + Cwd: "/home/user/project", + GitBranch: "main", + State: "Working", + Preview: "Building...", + Workspace: "3", + Label: "feature-x", + WaitingSince: &now, + }, + }, + } + + data, err := json.Marshal(resp) + if err != nil { + t.Fatalf("marshal: %v", err) + } + + var got Response + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + if !got.OK { + t.Error("ok = false, want true") + } + if len(got.Sessions) != 1 { + t.Fatalf("sessions len = %d, want 1", len(got.Sessions)) + } + + s := got.Sessions[0] + if s.PID != 1234 { + t.Errorf("pid = %d, want 1234", s.PID) + } + if s.Workspace != "3" { + t.Errorf("workspace = %q, want %q", s.Workspace, "3") + } + if s.Label != "feature-x" { + t.Errorf("label = %q, want %q", s.Label, "feature-x") + } + if s.WaitingSince == nil { + t.Error("waiting_since = nil, want non-nil") + } +}