All checks were successful
Beta Release / beta (push) Successful in 5m9s
Major additions: - RAG pipeline (indexing, chunking, search) with sidebar upload button - Memory system with CRUD API - Plugins and lessons modules - MCP discovery and MCP server - Advanced skills (auto-create, conditional, improver) - Agent browser/image support, delegate, sessions - File editor with CodeMirror in split panes - Markdown rendering via react-markdown + KaTeX + highlight.js - Raw markdown toggle - PWA manifest + service worker - Extension UI redesign with new design tokens and studio-style chat - Pipeline API for chat streaming - Mobile responsive layout 💘 Generated with Crush Assisted-by: GLM-5.1 via Crush <crush@charm.land>
190 lines
3.7 KiB
Go
190 lines
3.7 KiB
Go
package memory
|
|
|
|
import (
|
|
"database/sql"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
func testDBPath(t *testing.T) string {
|
|
dir := t.TempDir()
|
|
return filepath.Join(dir, "test_memory.db")
|
|
}
|
|
|
|
func newTestStore(t *testing.T) *Store {
|
|
t.Helper()
|
|
dbPath := testDBPath(t)
|
|
|
|
db, err := openDB(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open db: %v", err)
|
|
}
|
|
t.Cleanup(func() { db.Close() })
|
|
|
|
s := &Store{db: db, path: dbPath}
|
|
if err := s.migrate(); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
func openDB(path string) (*sql.DB, error) {
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
return sql.Open("sqlite", path)
|
|
}
|
|
|
|
func TestStoreAndRetrieve(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
m := &Memory{
|
|
Type: TypeFact,
|
|
Key: "golang_version",
|
|
Content: "User uses Go 1.24",
|
|
Source: "conversation",
|
|
}
|
|
|
|
if err := s.Store(m); err != nil {
|
|
t.Fatalf("store: %v", err)
|
|
}
|
|
|
|
if m.ID == "" {
|
|
t.Fatal("expected ID to be set")
|
|
}
|
|
|
|
got, err := s.Get(m.ID)
|
|
if err != nil {
|
|
t.Fatalf("get: %v", err)
|
|
}
|
|
|
|
if got.Key != m.Key {
|
|
t.Errorf("expected key %s, got %s", m.Key, got.Key)
|
|
}
|
|
if got.Content != m.Content {
|
|
t.Errorf("expected content %s, got %s", m.Content, got.Content)
|
|
}
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
m := &Memory{
|
|
Type: TypePreference,
|
|
Key: "editor",
|
|
Content: "vim",
|
|
}
|
|
s.Store(m)
|
|
|
|
if err := s.Delete(m.ID); err != nil {
|
|
t.Fatalf("delete: %v", err)
|
|
}
|
|
|
|
_, err := s.Get(m.ID)
|
|
if err == nil {
|
|
t.Error("expected error after delete")
|
|
}
|
|
}
|
|
|
|
func TestList(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
s.Store(&Memory{
|
|
Type: TypeFact,
|
|
Key: "fact_" + string(rune('a'+i)),
|
|
Content: "content",
|
|
})
|
|
}
|
|
|
|
memories, err := s.List(TypeFact, 10, 0)
|
|
if err != nil {
|
|
t.Fatalf("list: %v", err)
|
|
}
|
|
|
|
if len(memories) != 5 {
|
|
t.Errorf("expected 5 memories, got %d", len(memories))
|
|
}
|
|
}
|
|
|
|
func TestSearch(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
s.Store(&Memory{Type: TypeFact, Key: "language", Content: "Go is the primary language"})
|
|
s.Store(&Memory{Type: TypeFact, Key: "editor", Content: "VSCode is the editor"})
|
|
s.Store(&Memory{Type: TypeContext, Key: "project", Content: "Muyue is a Go project"})
|
|
|
|
results, err := s.Search("Go language", 10)
|
|
if err != nil {
|
|
t.Fatalf("search: %v", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
t.Error("expected search results")
|
|
}
|
|
}
|
|
|
|
func TestRecallPreferences(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
s.Store(&Memory{Type: TypePreference, Key: "theme", Content: "dark"})
|
|
s.Store(&Memory{Type: TypePreference, Key: "lang", Content: "fr"})
|
|
s.Store(&Memory{Type: TypeFact, Key: "tool", Content: "go"})
|
|
|
|
prefs, err := s.RecallPreferences()
|
|
if err != nil {
|
|
t.Fatalf("recall preferences: %v", err)
|
|
}
|
|
|
|
if len(prefs) != 2 {
|
|
t.Errorf("expected 2 preferences, got %d", len(prefs))
|
|
}
|
|
}
|
|
|
|
func TestRecallRecent(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
s.Store(&Memory{Type: TypeFact, Key: "old", Content: "old fact"})
|
|
|
|
recent, err := s.RecallRecent(time.Now().Add(-1*time.Hour), 10)
|
|
if err != nil {
|
|
t.Fatalf("recall recent: %v", err)
|
|
}
|
|
|
|
if len(recent) == 0 {
|
|
t.Error("expected recent memories")
|
|
}
|
|
}
|
|
|
|
func TestStorePreference(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
if err := s.StorePreference("editor", "vim"); err != nil {
|
|
t.Fatalf("store preference: %v", err)
|
|
}
|
|
|
|
prefs, _ := s.RecallPreferences()
|
|
if len(prefs) != 1 {
|
|
t.Errorf("expected 1 preference, got %d", len(prefs))
|
|
}
|
|
}
|
|
|
|
func TestCount(t *testing.T) {
|
|
s := newTestStore(t)
|
|
|
|
s.Store(&Memory{Type: TypeFact, Key: "a", Content: "a"})
|
|
s.Store(&Memory{Type: TypeFact, Key: "b", Content: "b"})
|
|
|
|
count, err := s.Count()
|
|
if err != nil {
|
|
t.Fatalf("count: %v", err)
|
|
}
|
|
if count != 2 {
|
|
t.Errorf("expected 2, got %d", count)
|
|
}
|
|
}
|