package activity import ( "context" "strings" "testing" "github.com/stretchr/testify/assert" ) var testCtx = context.Background() func TestExtractEntities_WikiLinks(t *testing.T) { sa := NewSynthesisActivities(nil) // No client needed for extraction entities, err := sa.ExtractEntitiesActivity(testCtx, "chunk-1", "Deploy [[Kubernetes]] with [[ArgoCD]]") assert.NoError(t, err) names := entityNames(entities) assert.Contains(t, names, "Kubernetes") assert.Contains(t, names, "ArgoCD") // Wiki links get high confidence for _, e := range entities { if e.Name == "Kubernetes" || e.Name == "ArgoCD" { assert.Equal(t, 0.95, e.Confidence) assert.Equal(t, "reference", e.EntityType) } } } func TestExtractEntities_ProperNouns(t *testing.T) { sa := NewSynthesisActivities(nil) entities, err := sa.ExtractEntitiesActivity(testCtx, "chunk-2", "Redis runs on Ubuntu Server") assert.NoError(t, err) names := entityNames(entities) assert.Contains(t, names, "Redis") assert.Contains(t, names, "Ubuntu Server") } func TestExtractEntities_TechnicalTerms(t *testing.T) { sa := NewSynthesisActivities(nil) entities, err := sa.ExtractEntitiesActivity(testCtx, "chunk-3", "Set MAX_RETRIES and use camelCase variables") assert.NoError(t, err) names := entityNames(entities) assert.Contains(t, names, "MAX_RETRIES") assert.Contains(t, names, "camelCase") } func TestExtractEntities_Deduplication(t *testing.T) { sa := NewSynthesisActivities(nil) entities, err := sa.ExtractEntitiesActivity(testCtx, "chunk-4", "[[Redis]] uses Redis for caching") assert.NoError(t, err) count := 0 for _, e := range entities { if e.Name == "Redis" { count++ } } assert.Equal(t, 1, count, "Redis should appear only once") } func TestExtractFacts_VerbPatterns(t *testing.T) { sa := NewSynthesisActivities(nil) entities := []ExtractedEntity{ {Name: "Kubernetes", EntityType: "tool"}, {Name: "Docker", EntityType: "tool"}, } facts, err := sa.ExtractFactsActivity(testCtx, "chunk-5", "Kubernetes uses Docker for container runtime. Redis depends on TCP", entities) assert.NoError(t, err) assert.Greater(t, len(facts), 0) // Find the "uses" fact found := false for _, f := range facts { if f.Predicate == "uses" && f.Subject == "Kubernetes" { found = true assert.Greater(t, f.Confidence, 0.7) // Boosted by known entities } } assert.True(t, found, "should find Kubernetes uses Docker fact") } func TestExtractFacts_EmptyText(t *testing.T) { sa := NewSynthesisActivities(nil) facts, err := sa.ExtractFactsActivity(testCtx, "chunk-6", "", nil) assert.NoError(t, err) assert.Empty(t, facts) } func TestContradicts(t *testing.T) { assert.True(t, contradicts("Kubernetes uses port 8080", ExtractedFact{ Subject: "Kubernetes", Predicate: "uses_port", Object: "9090", })) assert.False(t, contradicts("Kubernetes uses port 8080", ExtractedFact{ Subject: "Kubernetes", Predicate: "uses_port", Object: "8080", })) } func TestContainsSubject(t *testing.T) { assert.True(t, containsSubject("Kubernetes runs on Linux", "kubernetes")) assert.False(t, containsSubject("Docker runs on Linux", "kubernetes")) } func TestIsCommonWord(t *testing.T) { assert.True(t, isCommonWord("The")) assert.True(t, isCommonWord("This")) assert.False(t, isCommonWord("Kubernetes")) assert.False(t, isCommonWord("Redis")) } func TestClassifyEntity(t *testing.T) { assert.Equal(t, "tool", classifyEntity("Kubernetes")) assert.Equal(t, "tool", classifyEntity("Docker")) assert.Equal(t, "tool", classifyEntity("Redis")) assert.Equal(t, "concept", classifyEntity("SomeRandomThing")) } // --- Tests for ExtractFactsActivity Validation --- func TestExtractFacts_WithValidation(t *testing.T) { sa := NewSynthesisActivities(nil) // Test with very long object that should be truncated longText := "Kubernetes uses " + strings.Repeat("very long object name that should be truncated ", 20) entities := []ExtractedEntity{} facts, err := sa.ExtractFactsActivity(testCtx, "chunk-1", longText, entities) assert.NoError(t, err) // Verify no fact has object > 500 chars for _, f := range facts { assert.LessOrEqual(t, len(f.Object), 500, "object should be truncated to 500 chars") } } func TestExtractFacts_SkipsEmpty(t *testing.T) { sa := NewSynthesisActivities(nil) // Text with empty patterns that would extract nothing text := "Something uses and other things" entities := []ExtractedEntity{} facts, err := sa.ExtractFactsActivity(testCtx, "chunk-1", text, entities) assert.NoError(t, err) // Verify no empty facts for _, f := range facts { assert.NotEmpty(t, f.Subject, "subject should not be empty") assert.NotEmpty(t, f.Object, "object should not be empty") } } func TestPersistSynthesis_EmptyInput(t *testing.T) { // Test that empty input is handled (no entities or facts to persist) // Note: This test requires a mock memory service; for now we just test structure input := PersistInput{ ChunkID: "chunk-123", Project: "test", Source: "test://1", Kind: "L1", Entities: []ExtractedEntity{}, // Empty Facts: []ExtractedFact{}, // Empty Contradictions: []ContradictionResult{}, } // Verify input structure is valid assert.Equal(t, "chunk-123", input.ChunkID) assert.Equal(t, 0, len(input.Entities)) assert.Equal(t, 0, len(input.Facts)) } // helper func entityNames(entities []ExtractedEntity) []string { names := make([]string, len(entities)) for i, e := range entities { names[i] = e.Name } return names }