Skip to content

Commit b0378ea

Browse files
committed
tag builder tests
Signed-off-by: Harshil Gupta <[email protected]>
1 parent 191ad23 commit b0378ea

File tree

1 file changed

+390
-0
lines changed

1 file changed

+390
-0
lines changed
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
// Copyright (c) 2025 The Jaeger Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package query
5+
6+
import (
7+
"testing"
8+
9+
"github.com/olivere/elastic/v7"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/jaegertracing/jaeger/internal/storage/elasticsearch/dbmodel"
14+
)
15+
16+
func TestNewTagQueryBuilder(t *testing.T) {
17+
dotReplacer := dbmodel.NewDotReplacer("@")
18+
builder := NewTagQueryBuilder(dotReplacer)
19+
20+
assert.NotNil(t, builder)
21+
assert.Equal(t, dotReplacer, builder.dotReplacer)
22+
}
23+
24+
func TestTagQueryBuilder_BuildTagQuery(t *testing.T) {
25+
tests := []struct {
26+
name string
27+
key string
28+
value string
29+
dotReplacement string
30+
expectedQueries int
31+
}{
32+
{
33+
name: "simple key-value",
34+
key: "environment",
35+
value: "production",
36+
dotReplacement: "@",
37+
expectedQueries: 5, // 2 object queries + 3 nested queries
38+
},
39+
{
40+
name: "key with dots",
41+
key: "service.name",
42+
value: "user-service",
43+
dotReplacement: "@",
44+
expectedQueries: 5,
45+
},
46+
{
47+
name: "special characters in value",
48+
key: "version",
49+
value: "v1.2.3",
50+
dotReplacement: "#",
51+
expectedQueries: 5,
52+
},
53+
{
54+
name: "empty value",
55+
key: "status",
56+
value: "",
57+
dotReplacement: "@",
58+
expectedQueries: 5,
59+
},
60+
}
61+
62+
for _, tt := range tests {
63+
t.Run(tt.name, func(t *testing.T) {
64+
dotReplacer := dbmodel.NewDotReplacer(tt.dotReplacement)
65+
builder := NewTagQueryBuilder(dotReplacer)
66+
67+
query := builder.BuildTagQuery(tt.key, tt.value)
68+
69+
// Assert that we get a BoolQuery
70+
boolQuery, ok := query.(*elastic.BoolQuery)
71+
require.True(t, ok, "Expected BoolQuery")
72+
73+
// Convert to map to inspect structure
74+
queryMap, err := boolQuery.Source()
75+
require.NoError(t, err)
76+
77+
// Check that it's a bool query with should clause
78+
queryMapTyped, ok := queryMap.(map[string]interface{})
79+
require.True(t, ok, "Expected query map to be map[string]interface{}")
80+
81+
boolClause, exists := queryMapTyped["bool"]
82+
require.True(t, exists, "Expected bool clause")
83+
84+
boolMap, ok := boolClause.(map[string]interface{})
85+
require.True(t, ok, "Expected bool clause to be map")
86+
87+
shouldClause, exists := boolMap["should"]
88+
require.True(t, exists, "Expected should clause")
89+
90+
shouldQueries, ok := shouldClause.([]interface{})
91+
require.True(t, ok, "Expected should clause to be array")
92+
93+
// Verify we have the expected number of queries
94+
assert.Equal(t, tt.expectedQueries, len(shouldQueries), "Expected %d queries", tt.expectedQueries)
95+
})
96+
}
97+
}
98+
99+
func TestTagQueryBuilder_BuildNestedQuery(t *testing.T) {
100+
tests := []struct {
101+
name string
102+
field string
103+
key string
104+
value string
105+
expected map[string]interface{}
106+
}{
107+
{
108+
name: "nested tags query",
109+
field: "tags",
110+
key: "environment",
111+
value: "production",
112+
},
113+
{
114+
name: "nested process tags query",
115+
field: "process.tags",
116+
key: "service.name",
117+
value: "user-service",
118+
},
119+
{
120+
name: "nested log fields query",
121+
field: "logs.fields",
122+
key: "level",
123+
value: "error",
124+
},
125+
}
126+
127+
for _, tt := range tests {
128+
t.Run(tt.name, func(t *testing.T) {
129+
dotReplacer := dbmodel.NewDotReplacer("@")
130+
builder := NewTagQueryBuilder(dotReplacer)
131+
132+
query := builder.buildNestedQuery(tt.field, tt.key, tt.value)
133+
134+
// Assert that we get a NestedQuery
135+
nestedQuery, ok := query.(*elastic.NestedQuery)
136+
require.True(t, ok, "Expected NestedQuery")
137+
138+
// Convert to map to inspect structure
139+
queryMap, err := nestedQuery.Source()
140+
require.NoError(t, err)
141+
142+
// Check nested structure
143+
queryMapTyped, ok := queryMap.(map[string]interface{})
144+
require.True(t, ok, "Expected query map to be map[string]interface{}")
145+
146+
nestedClause, exists := queryMapTyped["nested"]
147+
require.True(t, exists, "Expected nested clause")
148+
149+
nestedMap, ok := nestedClause.(map[string]interface{})
150+
require.True(t, ok, "Expected nested clause to be map")
151+
152+
// Check path
153+
path, exists := nestedMap["path"]
154+
require.True(t, exists, "Expected path in nested query")
155+
assert.Equal(t, tt.field, path, "Expected path to match field")
156+
157+
// Check query structure
158+
queryClause, exists := nestedMap["query"]
159+
require.True(t, exists, "Expected query in nested clause")
160+
161+
queryBool, ok := queryClause.(map[string]interface{})
162+
require.True(t, ok, "Expected query to be map")
163+
164+
// Should have bool -> must structure
165+
boolClause, exists := queryBool["bool"]
166+
require.True(t, exists, "Expected bool clause in nested query")
167+
168+
boolMap, ok := boolClause.(map[string]interface{})
169+
require.True(t, ok, "Expected bool clause to be map")
170+
171+
mustClause, exists := boolMap["must"]
172+
require.True(t, exists, "Expected must clause")
173+
174+
mustQueries, ok := mustClause.([]interface{})
175+
require.True(t, ok, "Expected must clause to be array")
176+
177+
// Should have exactly 2 queries: key match and value regexp
178+
assert.Equal(t, 2, len(mustQueries), "Expected 2 must queries")
179+
})
180+
}
181+
}
182+
183+
func TestTagQueryBuilder_BuildObjectQuery(t *testing.T) {
184+
tests := []struct {
185+
name string
186+
field string
187+
key string
188+
value string
189+
}{
190+
{
191+
name: "object tag query",
192+
field: "tag",
193+
key: "environment",
194+
value: "production",
195+
},
196+
{
197+
name: "object process tag query",
198+
field: "process.tag",
199+
key: "service@name", // Already dot-replaced
200+
value: "user-service",
201+
},
202+
}
203+
204+
for _, tt := range tests {
205+
t.Run(tt.name, func(t *testing.T) {
206+
dotReplacer := dbmodel.NewDotReplacer("@")
207+
builder := NewTagQueryBuilder(dotReplacer)
208+
209+
query := builder.buildObjectQuery(tt.field, tt.key, tt.value)
210+
211+
// Assert that we get a BoolQuery
212+
boolQuery, ok := query.(*elastic.BoolQuery)
213+
require.True(t, ok, "Expected BoolQuery")
214+
215+
// Convert to map to inspect structure
216+
queryMap, err := boolQuery.Source()
217+
require.NoError(t, err)
218+
219+
// Check bool structure
220+
queryMapTyped, ok := queryMap.(map[string]interface{})
221+
require.True(t, ok, "Expected query map to be map[string]interface{}")
222+
223+
boolClause, exists := queryMapTyped["bool"]
224+
require.True(t, exists, "Expected bool clause")
225+
226+
boolMap, ok := boolClause.(map[string]interface{})
227+
require.True(t, ok, "Expected bool clause to be map")
228+
229+
mustClause, exists := boolMap["must"]
230+
require.True(t, exists, "Expected must clause")
231+
232+
// Must clause can be either a single query or an array of queries
233+
// When there's only one query, elastic returns it as a single object
234+
// When there are multiple queries, it returns an array
235+
if mustArray, ok := mustClause.([]interface{}); ok {
236+
// Multiple queries case
237+
assert.Equal(t, 1, len(mustArray), "Expected 1 must query")
238+
239+
// Check that it's a regexp query
240+
regexpQuery, ok := mustArray[0].(map[string]interface{})
241+
require.True(t, ok, "Expected query to be map")
242+
243+
_, exists = regexpQuery["regexp"]
244+
assert.True(t, exists, "Expected regexp query")
245+
} else if mustQuery, ok := mustClause.(map[string]interface{}); ok {
246+
// Single query case
247+
_, exists = mustQuery["regexp"]
248+
assert.True(t, exists, "Expected regexp query")
249+
} else {
250+
t.Fatal("Expected must clause to be either array or single query")
251+
}
252+
})
253+
}
254+
}
255+
256+
func TestTagQueryBuilder_DotReplacement(t *testing.T) {
257+
tests := []struct {
258+
name string
259+
dotReplacement string
260+
key string
261+
expectedKey string
262+
}{
263+
{
264+
name: "replace dots with @",
265+
dotReplacement: "@",
266+
key: "service.name",
267+
expectedKey: "service@name",
268+
},
269+
{
270+
name: "replace dots with #",
271+
dotReplacement: "#",
272+
key: "trace.span.id",
273+
expectedKey: "trace#span#id",
274+
},
275+
{
276+
name: "no dots in key",
277+
dotReplacement: "@",
278+
key: "environment",
279+
expectedKey: "environment",
280+
},
281+
{
282+
name: "multiple dots",
283+
dotReplacement: "_",
284+
key: "a.b.c.d",
285+
expectedKey: "a_b_c_d",
286+
},
287+
}
288+
289+
for _, tt := range tests {
290+
t.Run(tt.name, func(t *testing.T) {
291+
dotReplacer := dbmodel.NewDotReplacer(tt.dotReplacement)
292+
builder := NewTagQueryBuilder(dotReplacer)
293+
294+
// Build a query and check that dot replacement is applied in object queries
295+
query := builder.BuildTagQuery(tt.key, "test-value")
296+
297+
// Convert to map to inspect
298+
boolQuery, ok := query.(*elastic.BoolQuery)
299+
require.True(t, ok, "Expected BoolQuery")
300+
301+
queryMap, err := boolQuery.Source()
302+
require.NoError(t, err)
303+
304+
// The dot replacement should be visible in the object queries
305+
// We can verify this by checking the structure contains the replaced key
306+
queryMapTyped, ok := queryMap.(map[string]interface{})
307+
require.True(t, ok, "Expected query map to be map[string]interface{}")
308+
309+
boolClause := queryMapTyped["bool"].(map[string]interface{})
310+
shouldClause := boolClause["should"].([]interface{})
311+
312+
// First two queries should be object queries with dot replacement
313+
// This is a basic structural test - more detailed testing would require
314+
// examining the specific field names in the regexp queries
315+
assert.Equal(t, 5, len(shouldClause), "Expected 5 should queries")
316+
})
317+
}
318+
}
319+
320+
func TestTagQueryBuilder_EdgeCases(t *testing.T) {
321+
dotReplacer := dbmodel.NewDotReplacer("@")
322+
builder := NewTagQueryBuilder(dotReplacer)
323+
324+
t.Run("empty key", func(t *testing.T) {
325+
query := builder.BuildTagQuery("", "value")
326+
assert.NotNil(t, query)
327+
328+
// Should still build valid query structure
329+
boolQuery, ok := query.(*elastic.BoolQuery)
330+
require.True(t, ok, "Expected BoolQuery")
331+
332+
queryMap, err := boolQuery.Source()
333+
require.NoError(t, err)
334+
335+
queryMapTyped, ok := queryMap.(map[string]interface{})
336+
require.True(t, ok, "Expected query map to be map[string]interface{}")
337+
assert.Contains(t, queryMapTyped, "bool")
338+
})
339+
340+
t.Run("empty value", func(t *testing.T) {
341+
query := builder.BuildTagQuery("key", "")
342+
assert.NotNil(t, query)
343+
344+
boolQuery, ok := query.(*elastic.BoolQuery)
345+
require.True(t, ok, "Expected BoolQuery")
346+
347+
queryMap, err := boolQuery.Source()
348+
require.NoError(t, err)
349+
350+
queryMapTyped, ok := queryMap.(map[string]interface{})
351+
require.True(t, ok, "Expected query map to be map[string]interface{}")
352+
assert.Contains(t, queryMapTyped, "bool")
353+
})
354+
355+
t.Run("special characters", func(t *testing.T) {
356+
specialKey := "key!@#$%^&*()"
357+
specialValue := "value!@#$%^&*()"
358+
359+
query := builder.BuildTagQuery(specialKey, specialValue)
360+
assert.NotNil(t, query)
361+
362+
boolQuery, ok := query.(*elastic.BoolQuery)
363+
require.True(t, ok, "Expected BoolQuery")
364+
365+
queryMap, err := boolQuery.Source()
366+
require.NoError(t, err)
367+
368+
queryMapTyped, ok := queryMap.(map[string]interface{})
369+
require.True(t, ok, "Expected query map to be map[string]interface{}")
370+
assert.Contains(t, queryMapTyped, "bool")
371+
})
372+
}
373+
374+
func TestTagQueryBuilder_Constants(t *testing.T) {
375+
// Test that constants are properly defined
376+
assert.Equal(t, "tag", objectTagsField)
377+
assert.Equal(t, "process.tag", objectProcessTagsField)
378+
assert.Equal(t, "tags", nestedTagsField)
379+
assert.Equal(t, "process.tags", nestedProcessTagsField)
380+
assert.Equal(t, "logs.fields", nestedLogFieldsField)
381+
assert.Equal(t, "key", tagKeyField)
382+
assert.Equal(t, "value", tagValueField)
383+
384+
// Test field lists
385+
expectedObjectFields := []string{"tag", "process.tag"}
386+
assert.Equal(t, expectedObjectFields, objectTagFieldList)
387+
388+
expectedNestedFields := []string{"tags", "process.tags", "logs.fields"}
389+
assert.Equal(t, expectedNestedFields, nestedTagFieldList)
390+
}

0 commit comments

Comments
 (0)