| 1 | package cli |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | "time" |
| 10 | |
| 11 | "reasonix/internal/agent" |
| 12 | ) |
| 13 | |
| 14 | func TestRenameSessionUpdatesCustomTitle(t *testing.T) { |
| 15 | dir := t.TempDir() |
| 16 | sessionPath := filepath.Join(dir, "test-session.jsonl") |
| 17 | if err := os.WriteFile(sessionPath, []byte("{}\n"), 0o644); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | updatedAt := time.Date(2026, 6, 1, 12, 0, 0, 0, time.UTC) |
| 21 | if err := agent.SaveBranchMetaPreserveUpdated(sessionPath, agent.BranchMeta{ |
| 22 | TopicTitle: "Topic", |
| 23 | CreatedAt: updatedAt.Add(-time.Hour), |
| 24 | UpdatedAt: updatedAt, |
| 25 | }); err != nil { |
| 26 | t.Fatalf("seed meta: %v", err) |
| 27 | } |
| 28 | if err := agent.RenameSession(sessionPath, "My Test Title"); err != nil { |
| 29 | t.Fatalf("RenameSession failed: %v", err) |
| 30 | } |
| 31 | metaPath := sessionPath + ".meta" |
| 32 | raw, err := os.ReadFile(metaPath) |
| 33 | if err != nil { |
| 34 | t.Fatalf("reading meta: %v", err) |
| 35 | } |
| 36 | var m struct { |
| 37 | TopicTitle string `json:"topic_title"` |
| 38 | CustomTitle string `json:"custom_title"` |
| 39 | } |
| 40 | if err := json.Unmarshal(raw, &m); err != nil { |
| 41 | t.Fatalf("decoding meta: %v", err) |
| 42 | } |
| 43 | if m.CustomTitle != "My Test Title" { |
| 44 | t.Errorf("custom_title = %q, want %q", m.CustomTitle, "My Test Title") |
| 45 | } |
| 46 | if m.TopicTitle != "Topic" { |
| 47 | t.Errorf("topic_title = %q, want preserved Topic", m.TopicTitle) |
| 48 | } |
| 49 | stored, ok, err := agent.LoadBranchMeta(sessionPath) |
| 50 | if err != nil || !ok { |
| 51 | t.Fatalf("LoadBranchMeta after rename ok=%v err=%v", ok, err) |
| 52 | } |
| 53 | if !stored.UpdatedAt.Equal(updatedAt) { |
| 54 | t.Errorf("updated_at changed after rename: got %s want %s", stored.UpdatedAt, updatedAt) |
| 55 | } |
| 56 | if err := agent.RenameSession(sessionPath, "Updated Title"); err != nil { |
| 57 | t.Fatalf("second rename failed: %v", err) |
| 58 | } |
| 59 | raw, _ = os.ReadFile(metaPath) |
| 60 | json.Unmarshal(raw, &m) |
| 61 | if m.CustomTitle != "Updated Title" { |
| 62 | t.Errorf("custom_title after second rename = %q, want %q", m.CustomTitle, "Updated Title") |
| 63 | } |
| 64 | if m.TopicTitle != "Topic" { |
| 65 | t.Errorf("topic_title after second rename = %q, want preserved Topic", m.TopicTitle) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | func TestSessionPickerLabelPrefersCustomTitle(t *testing.T) { |
| 70 | s := agent.SessionInfo{Turns: 5, Preview: "first user message here", TopicTitle: ""} |
| 71 | got := sessionPickerLabel(s) |
| 72 | if got == "" { |
| 73 | t.Fatal("empty label") |
| 74 | } |
| 75 | s.TopicTitle = "My Topic Name" |
| 76 | s.CustomTitle = "My Custom Name" |
| 77 | got = sessionPickerLabel(s) |
| 78 | if !strings.Contains(got, "My Custom Name") { |
| 79 | t.Errorf("label %q should contain custom title", got) |
| 80 | } |
| 81 | if strings.Contains(got, "My Topic Name") { |
| 82 | t.Errorf("label %q should prefer custom title over topic title", got) |
| 83 | } |
| 84 | } |
| 85 |