package session import ( "os" "path/filepath" "testing" "agentu/internal/llm" ) func tempStore(t *testing.T) *Store { t.Helper() dir := t.TempDir() store, err := NewStore(dir) if err != nil { t.Fatal(err) } return store } func TestSaveAndLoad(t *testing.T) { store := tempStore(t) sess := &Session{ ID: GenerateID(), Name: "test session", Provider: "loveuer", Model: "deepseek-v4-flash", Messages: []llm.Message{ {Role: llm.RoleSystem, Content: "you are helpful"}, {Role: llm.RoleUser, Content: "hello"}, {Role: llm.RoleAssistant, Content: "hi there"}, }, LastUsage: &llm.Usage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15}, } if err := store.Save(sess); err != nil { t.Fatalf("save: %v", err) } loaded, err := store.Load(sess.ID) if err != nil { t.Fatalf("load: %v", err) } if loaded.ID != sess.ID { t.Errorf("ID = %q, want %q", loaded.ID, sess.ID) } if loaded.Name != sess.Name { t.Errorf("Name = %q, want %q", loaded.Name, sess.Name) } if len(loaded.Messages) != 3 { t.Errorf("Messages len = %d, want 3", len(loaded.Messages)) } if loaded.Messages[1].Content != "hello" { t.Errorf("Messages[1].Content = %q, want %q", loaded.Messages[1].Content, "hello") } if loaded.LastUsage == nil || loaded.LastUsage.TotalTokens != 15 { t.Errorf("LastUsage = %#v", loaded.LastUsage) } if loaded.UpdatedAt.IsZero() { t.Error("UpdatedAt should be set") } } func TestLoadNotFound(t *testing.T) { store := tempStore(t) _, err := store.Load("nonexistent") if err == nil { t.Fatal("expected error for nonexistent session") } } func TestList(t *testing.T) { store := tempStore(t) // Empty list metas, err := store.List() if err != nil { t.Fatalf("list: %v", err) } if len(metas) != 0 { t.Errorf("expected empty list, got %d", len(metas)) } // Save two sessions store.Save(&Session{ID: "aaa", Name: "first", Messages: []llm.Message{{Role: "user", Content: "a"}}}) store.Save(&Session{ID: "bbb", Name: "second", Messages: []llm.Message{{Role: "user", Content: "b"}, {Role: "assistant", Content: "c"}}}) metas, err = store.List() if err != nil { t.Fatalf("list: %v", err) } if len(metas) != 2 { t.Fatalf("expected 2 sessions, got %d", len(metas)) } // Sorted by updated_at desc if metas[0].ID != "bbb" && metas[0].ID != "aaa" { t.Errorf("unexpected first session ID: %q", metas[0].ID) } } func TestDelete(t *testing.T) { store := tempStore(t) store.Save(&Session{ID: "todelete", Name: "temp"}) if err := store.Delete("todelete"); err != nil { t.Fatalf("delete: %v", err) } _, err := store.Load("todelete") if err == nil { t.Fatal("expected error after delete") } } func TestDeleteNotFound(t *testing.T) { store := tempStore(t) err := store.Delete("nonexistent") if err == nil { t.Fatal("expected error for nonexistent delete") } } func TestSaveRequiresID(t *testing.T) { store := tempStore(t) err := store.Save(&Session{Name: "no id"}) if err == nil { t.Fatal("expected error for empty ID") } } func TestGenerateID(t *testing.T) { id := GenerateID() if len(id) != 8 { t.Errorf("ID length = %d, want 8", len(id)) } id2 := GenerateID() if id == id2 { t.Error("expected unique IDs") } } func TestDefaultName(t *testing.T) { tests := []struct { name string messages []llm.Message want string }{ {"empty", nil, ""}, {"no user msg", []llm.Message{{Role: "system", Content: "hi"}}, ""}, {"short", []llm.Message{{Role: "user", Content: "hello world"}}, "hello world"}, {"long", []llm.Message{{Role: "user", Content: "this is a really long message that should be truncated at some point in the middle"}}, "this is a really long message that..."}, {"multiline", []llm.Message{{Role: "user", Content: "first line\nsecond line"}}, "first line"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := DefaultName(tt.messages) if got != tt.want { t.Errorf("DefaultName = %q, want %q", got, tt.want) } }) } } func TestNewStoreCreatesDir(t *testing.T) { dir := filepath.Join(t.TempDir(), "nested", "sessions") store, err := NewStore(dir) if err != nil { t.Fatalf("NewStore: %v", err) } info, err := os.Stat(store.dir) if err != nil { t.Fatalf("stat: %v", err) } if !info.IsDir() { t.Error("expected directory") } }