package shellknowledge import ( "context" "os" "path/filepath" "testing" "log/slog" "github.com/enmanuel/agents/pkg/knowledge" ) func testStore(t *testing.T) (*FileStore, string) { t.Helper() dir := t.TempDir() knowledgeDir := filepath.Join(dir, "knowledge") dbPath := filepath.Join(dir, "data", "knowledge.db") logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) store, err := New(knowledgeDir, dbPath, logger) if err != nil { t.Fatal(err) } t.Cleanup(func() { store.Close() }) return store, knowledgeDir } func TestValidSlug(t *testing.T) { tests := []struct { slug string want bool }{ {"go-patterns", true}, {"ab", true}, {"a-b", true}, {"abc123", true}, {"a", false}, // too short {"A-B", false}, // uppercase {"-bad", false}, // starts with hyphen {"bad-", false}, // ends with hyphen {"has space", false}, // space {"has_underscore", false}, // underscore {"", false}, } for _, tt := range tests { if got := ValidSlug(tt.slug); got != tt.want { t.Errorf("ValidSlug(%q) = %v, want %v", tt.slug, got, tt.want) } } } func TestPutAndGet(t *testing.T) { store, _ := testStore(t) ctx := context.Background() doc := knowledge.Document{ Slug: "test-doc", Content: "# Test Document\n\nThis is a test.", } if err := store.Put(ctx, doc); err != nil { t.Fatal(err) } got, err := store.Get(ctx, "test-doc") if err != nil { t.Fatal(err) } if got.Content != doc.Content { t.Errorf("content mismatch: got %q, want %q", got.Content, doc.Content) } if got.Title != "Test Document" { t.Errorf("title = %q, want %q", got.Title, "Test Document") } } func TestPutInvalidSlug(t *testing.T) { store, _ := testStore(t) ctx := context.Background() err := store.Put(ctx, knowledge.Document{Slug: "BAD", Content: "test"}) if err == nil { t.Error("expected error for invalid slug") } } func TestPutTooLarge(t *testing.T) { store, _ := testStore(t) ctx := context.Background() bigContent := make([]byte, 65*1024) for i := range bigContent { bigContent[i] = 'x' } err := store.Put(ctx, knowledge.Document{Slug: "too-big", Content: string(bigContent)}) if err == nil { t.Error("expected error for oversized document") } } func TestSyncAndSearch(t *testing.T) { store, knowledgeDir := testStore(t) ctx := context.Background() // Write files directly to disk os.WriteFile(filepath.Join(knowledgeDir, "go-patterns.md"), []byte("# Go Patterns\n\nUse interfaces for dependency injection."), 0o644) os.WriteFile(filepath.Join(knowledgeDir, "matrix-tips.md"), []byte("# Matrix Tips\n\nUse mautrix-go for Matrix bots."), 0o644) if err := store.Sync(ctx); err != nil { t.Fatal(err) } // Search for "interfaces" results, err := store.Search(ctx, "interfaces", 5) if err != nil { t.Fatal(err) } if len(results) == 0 { t.Fatal("expected at least 1 search result") } if results[0].Slug != "go-patterns" { t.Errorf("expected slug go-patterns, got %q", results[0].Slug) } } func TestList(t *testing.T) { store, _ := testStore(t) ctx := context.Background() // Empty initially docs, err := store.List(ctx) if err != nil { t.Fatal(err) } if len(docs) != 0 { t.Errorf("expected 0 docs, got %d", len(docs)) } // Add two docs store.Put(ctx, knowledge.Document{Slug: "alpha", Content: "# Alpha\nContent A"}) store.Put(ctx, knowledge.Document{Slug: "beta", Content: "# Beta\nContent B"}) docs, err = store.List(ctx) if err != nil { t.Fatal(err) } if len(docs) != 2 { t.Fatalf("expected 2 docs, got %d", len(docs)) } } func TestDelete(t *testing.T) { store, knowledgeDir := testStore(t) ctx := context.Background() store.Put(ctx, knowledge.Document{Slug: "to-delete", Content: "# Delete Me\nGoodbye"}) // Verify file exists if _, err := os.Stat(filepath.Join(knowledgeDir, "to-delete.md")); err != nil { t.Fatal("file should exist after Put") } if err := store.Delete(ctx, "to-delete"); err != nil { t.Fatal(err) } // File removed if _, err := os.Stat(filepath.Join(knowledgeDir, "to-delete.md")); !os.IsNotExist(err) { t.Error("file should be removed after Delete") } // Not in index _, err := store.Get(ctx, "to-delete") if err == nil { t.Error("expected error for deleted document") } } func TestGetNotFound(t *testing.T) { store, _ := testStore(t) ctx := context.Background() _, err := store.Get(ctx, "nonexistent") if err == nil { t.Error("expected error for nonexistent document") } } func TestExtractTitle(t *testing.T) { tests := []struct { content string slug string want string }{ {"# My Title\nBody", "slug", "My Title"}, {"No heading here", "my-doc", "My doc"}, {"", "empty-doc", "Empty doc"}, {"\n\n# Late Title\n", "slug", "Late Title"}, } for _, tt := range tests { got := extractTitle(tt.content, tt.slug) if got != tt.want { t.Errorf("extractTitle(%q, %q) = %q, want %q", tt.content, tt.slug, got, tt.want) } } }