mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
396 lines
14 KiB
Go
396 lines
14 KiB
Go
package querybuilder
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestContradictionDetection(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
hasContradiction bool
|
|
expectedErrors []string
|
|
}{
|
|
{
|
|
name: "Simple equality contradiction",
|
|
query: `service.name = 'redis' service.name='route' http.status_code=200`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "Equal and not equal same value",
|
|
query: `service.name = 'redis' AND service.name != 'redis'`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "Range contradiction",
|
|
query: `http.status_code > 500 AND http.status_code < 400`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"http.status_code"},
|
|
},
|
|
{
|
|
name: "IN and NOT IN overlap",
|
|
query: `service.name IN ('redis', 'mysql') AND service.name NOT IN ('redis', 'postgres')`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "EXISTS and NOT EXISTS",
|
|
query: `custom.tag EXISTS AND custom.tag NOT EXISTS`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"custom.tag"},
|
|
},
|
|
{
|
|
name: "Equal and NOT IN containing value",
|
|
query: `service.name = 'redis' AND service.name NOT IN ('redis', 'mysql')`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "Non-overlapping BETWEEN ranges",
|
|
query: `http.status_code BETWEEN 200 AND 299 AND http.status_code BETWEEN 400 AND 499`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"http.status_code"},
|
|
},
|
|
{
|
|
name: "Valid query with no contradictions",
|
|
query: `service.name = 'redis' AND http.status_code >= 200 AND http.status_code < 300`,
|
|
hasContradiction: false,
|
|
expectedErrors: []string{},
|
|
},
|
|
{
|
|
name: "OR expression - no contradiction",
|
|
query: `service.name = 'redis' OR service.name = 'mysql'`,
|
|
hasContradiction: false,
|
|
expectedErrors: []string{},
|
|
},
|
|
{
|
|
name: "Complex valid query",
|
|
query: `(service.name = 'redis' OR service.name = 'mysql') AND http.status_code = 200`,
|
|
hasContradiction: false,
|
|
expectedErrors: []string{},
|
|
},
|
|
{
|
|
name: "Negated contradiction",
|
|
query: `NOT (service.name = 'redis') AND service.name = 'redis'`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "Multiple field contradictions",
|
|
query: `service.name = 'redis' AND service.name = 'mysql' AND http.status_code = 200 AND http.status_code = 404`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name", "http.status_code"},
|
|
},
|
|
{
|
|
name: "Implicit AND with contradiction",
|
|
query: `service.name='redis' service.name='mysql'`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
{
|
|
name: "Equal with incompatible range",
|
|
query: `http.status_code = 200 AND http.status_code > 300`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"http.status_code"},
|
|
},
|
|
{
|
|
name: "Complex nested contradiction",
|
|
query: `(service.name = 'redis' AND http.status_code = 200) AND (service.name = 'mysql' AND http.status_code = 200)`,
|
|
hasContradiction: true,
|
|
expectedErrors: []string{"service.name"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
contradictions, err := DetectContradictions(tt.query)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
hasContradiction := len(contradictions) > 0
|
|
|
|
if hasContradiction != tt.hasContradiction {
|
|
t.Errorf("expected hasContradiction=%v, got %v. Contradictions: %v",
|
|
tt.hasContradiction, hasContradiction, contradictions)
|
|
}
|
|
|
|
if tt.hasContradiction {
|
|
// Check that we found contradictions for expected fields
|
|
for _, expectedField := range tt.expectedErrors {
|
|
found := false
|
|
for _, contradiction := range contradictions {
|
|
if strings.Contains(contradiction, expectedField) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("expected contradiction for field %s, but not found. Got: %v",
|
|
expectedField, contradictions)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestComplexNestedContradictions(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
hasContradiction bool
|
|
expectedFields []string
|
|
description string
|
|
}{
|
|
// Complex nested AND/OR combinations
|
|
{
|
|
name: "Nested AND with contradiction in inner expression",
|
|
query: `(service.name = 'redis' AND http.status_code = 200) AND (service.name = 'mysql' AND http.status_code = 200)`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "Inner ANDs both valid, but combined they contradict on service.name",
|
|
},
|
|
{
|
|
name: "OR with contradictory AND branches - no contradiction",
|
|
query: `(service.name = 'redis' AND service.name = 'mysql') OR (http.status_code = 200)`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "First branch impossible",
|
|
},
|
|
{
|
|
name: "Deeply nested contradiction",
|
|
query: `((service.name = 'redis' AND (http.status_code > 200 AND http.status_code < 200)) AND region = 'us-east')`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"http.status_code"},
|
|
description: "Nested impossible range condition",
|
|
},
|
|
{
|
|
name: "Multiple field contradictions in nested structure",
|
|
query: `(service.name = 'redis' AND service.name != 'redis') AND (http.status_code = 200 AND http.status_code = 404)`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name", "http.status_code"},
|
|
description: "Both nested expressions have contradictions",
|
|
},
|
|
|
|
// Complex BETWEEN contradictions
|
|
{
|
|
name: "BETWEEN with overlapping ranges - valid",
|
|
query: `http.status_code BETWEEN 200 AND 299 AND http.status_code BETWEEN 250 AND 350`,
|
|
hasContradiction: false,
|
|
expectedFields: []string{},
|
|
description: "Ranges overlap at 250-299, so valid",
|
|
},
|
|
{
|
|
name: "BETWEEN with exact value outside range",
|
|
query: `http.status_code = 500 AND http.status_code BETWEEN 200 AND 299`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"http.status_code"},
|
|
description: "Exact value outside BETWEEN range",
|
|
},
|
|
{
|
|
name: "Multiple BETWEEN with no overlap",
|
|
query: `(latency BETWEEN 100 AND 200) AND (latency BETWEEN 300 AND 400) AND (latency BETWEEN 500 AND 600)`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"latency"},
|
|
description: "Three non-overlapping ranges",
|
|
},
|
|
|
|
// Complex IN/NOT IN combinations
|
|
{
|
|
name: "IN with nested NOT IN contradiction",
|
|
query: `service.name IN ('redis', 'mysql', 'postgres') AND (service.name NOT IN ('mysql', 'postgres') AND service.name NOT IN ('redis'))`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "Combined NOT IN excludes all values from IN",
|
|
},
|
|
{
|
|
name: "Complex valid IN/NOT IN",
|
|
query: `service.name IN ('redis', 'mysql', 'postgres') AND service.name NOT IN ('mongodb', 'cassandra')`,
|
|
hasContradiction: false,
|
|
expectedFields: []string{},
|
|
description: "Non-overlapping IN and NOT IN lists",
|
|
},
|
|
|
|
// Implicit AND with complex expressions
|
|
{
|
|
name: "Implicit AND with nested contradiction",
|
|
query: `service.name='redis' (http.status_code > 500 http.status_code < 400)`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"http.status_code"},
|
|
description: "Implicit AND creates impossible range",
|
|
},
|
|
{
|
|
name: "Mixed implicit and explicit AND",
|
|
query: `service.name='redis' service.name='mysql' AND http.status_code=200`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "Implicit AND between service names creates contradiction",
|
|
},
|
|
|
|
// NOT operator complexities
|
|
{
|
|
name: "Double negation with contradiction",
|
|
query: `NOT (NOT (service.name = 'redis')) AND service.name = 'mysql'`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "Double NOT cancels out, creating contradiction",
|
|
},
|
|
|
|
// Range conditions with multiple operators
|
|
{
|
|
name: "Chained range conditions creating impossible range",
|
|
query: `value > 100 AND value < 200 AND value > 300 AND value < 400`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"value"},
|
|
description: "Multiple ranges that cannot be satisfied simultaneously",
|
|
},
|
|
{
|
|
name: "Valid narrowing range",
|
|
query: `value > 100 AND value < 400 AND value > 200 AND value < 300`,
|
|
hasContradiction: false,
|
|
expectedFields: []string{},
|
|
description: "Ranges narrow down to valid 200-300 range",
|
|
},
|
|
|
|
// Mixed operator types
|
|
{
|
|
name: "LIKE pattern with exact value contradiction",
|
|
query: `service.name = 'redis-cache-01' AND service.name LIKE 'mysql%'`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"service.name"},
|
|
description: "Exact value doesn't match LIKE pattern",
|
|
},
|
|
{
|
|
name: "EXISTS with value contradiction",
|
|
query: `custom.tag EXISTS AND custom.tag = 'value' AND custom.tag NOT EXISTS`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"custom.tag"},
|
|
description: "Field both exists with value and doesn't exist",
|
|
},
|
|
|
|
// Edge cases
|
|
{
|
|
name: "Same field different types",
|
|
query: `http.status_code = '200' AND http.status_code = 200`,
|
|
hasContradiction: false, // Depends on type coercion
|
|
expectedFields: []string{},
|
|
description: "Same value different types - implementation dependent",
|
|
},
|
|
{
|
|
name: "Complex parentheses with valid expression",
|
|
query: `((((service.name = 'redis')))) AND ((((http.status_code = 200))))`,
|
|
hasContradiction: false,
|
|
expectedFields: []string{},
|
|
description: "Multiple parentheses levels but valid expression",
|
|
},
|
|
|
|
// Real-world complex scenarios
|
|
{
|
|
name: "Monitoring query with impossible conditions",
|
|
query: `service.name = 'api-gateway' AND
|
|
http.status_code >= 500 AND
|
|
http.status_code < 500 AND
|
|
region IN ('us-east-1', 'us-west-2') AND
|
|
region NOT IN ('us-east-1', 'us-west-2', 'eu-west-1')`,
|
|
hasContradiction: true,
|
|
expectedFields: []string{"http.status_code", "region"},
|
|
description: "Multiple contradictions in monitoring query",
|
|
},
|
|
{
|
|
name: "Valid complex monitoring query",
|
|
query: `(service.name = 'api-gateway' OR service.name = 'web-server') AND
|
|
http.status_code >= 400 AND
|
|
http.status_code < 500 AND
|
|
region IN ('us-east-1', 'us-west-2') AND
|
|
latency > 1000`,
|
|
hasContradiction: false,
|
|
expectedFields: []string{},
|
|
description: "Complex but valid monitoring conditions",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
contradictions, err := DetectContradictions(tt.query)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
hasContradiction := len(contradictions) > 0
|
|
|
|
if hasContradiction != tt.hasContradiction {
|
|
t.Errorf("Test: %s\nDescription: %s\nExpected hasContradiction=%v, got %v\nContradictions: %v",
|
|
tt.name, tt.description, tt.hasContradiction, hasContradiction, contradictions)
|
|
}
|
|
|
|
if tt.hasContradiction {
|
|
// Check that we found contradictions for expected fields
|
|
for _, expectedField := range tt.expectedFields {
|
|
found := false
|
|
for _, contradiction := range contradictions {
|
|
if strings.Contains(contradiction, expectedField) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Errorf("Test: %s\nExpected contradiction for field %s, but not found.\nGot: %v",
|
|
tt.name, expectedField, contradictions)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExpressionLevelHandling(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
hasContradiction bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "OR at top level - no contradiction",
|
|
query: `service.name = 'redis' OR service.name = 'mysql'`,
|
|
hasContradiction: false,
|
|
description: "Top level OR should not check for contradictions",
|
|
},
|
|
{
|
|
name: "AND within OR - contradiction only in AND branch",
|
|
query: `(service.name = 'redis' AND service.name = 'mysql') OR http.status_code = 200`,
|
|
hasContradiction: true,
|
|
description: "Contradiction in one OR branch doesn't make whole expression contradictory",
|
|
},
|
|
{
|
|
name: "Nested OR within AND - valid",
|
|
query: `http.status_code = 200 AND (service.name = 'redis' OR service.name = 'mysql')`,
|
|
hasContradiction: false,
|
|
description: "OR within AND is valid",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
contradictions, err := DetectContradictions(tt.query)
|
|
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
hasContradiction := len(contradictions) > 0
|
|
|
|
if hasContradiction != tt.hasContradiction {
|
|
t.Errorf("Test: %s\nDescription: %s\nExpected hasContradiction=%v, got %v\nContradictions: %v",
|
|
tt.name, tt.description, tt.hasContradiction, hasContradiction, contradictions)
|
|
}
|
|
})
|
|
}
|
|
}
|