feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
This commit is contained in:
@@ -0,0 +1,208 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user