Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
evidence_test.go10.5 kB
package inference import ( "sync" "testing" "time" "github.com/orneryd/nornicdb/pkg/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewEvidenceBuffer(t *testing.T) { eb := NewEvidenceBuffer() require.NotNil(t, eb) assert.Empty(t, eb.entries) assert.NotEmpty(t, eb.thresholds) } func TestEvidenceBuffer_FeatureDisabled(t *testing.T) { // Disable feature cleanup := config.WithEvidenceBufferingDisabled() defer cleanup() eb := NewEvidenceBuffer() // Single evidence should immediately return true (materialize) result := eb.AddEvidence("a", "b", "relates_to", 0.5, "coaccess", "session-1") assert.True(t, result, "Should materialize immediately when feature disabled") } func TestEvidenceBuffer_RequiresThreshold(t *testing.T) { // Enable feature cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() // Use custom low threshold for testing eb.SetThreshold("test_label", EvidenceThreshold{ MinCount: 3, MinScore: 0.5, MinSessions: 2, MaxAge: 1 * time.Hour, }) // First evidence - should not materialize result := eb.AddEvidence("a", "b", "test_label", 0.6, "coaccess", "session-1") assert.False(t, result, "Should not materialize with only 1 evidence") // Second evidence - different session, still not enough result = eb.AddEvidence("a", "b", "test_label", 0.6, "similarity", "session-2") assert.False(t, result, "Should not materialize with only 2 evidence") // Third evidence - now meets count and session threshold result = eb.AddEvidence("a", "b", "test_label", 0.6, "topology", "session-2") assert.True(t, result, "Should materialize after meeting threshold") } func TestEvidenceBuffer_LowScoreDoesNotMaterialize(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test_label", EvidenceThreshold{ MinCount: 2, MinScore: 0.7, // High threshold MinSessions: 1, MaxAge: 1 * time.Hour, }) // Add enough count but low scores eb.AddEvidence("a", "b", "test_label", 0.3, "coaccess", "session-1") result := eb.AddEvidence("a", "b", "test_label", 0.3, "similarity", "session-1") assert.False(t, result, "Should not materialize with low average score") // Check evidence ev := eb.GetEvidence("a", "b", "test_label") assert.Equal(t, 2, ev.Count) assert.Equal(t, 0.3, ev.ScoreAvg) // Average of 0.3 and 0.3 } func TestEvidenceBuffer_CheckThreshold(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test_label", EvidenceThreshold{ MinCount: 2, MinScore: 0.5, MinSessions: 1, MaxAge: 1 * time.Hour, }) // No evidence met, reason := eb.CheckThreshold("a", "b", "test_label") assert.False(t, met) assert.Contains(t, reason, "no evidence") // Add some evidence eb.AddEvidence("a", "b", "test_label", 0.6, "coaccess", "session-1") met, reason = eb.CheckThreshold("a", "b", "test_label") assert.False(t, met) assert.Contains(t, reason, "insufficient count") // Add enough evidence eb.AddEvidence("a", "b", "test_label", 0.6, "similarity", "session-1") met, reason = eb.CheckThreshold("a", "b", "test_label") assert.True(t, met) assert.Contains(t, reason, "threshold met") } func TestEvidenceBuffer_ExpiredEvidence(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test_label", EvidenceThreshold{ MinCount: 2, MinScore: 0.5, MinSessions: 1, MaxAge: 1 * time.Millisecond, // Very short for testing }) // Add evidence eb.AddEvidence("a", "b", "test_label", 0.6, "coaccess", "session-1") // Wait for expiry time.Sleep(5 * time.Millisecond) // Add second evidence - should not materialize because first is expired result := eb.AddEvidence("a", "b", "test_label", 0.6, "similarity", "session-1") assert.False(t, result, "Should not materialize with expired evidence") } func TestEvidenceBuffer_GetEvidence(t *testing.T) { eb := NewEvidenceBuffer() // No evidence ev := eb.GetEvidence("a", "b", "test") assert.Nil(t, ev) // Add evidence eb.AddEvidence("a", "b", "test", 0.7, "coaccess", "session-1") eb.AddEvidence("a", "b", "test", 0.8, "similarity", "session-2") ev = eb.GetEvidence("a", "b", "test") require.NotNil(t, ev) assert.Equal(t, 2, ev.Count) assert.Equal(t, 1.5, ev.ScoreSum) assert.Equal(t, 0.75, ev.ScoreAvg) assert.Len(t, ev.Sessions, 2) assert.Len(t, ev.Signals, 2) } func TestEvidenceBuffer_ClearEntry(t *testing.T) { eb := NewEvidenceBuffer() eb.AddEvidence("a", "b", "test", 0.5, "coaccess", "s1") eb.AddEvidence("c", "d", "test", 0.5, "coaccess", "s1") assert.Equal(t, 2, eb.Size()) eb.ClearEntry("a", "b", "test") assert.Equal(t, 1, eb.Size()) assert.Nil(t, eb.GetEvidence("a", "b", "test")) assert.NotNil(t, eb.GetEvidence("c", "d", "test")) } func TestEvidenceBuffer_Clear(t *testing.T) { eb := NewEvidenceBuffer() eb.AddEvidence("a", "b", "test", 0.5, "coaccess", "s1") eb.AddEvidence("c", "d", "test", 0.5, "coaccess", "s1") assert.Equal(t, 2, eb.Size()) eb.Clear() assert.Equal(t, 0, eb.Size()) } func TestEvidenceBuffer_Cleanup(t *testing.T) { eb := NewEvidenceBuffer() eb.SetThreshold("test", EvidenceThreshold{ MinCount: 1, MinScore: 0.1, MinSessions: 1, MaxAge: 1 * time.Millisecond, }) eb.AddEvidence("a", "b", "test", 0.5, "coaccess", "s1") eb.AddEvidence("c", "d", "test", 0.5, "coaccess", "s1") // Wait for expiry time.Sleep(5 * time.Millisecond) // Add fresh entry eb.SetThreshold("fresh", EvidenceThreshold{ MinCount: 1, MinScore: 0.1, MinSessions: 1, MaxAge: 1 * time.Hour, }) eb.AddEvidence("e", "f", "fresh", 0.5, "coaccess", "s1") assert.Equal(t, 3, eb.Size()) removed := eb.Cleanup() assert.Equal(t, 2, removed) assert.Equal(t, 1, eb.Size()) } func TestEvidenceBuffer_Stats(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test", EvidenceThreshold{ MinCount: 2, MinScore: 0.5, MinSessions: 1, MaxAge: 1 * time.Hour, }) // Initial stats stats := eb.Stats() assert.Equal(t, int64(0), stats.TotalEntries) assert.Equal(t, int64(0), stats.TotalAdded) // Add evidence eb.AddEvidence("a", "b", "test", 0.6, "coaccess", "s1") eb.AddEvidence("a", "b", "test", 0.6, "similarity", "s1") // This should materialize stats = eb.Stats() assert.Equal(t, int64(1), stats.TotalEntries) assert.Equal(t, int64(2), stats.TotalAdded) assert.Equal(t, int64(1), stats.TotalMaterialized) assert.Equal(t, 0.5, stats.MaterializeRate) } func TestEvidenceBuffer_Concurrent(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test", EvidenceThreshold{ MinCount: 100, MinScore: 0.1, MinSessions: 1, MaxAge: 1 * time.Hour, }) var wg sync.WaitGroup iterations := 50 // Concurrent writes for i := 0; i < iterations; i++ { wg.Add(1) go func(i int) { defer wg.Done() eb.AddEvidence("a", "b", "test", 0.5, "coaccess", "session-1") }(i) } // Concurrent reads for i := 0; i < iterations; i++ { wg.Add(1) go func() { defer wg.Done() eb.GetEvidence("a", "b", "test") }() } wg.Wait() ev := eb.GetEvidence("a", "b", "test") require.NotNil(t, ev) assert.Equal(t, iterations, ev.Count) } func TestEvidenceBuffer_DifferentLabels(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("easy", EvidenceThreshold{ MinCount: 1, MinScore: 0.1, MinSessions: 1, MaxAge: 1 * time.Hour, }) eb.SetThreshold("hard", EvidenceThreshold{ MinCount: 10, MinScore: 0.9, MinSessions: 5, MaxAge: 1 * time.Hour, }) // Same edge pair, different labels resultEasy := eb.AddEvidence("a", "b", "easy", 0.5, "coaccess", "s1") resultHard := eb.AddEvidence("a", "b", "hard", 0.5, "coaccess", "s1") assert.True(t, resultEasy, "Easy label should materialize") assert.False(t, resultHard, "Hard label should not materialize") } func TestEvidenceBuffer_WithMetadata(t *testing.T) { eb := NewEvidenceBuffer() metadata := map[string]interface{}{ "source": "api", "timestamp": time.Now().Unix(), } eb.AddEvidenceWithMetadata("a", "b", "test", 0.5, "coaccess", "s1", metadata) ev := eb.GetEvidence("a", "b", "test") require.NotNil(t, ev) assert.Equal(t, "api", ev.Metadata["source"]) } func TestEvidenceBuffer_GetPendingEdges(t *testing.T) { cleanup := config.WithEvidenceBufferingEnabled() defer cleanup() eb := NewEvidenceBuffer() eb.SetThreshold("test", EvidenceThreshold{ MinCount: 4, MinScore: 0.5, MinSessions: 2, MaxAge: 1 * time.Hour, }) // Add partial evidence (50% progress roughly) eb.AddEvidence("a", "b", "test", 0.6, "coaccess", "s1") eb.AddEvidence("a", "b", "test", 0.6, "similarity", "s2") // Get pending edges with at least 50% progress pending := eb.GetPendingEdges(0.5) assert.Len(t, pending, 1) assert.Equal(t, "a", pending[0].Key.Src) } func TestGlobalEvidenceBuffer(t *testing.T) { ResetGlobalEvidenceBuffer() eb1 := GlobalEvidenceBuffer() eb2 := GlobalEvidenceBuffer() assert.Same(t, eb1, eb2) ResetGlobalEvidenceBuffer() eb3 := GlobalEvidenceBuffer() assert.NotSame(t, eb1, eb3) } func TestEvidenceBuffer_DefaultThresholds(t *testing.T) { eb := NewEvidenceBuffer() // Verify default thresholds are set threshold := eb.GetThreshold("relates_to") assert.Equal(t, 3, threshold.MinCount) assert.Equal(t, 0.5, threshold.MinScore) assert.Equal(t, 2, threshold.MinSessions) // Unknown label should get default threshold threshold = eb.GetThreshold("unknown_label") assert.Equal(t, DefaultEvidenceThreshold.MinCount, threshold.MinCount) } func TestEvidenceKey_String(t *testing.T) { key := EvidenceKey{Src: "node1", Dst: "node2", Label: "relates_to"} assert.Equal(t, "node1:node2:relates_to", key.String()) } func TestEvidenceBufferWithOptions(t *testing.T) { eb := NewEvidenceBufferWithOptions( WithThreshold("custom", EvidenceThreshold{ MinCount: 5, MinScore: 0.8, MinSessions: 3, MaxAge: 2 * time.Hour, }), ) threshold := eb.GetThreshold("custom") assert.Equal(t, 5, threshold.MinCount) assert.Equal(t, 0.8, threshold.MinScore) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server