package memory_test import ( "context" "testing" "time" "github.com/AmaniQuery/amaniquery/internal/memory" ) // MockEmbeddingClient for testing type MockEmbeddingClient struct{} func (m *MockEmbeddingClient) Generate(ctx context.Context, text string) ([]float32, error) { // Return a simple embedding based on text length embedding := make([]float32, 128) for i := range embedding { embedding[i] = float32(len(text)%10) / 10.0 } return embedding, nil } func (m *MockEmbeddingClient) GenerateBatch(ctx context.Context, texts []string) ([][]float32, error) { embeddings := make([][]float32, len(texts)) for i, text := range texts { embedding := make([]float32, 128) for j := range embedding { embedding[j] = float32(len(text)%10) / 10.0 } embeddings[i] = embedding } return embeddings, nil } // MockLLMClient for testing type MockLLMClient struct{} func (m *MockLLMClient) Generate(ctx context.Context, prompt string) (string, error) { return "Mock LLM response", nil } func TestLocalBackend_StoreAndRetrieve(t *testing.T) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) defer backend.Close() ctx := context.Background() // Store an entry entry := &memory.MemoryEntry{ ID: "test-1", Type: memory.EpisodicMemory, Content: "This is a test memory entry", Timestamp: time.Now(), Confidence: 0.9, UserID: "user-1", SessionID: "session-1", Source: "test", } err := backend.Store(ctx, entry) if err != nil { t.Fatalf("Store failed: %v", err) } // Retrieve the entry query := &memory.MemoryQuery{ UserID: "user-1", TopK: 10, } results, err := backend.Retrieve(ctx, query) if err != nil { t.Fatalf("Retrieve failed: %v", err) } if len(results) != 1 { t.Fatalf("Expected 1 result, got %d", len(results)) } if results[0].ID != "test-1" { t.Errorf("Expected ID 'test-1', got '%s'", results[0].ID) } } func TestLocalBackend_DeleteUserData(t *testing.T) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) defer backend.Close() ctx := context.Background() // Store multiple entries for a user for i := 0; i < 5; i++ { entry := &memory.MemoryEntry{ ID: "test-" + string(rune('0'+i)), Type: memory.EpisodicMemory, Content: "Test content", Timestamp: time.Now(), UserID: "user-gdpr", SessionID: "session-1", } backend.Store(ctx, entry) } // Verify entries exist query := &memory.MemoryQuery{UserID: "user-gdpr", TopK: 10} results, _ := backend.Retrieve(ctx, query) if len(results) != 5 { t.Fatalf("Expected 5 entries before delete, got %d", len(results)) } // Delete user data err := backend.DeleteUserData(ctx, "user-gdpr") if err != nil { t.Fatalf("DeleteUserData failed: %v", err) } // Verify entries are gone results, _ = backend.Retrieve(ctx, query) if len(results) != 0 { t.Fatalf("Expected 0 entries after delete, got %d", len(results)) } } func TestWorkingMemory_AddAndPrune(t *testing.T) { wm := memory.NewWorkingMemory("session-1", "user-1", 1024) // 1KB limit // Add entries until we exceed the limit for i := 0; i < 10; i++ { entry := &memory.MemoryEntry{ ID: "test-" + string(rune('0'+i)), Content: "This is a test entry with some content to take up space", } wm.Add(entry) } // Check that pruning occurred stats := wm.Stats() if stats.SizeBytes > 1024 { t.Errorf("Expected size <= 1024, got %d", stats.SizeBytes) } } func TestWorkingMemory_GetRecent(t *testing.T) { wm := memory.NewWorkingMemory("session-1", "user-1", 10*1024) // Add entries for i := 0; i < 5; i++ { entry := &memory.MemoryEntry{ ID: "test-" + string(rune('0'+i)), Content: "Entry content", } wm.Add(entry) } // Get recent 3 recent := wm.GetRecent(3) if len(recent) != 3 { t.Fatalf("Expected 3 recent entries, got %d", len(recent)) } // Should be the last 3 added if recent[0].ID != "test-2" { t.Errorf("Expected first recent to be test-2, got %s", recent[0].ID) } } func TestTemporalContext_RecencyScoring(t *testing.T) { tc := memory.NewTemporalContext("user-1", "session-1") // Create entries with different timestamps now := time.Now() recentEntry := &memory.MemoryEntry{ ID: "recent", Timestamp: now, Confidence: 1.0, } oldEntry := &memory.MemoryEntry{ ID: "old", Timestamp: now.Add(-24 * time.Hour), Confidence: 1.0, } recentScore := tc.CalculateRecencyScore(recentEntry) oldScore := tc.CalculateRecencyScore(oldEntry) if recentScore <= oldScore { t.Errorf("Recent entry should have higher score: recent=%f, old=%f", recentScore, oldScore) } } func TestContextWindowBuilder_Build(t *testing.T) { entries := []*memory.MemoryEntry{ { ID: "1", Type: memory.SemanticMemory, Content: "First entry", Timestamp: time.Now(), Source: "test", }, { ID: "2", Type: memory.EpisodicMemory, Content: "Second entry", Timestamp: time.Now(), Source: "test", }, } builder := memory.NewContextWindowBuilder(). WithMaxTokens(1000). WithFormatStyle(memory.FormatMarkdown) window := builder.Build(entries) if window.TotalTokens == 0 { t.Error("Expected non-zero token count") } if len(window.Entries) != 2 { t.Errorf("Expected 2 entries in window, got %d", len(window.Entries)) } if window.FormattedContext == "" { t.Error("Expected non-empty formatted context") } } func TestMemoryOrchestrator_StoreAndQuery(t *testing.T) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) embedder := &MockEmbeddingClient{} llmClient := &MockLLMClient{} orchestrator := memory.NewMemoryOrchestrator(backend, embedder, llmClient, config) defer orchestrator.Close() ctx := context.Background() // Store an entry entry := &memory.MemoryEntry{ Type: memory.SemanticMemory, Content: "The capital of France is Paris", UserID: "user-1", SessionID: "session-1", } err := orchestrator.Store(ctx, entry) if err != nil { t.Fatalf("Store failed: %v", err) } // Query memCtx, err := orchestrator.ProcessQuery(ctx, &memory.QueryRequest{ Query: "What is the capital of France?", UserID: "user-1", SessionID: "session-1", MaxTokens: 1000, }) if err != nil { t.Fatalf("ProcessQuery failed: %v", err) } if memCtx == nil { t.Fatal("Expected non-nil memory context") } } func TestAgentMemoryIntegration(t *testing.T) { config := memory.DefaultMemoryConfig() embedder := &MockEmbeddingClient{} llmClient := &MockLLMClient{} integration, err := memory.NewAgentMemoryIntegration(config, embedder, llmClient) if err != nil { t.Fatalf("Failed to create integration: %v", err) } defer integration.Stop() ctx := context.Background() // Store a conversation turn err = integration.StoreConversationTurn(ctx, "user-1", "session-1", "What is the weather?", "I don't have access to real-time weather data.") if err != nil { t.Fatalf("StoreConversationTurn failed: %v", err) } // Get context for a follow-up query memCtx, err := integration.GetContextForQuery(ctx, "user-1", "session-1", "Tell me more about the weather", 1000) if err != nil { t.Fatalf("GetContextForQuery failed: %v", err) } if memCtx == nil { t.Fatal("Expected non-nil memory context") } // Check metrics metrics := integration.GetMetrics() if metrics.TotalStores == 0 { t.Error("Expected some stores to be recorded") } } func TestMemoryWorker(t *testing.T) { config := memory.DefaultMemoryConfig() embedder := &MockEmbeddingClient{} llmClient := &MockLLMClient{} integration, err := memory.NewAgentMemoryIntegration(config, embedder, llmClient) if err != nil { t.Fatalf("Failed to create integration: %v", err) } worker := memory.NewMemoryWorker(integration, 2) worker.Start() defer worker.Stop() ctx := context.Background() // Submit async work entry := &memory.MemoryEntry{ Type: memory.EpisodicMemory, Content: "Async stored entry", UserID: "user-1", SessionID: "session-1", } result, err := worker.SubmitWorkWithResult(ctx, memory.MemoryWorkItem{ Type: memory.WorkStoreEntry, Data: entry, }) if err != nil { t.Fatalf("SubmitWorkWithResult failed: %v", err) } if !result.Success { t.Errorf("Work item failed: %v", result.Error) } } func TestGDPRManager_ExportUserData(t *testing.T) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) gdprManager := memory.NewGDPRManager(backend, nil) ctx := context.Background() // Store some data for i := 0; i < 3; i++ { entry := &memory.MemoryEntry{ ID: "export-test-" + string(rune('0'+i)), Type: memory.SemanticMemory, Content: "Test content for export", Timestamp: time.Now(), UserID: "export-user", SessionID: "session-1", } backend.Store(ctx, entry) } // Export user data export, err := gdprManager.ExportUserData(ctx, "export-user", "admin") if err != nil { t.Fatalf("ExportUserData failed: %v", err) } if export == nil { t.Fatal("Expected non-nil export") } if export.EntryCount != 3 { t.Errorf("Expected 3 entries in export, got %d", export.EntryCount) } } // Benchmark tests func BenchmarkLocalBackend_Store(b *testing.B) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) defer backend.Close() ctx := context.Background() b.ResetTimer() for i := 0; i < b.N; i++ { entry := &memory.MemoryEntry{ ID: "bench-" + string(rune(i%256)), Type: memory.EpisodicMemory, Content: "Benchmark test content", Timestamp: time.Now(), UserID: "bench-user", SessionID: "bench-session", } backend.Store(ctx, entry) } } func BenchmarkLocalBackend_Retrieve(b *testing.B) { config := memory.DefaultMemoryConfig() backend := memory.NewLocalMemoryBackend(config) defer backend.Close() ctx := context.Background() // Pre-populate for i := 0; i < 1000; i++ { entry := &memory.MemoryEntry{ ID: "bench-" + string(rune(i%256)) + string(rune(i/256)), Type: memory.EpisodicMemory, Content: "Benchmark test content for retrieval", Timestamp: time.Now(), UserID: "bench-user", SessionID: "bench-session", } backend.Store(ctx, entry) } query := &memory.MemoryQuery{ UserID: "bench-user", TopK: 10, } b.ResetTimer() for i := 0; i < b.N; i++ { backend.Retrieve(ctx, query) } } func BenchmarkContextWindowBuilder(b *testing.B) { entries := make([]*memory.MemoryEntry, 100) for i := range entries { entries[i] = &memory.MemoryEntry{ ID: "bench-" + string(rune(i%256)), Type: memory.SemanticMemory, Content: "This is benchmark content for context window building", Timestamp: time.Now(), Source: "benchmark", } } builder := memory.NewContextWindowBuilder().WithMaxTokens(4000) b.ResetTimer() for i := 0; i < b.N; i++ { builder.Build(entries) } }