1// Copyright The OpenTelemetry Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package processorhelper
16
17import (
18	"crypto/sha1" // #nosec
19	"encoding/binary"
20	"errors"
21	"fmt"
22	"math"
23	"regexp"
24	"testing"
25
26	"github.com/stretchr/testify/assert"
27	"github.com/stretchr/testify/require"
28
29	"go.opentelemetry.io/collector/model/pdata"
30)
31
32// Common structure for all the Tests
33type testCase struct {
34	name               string
35	inputAttributes    map[string]pdata.AttributeValue
36	expectedAttributes map[string]pdata.AttributeValue
37}
38
39// runIndividualTestCase is the common logic of passing trace data through a configured attributes processor.
40func runIndividualTestCase(t *testing.T, tt testCase, ap *AttrProc) {
41	t.Run(tt.name, func(t *testing.T) {
42		attrMap := pdata.NewAttributeMap().InitFromMap(tt.inputAttributes)
43		ap.Process(attrMap)
44		attrMap.Sort()
45		require.Equal(t, pdata.NewAttributeMap().InitFromMap(tt.expectedAttributes).Sort(), attrMap)
46	})
47}
48
49func TestAttributes_InsertValue(t *testing.T) {
50	testCases := []testCase{
51		// Ensure `attribute1` is set for spans with no attributes.
52		{
53			name:            "InsertEmptyAttributes",
54			inputAttributes: map[string]pdata.AttributeValue{},
55			expectedAttributes: map[string]pdata.AttributeValue{
56				"attribute1": pdata.NewAttributeValueInt(123),
57			},
58		},
59		// Ensure `attribute1` is set.
60		{
61			name: "InsertKeyNoExists",
62			inputAttributes: map[string]pdata.AttributeValue{
63				"anotherkey": pdata.NewAttributeValueString("bob"),
64			},
65			expectedAttributes: map[string]pdata.AttributeValue{
66				"anotherkey": pdata.NewAttributeValueString("bob"),
67				"attribute1": pdata.NewAttributeValueInt(123),
68			},
69		},
70		// Ensures no insert is performed because the keys `attribute1` already exists.
71		{
72			name: "InsertKeyExists",
73			inputAttributes: map[string]pdata.AttributeValue{
74				"attribute1": pdata.NewAttributeValueString("bob"),
75			},
76			expectedAttributes: map[string]pdata.AttributeValue{
77				"attribute1": pdata.NewAttributeValueString("bob"),
78			},
79		},
80	}
81
82	cfg := &Settings{
83		Actions: []ActionKeyValue{
84			{Key: "attribute1", Action: INSERT, Value: 123},
85		},
86	}
87
88	ap, err := NewAttrProc(cfg)
89	require.Nil(t, err)
90	require.NotNil(t, ap)
91
92	for _, tt := range testCases {
93		runIndividualTestCase(t, tt, ap)
94	}
95}
96
97func TestAttributes_InsertFromAttribute(t *testing.T) {
98
99	testCases := []testCase{
100		// Ensure no attribute is inserted because because attributes do not exist.
101		{
102			name:               "InsertEmptyAttributes",
103			inputAttributes:    map[string]pdata.AttributeValue{},
104			expectedAttributes: map[string]pdata.AttributeValue{},
105		},
106		// Ensure no attribute is inserted because because from_attribute `string_key` does not exist.
107		{
108			name: "InsertMissingFromAttribute",
109			inputAttributes: map[string]pdata.AttributeValue{
110				"bob": pdata.NewAttributeValueInt(1),
111			},
112			expectedAttributes: map[string]pdata.AttributeValue{
113				"bob": pdata.NewAttributeValueInt(1),
114			},
115		},
116		// Ensure `string key` is set.
117		{
118			name: "InsertAttributeExists",
119			inputAttributes: map[string]pdata.AttributeValue{
120				"anotherkey": pdata.NewAttributeValueInt(8892342),
121			},
122			expectedAttributes: map[string]pdata.AttributeValue{
123				"anotherkey": pdata.NewAttributeValueInt(8892342),
124				"string key": pdata.NewAttributeValueInt(8892342),
125			},
126		},
127		// Ensures no insert is performed because the keys `string key` already exist.
128		{
129			name: "InsertKeysExists",
130			inputAttributes: map[string]pdata.AttributeValue{
131				"anotherkey": pdata.NewAttributeValueInt(8892342),
132				"string key": pdata.NewAttributeValueString("here"),
133			},
134			expectedAttributes: map[string]pdata.AttributeValue{
135				"anotherkey": pdata.NewAttributeValueInt(8892342),
136				"string key": pdata.NewAttributeValueString("here"),
137			},
138		},
139	}
140	cfg := &Settings{
141		Actions: []ActionKeyValue{
142			{Key: "string key", Action: INSERT, FromAttribute: "anotherkey"},
143		},
144	}
145
146	ap, err := NewAttrProc(cfg)
147	require.Nil(t, err)
148	require.NotNil(t, ap)
149
150	for _, tt := range testCases {
151		runIndividualTestCase(t, tt, ap)
152	}
153}
154
155func TestAttributes_UpdateValue(t *testing.T) {
156
157	testCases := []testCase{
158		// Ensure no changes to the span as there is no attributes map.
159		{
160			name:               "UpdateNoAttributes",
161			inputAttributes:    map[string]pdata.AttributeValue{},
162			expectedAttributes: map[string]pdata.AttributeValue{},
163		},
164		// Ensure no changes to the span as the key does not exist.
165		{
166			name: "UpdateKeyNoExist",
167			inputAttributes: map[string]pdata.AttributeValue{
168				"boo": pdata.NewAttributeValueString("foo"),
169			},
170			expectedAttributes: map[string]pdata.AttributeValue{
171				"boo": pdata.NewAttributeValueString("foo"),
172			},
173		},
174		// Ensure the attribute `db.secret` is updated.
175		{
176			name: "UpdateAttributes",
177			inputAttributes: map[string]pdata.AttributeValue{
178				"db.secret": pdata.NewAttributeValueString("password1234"),
179			},
180			expectedAttributes: map[string]pdata.AttributeValue{
181				"db.secret": pdata.NewAttributeValueString("redacted"),
182			},
183		},
184	}
185
186	cfg := &Settings{
187		Actions: []ActionKeyValue{
188			{Key: "db.secret", Action: UPDATE, Value: "redacted"},
189		},
190	}
191
192	ap, err := NewAttrProc(cfg)
193	require.Nil(t, err)
194	require.NotNil(t, ap)
195
196	for _, tt := range testCases {
197		runIndividualTestCase(t, tt, ap)
198	}
199}
200
201func TestAttributes_UpdateFromAttribute(t *testing.T) {
202
203	testCases := []testCase{
204		// Ensure no changes to the span as there is no attributes map.
205		{
206			name:               "UpdateNoAttributes",
207			inputAttributes:    map[string]pdata.AttributeValue{},
208			expectedAttributes: map[string]pdata.AttributeValue{},
209		},
210		// Ensure the attribute `boo` isn't updated because attribute `foo` isn't present in the span.
211		{
212			name: "UpdateKeyNoExistFromAttribute",
213			inputAttributes: map[string]pdata.AttributeValue{
214				"boo": pdata.NewAttributeValueString("bob"),
215			},
216			expectedAttributes: map[string]pdata.AttributeValue{
217				"boo": pdata.NewAttributeValueString("bob"),
218			},
219		},
220		// Ensure no updates as the target key `boo` doesn't exists.
221		{
222			name: "UpdateKeyNoExistMainAttributed",
223			inputAttributes: map[string]pdata.AttributeValue{
224				"foo": pdata.NewAttributeValueString("over there"),
225			},
226			expectedAttributes: map[string]pdata.AttributeValue{
227				"foo": pdata.NewAttributeValueString("over there"),
228			},
229		},
230		// Ensure no updates as the target key `boo` doesn't exists.
231		{
232			name: "UpdateKeyFromExistingAttribute",
233			inputAttributes: map[string]pdata.AttributeValue{
234				"foo": pdata.NewAttributeValueString("there is a party over here"),
235				"boo": pdata.NewAttributeValueString("not here"),
236			},
237			expectedAttributes: map[string]pdata.AttributeValue{
238				"foo": pdata.NewAttributeValueString("there is a party over here"),
239				"boo": pdata.NewAttributeValueString("there is a party over here"),
240			},
241		},
242	}
243
244	cfg := &Settings{
245		Actions: []ActionKeyValue{
246			{Key: "boo", Action: UPDATE, FromAttribute: "foo"},
247		},
248	}
249
250	ap, err := NewAttrProc(cfg)
251	require.Nil(t, err)
252	require.NotNil(t, ap)
253
254	for _, tt := range testCases {
255		runIndividualTestCase(t, tt, ap)
256	}
257}
258
259func TestAttributes_UpsertValue(t *testing.T) {
260	testCases := []testCase{
261		// Ensure `region` is set for spans with no attributes.
262		{
263			name:            "UpsertNoAttributes",
264			inputAttributes: map[string]pdata.AttributeValue{},
265			expectedAttributes: map[string]pdata.AttributeValue{
266				"region": pdata.NewAttributeValueString("planet-earth"),
267			},
268		},
269		// Ensure `region` is inserted for spans with some attributes(the key doesn't exist).
270		{
271			name: "UpsertAttributeNoExist",
272			inputAttributes: map[string]pdata.AttributeValue{
273				"mission": pdata.NewAttributeValueString("to mars"),
274			},
275			expectedAttributes: map[string]pdata.AttributeValue{
276				"mission": pdata.NewAttributeValueString("to mars"),
277				"region":  pdata.NewAttributeValueString("planet-earth"),
278			},
279		},
280		// Ensure `region` is updated for spans with the attribute key `region`.
281		{
282			name: "UpsertAttributeExists",
283			inputAttributes: map[string]pdata.AttributeValue{
284				"mission": pdata.NewAttributeValueString("to mars"),
285				"region":  pdata.NewAttributeValueString("solar system"),
286			},
287			expectedAttributes: map[string]pdata.AttributeValue{
288				"mission": pdata.NewAttributeValueString("to mars"),
289				"region":  pdata.NewAttributeValueString("planet-earth"),
290			},
291		},
292	}
293
294	cfg := &Settings{
295		Actions: []ActionKeyValue{
296			{Key: "region", Action: UPSERT, Value: "planet-earth"},
297		},
298	}
299
300	ap, err := NewAttrProc(cfg)
301	require.Nil(t, err)
302	require.NotNil(t, ap)
303
304	for _, tt := range testCases {
305		runIndividualTestCase(t, tt, ap)
306	}
307}
308
309func TestAttributes_Extract(t *testing.T) {
310	testCases := []testCase{
311		// Ensure `new_user_key` is not set for spans with no attributes.
312		{
313			name:               "UpsertEmptyAttributes",
314			inputAttributes:    map[string]pdata.AttributeValue{},
315			expectedAttributes: map[string]pdata.AttributeValue{},
316		},
317		// Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`.
318		{
319			name: "No extract with no target key",
320			inputAttributes: map[string]pdata.AttributeValue{
321				"boo": pdata.NewAttributeValueString("ghosts are scary"),
322			},
323			expectedAttributes: map[string]pdata.AttributeValue{
324				"boo": pdata.NewAttributeValueString("ghosts are scary"),
325			},
326		},
327		// Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`.
328		{
329			name: "No extract with non string target key",
330			inputAttributes: map[string]pdata.AttributeValue{
331				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
332				"user_key": pdata.NewAttributeValueInt(1234),
333			},
334			expectedAttributes: map[string]pdata.AttributeValue{
335				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
336				"user_key": pdata.NewAttributeValueInt(1234),
337			},
338		},
339		// Ensure `new_user_key` is not updated for spans with attribute
340		// `user_key` because `user_key` does not match the regular expression.
341		{
342			name: "No extract with no pattern matching",
343			inputAttributes: map[string]pdata.AttributeValue{
344				"user_key": pdata.NewAttributeValueString("does not match"),
345				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
346			},
347			expectedAttributes: map[string]pdata.AttributeValue{
348				"user_key": pdata.NewAttributeValueString("does not match"),
349				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
350			},
351		},
352		// Ensure `new_user_key` is not updated for spans with attribute
353		// `user_key` because `user_key` does not match all of the regular
354		// expression.
355		{
356			name: "No extract with no pattern matching",
357			inputAttributes: map[string]pdata.AttributeValue{
358				"user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update"),
359				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
360			},
361			expectedAttributes: map[string]pdata.AttributeValue{
362				"user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update"),
363				"boo":      pdata.NewAttributeValueString("ghosts are scary"),
364			},
365		},
366		// Ensure `new_user_key` and `version` is inserted for spans with attribute `user_key`.
367		{
368			name: "Extract insert new values.",
369			inputAttributes: map[string]pdata.AttributeValue{
370				"user_key": pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
371				"foo":      pdata.NewAttributeValueString("casper the friendly ghost"),
372			},
373			expectedAttributes: map[string]pdata.AttributeValue{
374				"user_key":     pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
375				"new_user_key": pdata.NewAttributeValueString("12345678"),
376				"version":      pdata.NewAttributeValueString("v1"),
377				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
378			},
379		},
380		// Ensure `new_user_key` and `version` is updated for spans with attribute `user_key`.
381		{
382			name: "Extract updates existing values ",
383			inputAttributes: map[string]pdata.AttributeValue{
384				"user_key":     pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
385				"new_user_key": pdata.NewAttributeValueString("2321"),
386				"version":      pdata.NewAttributeValueString("na"),
387				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
388			},
389			expectedAttributes: map[string]pdata.AttributeValue{
390				"user_key":     pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
391				"new_user_key": pdata.NewAttributeValueString("12345678"),
392				"version":      pdata.NewAttributeValueString("v1"),
393				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
394			},
395		},
396		// Ensure `new_user_key` is updated and `version` is inserted for spans with attribute `user_key`.
397		{
398			name: "Extract upserts values",
399			inputAttributes: map[string]pdata.AttributeValue{
400				"user_key":     pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
401				"new_user_key": pdata.NewAttributeValueString("2321"),
402				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
403			},
404			expectedAttributes: map[string]pdata.AttributeValue{
405				"user_key":     pdata.NewAttributeValueString("/api/v1/document/12345678/update/v1"),
406				"new_user_key": pdata.NewAttributeValueString("12345678"),
407				"version":      pdata.NewAttributeValueString("v1"),
408				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
409			},
410		},
411	}
412
413	cfg := &Settings{
414		Actions: []ActionKeyValue{
415
416			{Key: "user_key", RegexPattern: "^\\/api\\/v1\\/document\\/(?P<new_user_key>.*)\\/update\\/(?P<version>.*)$", Action: EXTRACT},
417		},
418	}
419
420	ap, err := NewAttrProc(cfg)
421	require.Nil(t, err)
422	require.NotNil(t, ap)
423
424	for _, tt := range testCases {
425		runIndividualTestCase(t, tt, ap)
426	}
427}
428
429func TestAttributes_UpsertFromAttribute(t *testing.T) {
430
431	testCases := []testCase{
432		// Ensure `new_user_key` is not set for spans with no attributes.
433		{
434			name:               "UpsertEmptyAttributes",
435			inputAttributes:    map[string]pdata.AttributeValue{},
436			expectedAttributes: map[string]pdata.AttributeValue{},
437		},
438		// Ensure `new_user_key` is not inserted for spans with missing attribute `user_key`.
439		{
440			name: "UpsertFromAttributeNoExist",
441			inputAttributes: map[string]pdata.AttributeValue{
442				"boo": pdata.NewAttributeValueString("ghosts are scary"),
443			},
444			expectedAttributes: map[string]pdata.AttributeValue{
445				"boo": pdata.NewAttributeValueString("ghosts are scary"),
446			},
447		},
448		// Ensure `new_user_key` is inserted for spans with attribute `user_key`.
449		{
450			name: "UpsertFromAttributeExistsInsert",
451			inputAttributes: map[string]pdata.AttributeValue{
452				"user_key": pdata.NewAttributeValueInt(2245),
453				"foo":      pdata.NewAttributeValueString("casper the friendly ghost"),
454			},
455			expectedAttributes: map[string]pdata.AttributeValue{
456				"user_key":     pdata.NewAttributeValueInt(2245),
457				"new_user_key": pdata.NewAttributeValueInt(2245),
458				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
459			},
460		},
461		// Ensure `new_user_key` is updated for spans with attribute `user_key`.
462		{
463			name: "UpsertFromAttributeExistsUpdate",
464			inputAttributes: map[string]pdata.AttributeValue{
465				"user_key":     pdata.NewAttributeValueInt(2245),
466				"new_user_key": pdata.NewAttributeValueInt(5422),
467				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
468			},
469			expectedAttributes: map[string]pdata.AttributeValue{
470				"user_key":     pdata.NewAttributeValueInt(2245),
471				"new_user_key": pdata.NewAttributeValueInt(2245),
472				"foo":          pdata.NewAttributeValueString("casper the friendly ghost"),
473			},
474		},
475	}
476
477	cfg := &Settings{
478		Actions: []ActionKeyValue{
479			{Key: "new_user_key", Action: UPSERT, FromAttribute: "user_key"},
480		},
481	}
482
483	ap, err := NewAttrProc(cfg)
484	require.Nil(t, err)
485	require.NotNil(t, ap)
486
487	for _, tt := range testCases {
488		runIndividualTestCase(t, tt, ap)
489	}
490}
491
492func TestAttributes_Delete(t *testing.T) {
493	testCases := []testCase{
494		// Ensure the span contains no changes.
495		{
496			name:               "DeleteEmptyAttributes",
497			inputAttributes:    map[string]pdata.AttributeValue{},
498			expectedAttributes: map[string]pdata.AttributeValue{},
499		},
500		// Ensure the span contains no changes because the key doesn't exist.
501		{
502			name: "DeleteAttributeNoExist",
503			inputAttributes: map[string]pdata.AttributeValue{
504				"boo": pdata.NewAttributeValueString("ghosts are scary"),
505			},
506			expectedAttributes: map[string]pdata.AttributeValue{
507				"boo": pdata.NewAttributeValueString("ghosts are scary"),
508			},
509		},
510		// Ensure `duplicate_key` is deleted for spans with the attribute set.
511		{
512			name: "DeleteAttributeExists",
513			inputAttributes: map[string]pdata.AttributeValue{
514				"duplicate_key": pdata.NewAttributeValueDouble(3245.6),
515				"original_key":  pdata.NewAttributeValueDouble(3245.6),
516			},
517			expectedAttributes: map[string]pdata.AttributeValue{
518				"original_key": pdata.NewAttributeValueDouble(3245.6),
519			},
520		},
521	}
522
523	cfg := &Settings{
524		Actions: []ActionKeyValue{
525			{Key: "duplicate_key", Action: DELETE},
526		},
527	}
528
529	ap, err := NewAttrProc(cfg)
530	require.Nil(t, err)
531	require.NotNil(t, ap)
532
533	for _, tt := range testCases {
534		runIndividualTestCase(t, tt, ap)
535	}
536}
537
538func TestAttributes_HashValue(t *testing.T) {
539
540	intVal := int64(24)
541	intBytes := make([]byte, int64ByteSize)
542	binary.LittleEndian.PutUint64(intBytes, uint64(intVal))
543
544	doubleVal := 2.4
545	doubleBytes := make([]byte, float64ByteSize)
546	binary.LittleEndian.PutUint64(doubleBytes, math.Float64bits(doubleVal))
547
548	testCases := []testCase{
549		// Ensure no changes to the span as there is no attributes map.
550		{
551			name:               "HashNoAttributes",
552			inputAttributes:    map[string]pdata.AttributeValue{},
553			expectedAttributes: map[string]pdata.AttributeValue{},
554		},
555		// Ensure no changes to the span as the key does not exist.
556		{
557			name: "HashKeyNoExist",
558			inputAttributes: map[string]pdata.AttributeValue{
559				"boo": pdata.NewAttributeValueString("foo"),
560			},
561			expectedAttributes: map[string]pdata.AttributeValue{
562				"boo": pdata.NewAttributeValueString("foo"),
563			},
564		},
565		// Ensure string data types are hashed correctly
566		{
567			name: "HashString",
568			inputAttributes: map[string]pdata.AttributeValue{
569				"updateme": pdata.NewAttributeValueString("foo"),
570			},
571			expectedAttributes: map[string]pdata.AttributeValue{
572				"updateme": pdata.NewAttributeValueString(sha1Hash([]byte("foo"))),
573			},
574		},
575		// Ensure int data types are hashed correctly
576		{
577			name: "HashInt",
578			inputAttributes: map[string]pdata.AttributeValue{
579				"updateme": pdata.NewAttributeValueInt(intVal),
580			},
581			expectedAttributes: map[string]pdata.AttributeValue{
582				"updateme": pdata.NewAttributeValueString(sha1Hash(intBytes)),
583			},
584		},
585		// Ensure double data types are hashed correctly
586		{
587			name: "HashDouble",
588			inputAttributes: map[string]pdata.AttributeValue{
589				"updateme": pdata.NewAttributeValueDouble(doubleVal),
590			},
591			expectedAttributes: map[string]pdata.AttributeValue{
592				"updateme": pdata.NewAttributeValueString(sha1Hash(doubleBytes)),
593			},
594		},
595		// Ensure bool data types are hashed correctly
596		{
597			name: "HashBoolTrue",
598			inputAttributes: map[string]pdata.AttributeValue{
599				"updateme": pdata.NewAttributeValueBool(true),
600			},
601			expectedAttributes: map[string]pdata.AttributeValue{
602				"updateme": pdata.NewAttributeValueString(sha1Hash([]byte{1})),
603			},
604		},
605		// Ensure bool data types are hashed correctly
606		{
607			name: "HashBoolFalse",
608			inputAttributes: map[string]pdata.AttributeValue{
609				"updateme": pdata.NewAttributeValueBool(false),
610			},
611			expectedAttributes: map[string]pdata.AttributeValue{
612				"updateme": pdata.NewAttributeValueString(sha1Hash([]byte{0})),
613			},
614		},
615	}
616
617	cfg := &Settings{
618		Actions: []ActionKeyValue{
619			{Key: "updateme", Action: HASH},
620		},
621	}
622
623	ap, err := NewAttrProc(cfg)
624	require.Nil(t, err)
625	require.NotNil(t, ap)
626
627	for _, tt := range testCases {
628		runIndividualTestCase(t, tt, ap)
629	}
630}
631
632func TestAttributes_FromAttributeNoChange(t *testing.T) {
633	tc := testCase{
634		name: "FromAttributeNoChange",
635		inputAttributes: map[string]pdata.AttributeValue{
636			"boo": pdata.NewAttributeValueString("ghosts are scary"),
637		},
638		expectedAttributes: map[string]pdata.AttributeValue{
639			"boo": pdata.NewAttributeValueString("ghosts are scary"),
640		},
641	}
642
643	cfg := &Settings{
644		Actions: []ActionKeyValue{
645			{Key: "boo", Action: INSERT, FromAttribute: "boo"},
646			{Key: "boo", Action: UPDATE, FromAttribute: "boo"},
647			{Key: "boo", Action: UPSERT, FromAttribute: "boo"},
648		},
649	}
650
651	ap, err := NewAttrProc(cfg)
652	require.Nil(t, err)
653	require.NotNil(t, ap)
654
655	runIndividualTestCase(t, tc, ap)
656}
657
658func TestAttributes_Ordering(t *testing.T) {
659	testCases := []testCase{
660		// For this example, the operations performed are
661		// 1. insert `operation`: `default`
662		// 2. insert `svc.operation`: `default`
663		// 3. delete `operation`.
664		{
665			name: "OrderingApplyAllSteps",
666			inputAttributes: map[string]pdata.AttributeValue{
667				"foo": pdata.NewAttributeValueString("casper the friendly ghost"),
668			},
669			expectedAttributes: map[string]pdata.AttributeValue{
670				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
671				"svc.operation": pdata.NewAttributeValueString("default"),
672			},
673		},
674		// For this example, the operations performed are
675		// 1. do nothing for the first action of insert `operation`: `default`
676		// 2. insert `svc.operation`: `arithmetic`
677		// 3. delete `operation`.
678		{
679			name: "OrderingOperationExists",
680			inputAttributes: map[string]pdata.AttributeValue{
681				"foo":       pdata.NewAttributeValueString("casper the friendly ghost"),
682				"operation": pdata.NewAttributeValueString("arithmetic"),
683			},
684			expectedAttributes: map[string]pdata.AttributeValue{
685				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
686				"svc.operation": pdata.NewAttributeValueString("arithmetic"),
687			},
688		},
689
690		// For this example, the operations performed are
691		// 1. insert `operation`: `default`
692		// 2. update `svc.operation` to `default`
693		// 3. delete `operation`.
694		{
695			name: "OrderingSvcOperationExists",
696			inputAttributes: map[string]pdata.AttributeValue{
697				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
698				"svc.operation": pdata.NewAttributeValueString("some value"),
699			},
700			expectedAttributes: map[string]pdata.AttributeValue{
701				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
702				"svc.operation": pdata.NewAttributeValueString("default"),
703			},
704		},
705
706		// For this example, the operations performed are
707		// 1. do nothing for the first action of insert `operation`: `default`
708		// 2. update `svc.operation` to `arithmetic`
709		// 3. delete `operation`.
710		{
711			name: "OrderingBothAttributesExist",
712			inputAttributes: map[string]pdata.AttributeValue{
713				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
714				"operation":     pdata.NewAttributeValueString("arithmetic"),
715				"svc.operation": pdata.NewAttributeValueString("add"),
716			},
717			expectedAttributes: map[string]pdata.AttributeValue{
718				"foo":           pdata.NewAttributeValueString("casper the friendly ghost"),
719				"svc.operation": pdata.NewAttributeValueString("arithmetic"),
720			},
721		},
722	}
723
724	cfg := &Settings{
725		Actions: []ActionKeyValue{
726			{Key: "operation", Action: INSERT, Value: "default"},
727			{Key: "svc.operation", Action: UPSERT, FromAttribute: "operation"},
728			{Key: "operation", Action: DELETE},
729		},
730	}
731
732	ap, err := NewAttrProc(cfg)
733	require.Nil(t, err)
734	require.NotNil(t, ap)
735
736	for _, tt := range testCases {
737		runIndividualTestCase(t, tt, ap)
738	}
739}
740
741func TestInvalidConfig(t *testing.T) {
742	testcase := []struct {
743		name        string
744		actionLists []ActionKeyValue
745		errorString string
746	}{
747		{
748			name: "missing key",
749			actionLists: []ActionKeyValue{
750				{Key: "one", Action: DELETE},
751				{Key: "", Value: 123, Action: UPSERT},
752			},
753			errorString: "error creating AttrProc due to missing required field \"key\" at the 1-th actions",
754		},
755		{
756			name: "invalid action",
757			actionLists: []ActionKeyValue{
758				{Key: "invalid", Action: "invalid"},
759			},
760			errorString: "error creating AttrProc due to unsupported action \"invalid\" at the 0-th actions",
761		},
762		{
763			name: "unsupported value",
764			actionLists: []ActionKeyValue{
765				{Key: "UnsupportedValue", Value: []int{}, Action: UPSERT},
766			},
767			errorString: "error unsupported value type \"[]int\"",
768		},
769		{
770			name: "missing value or from attribute",
771			actionLists: []ActionKeyValue{
772				{Key: "MissingValueFromAttributes", Action: INSERT},
773			},
774			errorString: "error creating AttrProc. Either field \"value\" or \"from_attribute\" setting must be specified for 0-th action",
775		},
776		{
777			name: "both set value and from attribute",
778			actionLists: []ActionKeyValue{
779				{Key: "BothSet", Value: 123, FromAttribute: "aa", Action: UPSERT},
780			},
781			errorString: "error creating AttrProc due to both fields \"value\" and \"from_attribute\" being set at the 0-th actions",
782		},
783		{
784			name: "pattern shouldn't be specified",
785			actionLists: []ActionKeyValue{
786				{Key: "key", RegexPattern: "(?P<operation_website>.*?)$", FromAttribute: "aa", Action: INSERT},
787			},
788			errorString: "error creating AttrProc. Action \"insert\" does not use the \"pattern\" field. This must not be specified for 0-th action",
789		},
790		{
791			name: "missing rule for extract",
792			actionLists: []ActionKeyValue{
793				{Key: "aa", Action: EXTRACT},
794			},
795			errorString: "error creating AttrProc due to missing required field \"pattern\" for action \"extract\" at the 0-th action",
796		},
797		{name: "set value for extract",
798			actionLists: []ActionKeyValue{
799				{Key: "Key", RegexPattern: "(?P<operation_website>.*?)$", Value: "value", Action: EXTRACT},
800			},
801			errorString: "error creating AttrProc. Action \"extract\" does not use \"value\" or \"from_attribute\" field. These must not be specified for 0-th action",
802		},
803		{
804			name: "set from attribute for extract",
805			actionLists: []ActionKeyValue{
806				{Key: "key", RegexPattern: "(?P<operation_website>.*?)$", FromAttribute: "aa", Action: EXTRACT},
807			},
808			errorString: "error creating AttrProc. Action \"extract\" does not use \"value\" or \"from_attribute\" field. These must not be specified for 0-th action",
809		},
810		{
811			name: "invalid regex",
812			actionLists: []ActionKeyValue{
813				{Key: "aa", RegexPattern: "(?P<invalid.regex>.*?)$", Action: EXTRACT},
814			},
815			errorString: "error creating AttrProc. Field \"pattern\" has invalid pattern: \"(?P<invalid.regex>.*?)$\" to be set at the 0-th actions",
816		},
817		{
818			name: "delete with regex",
819			actionLists: []ActionKeyValue{
820				{RegexPattern: "(?P<operation_website>.*?)$", Key: "ab", Action: DELETE},
821			},
822			errorString: "error creating AttrProc. Action \"delete\" does not use \"value\", \"pattern\" or \"from_attribute\" field. These must not be specified for 0-th action",
823		},
824		{
825			name: "regex with unnamed capture group",
826			actionLists: []ActionKeyValue{
827				{Key: "aa", RegexPattern: ".*$", Action: EXTRACT},
828			},
829			errorString: "error creating AttrProc. Field \"pattern\" contains no named matcher groups at the 0-th actions",
830		},
831		{
832			name: "regex with one unnamed capture groups",
833			actionLists: []ActionKeyValue{
834				{Key: "aa", RegexPattern: "^\\/api\\/v1\\/document\\/(?P<new_user_key>.*)\\/update\\/(.*)$", Action: EXTRACT},
835			},
836			errorString: "error creating AttrProc. Field \"pattern\" contains at least one unnamed matcher group at the 0-th actions",
837		},
838	}
839
840	for _, tc := range testcase {
841		t.Run(tc.name, func(t *testing.T) {
842			ap, err := NewAttrProc(&Settings{Actions: tc.actionLists})
843			assert.Nil(t, ap)
844			assert.EqualValues(t, errors.New(tc.errorString), err)
845		})
846	}
847}
848
849func TestValidConfiguration(t *testing.T) {
850	cfg := &Settings{
851		Actions: []ActionKeyValue{
852			{Key: "one", Action: "Delete"},
853			{Key: "two", Value: 123, Action: "INSERT"},
854			{Key: "three", FromAttribute: "two", Action: "upDaTE"},
855			{Key: "five", FromAttribute: "two", Action: "upsert"},
856			{Key: "two", RegexPattern: "^\\/api\\/v1\\/document\\/(?P<documentId>.*)\\/update$", Action: "EXTRact"},
857		},
858	}
859	ap, err := NewAttrProc(cfg)
860	require.NoError(t, err)
861
862	av := pdata.NewAttributeValueInt(123)
863	compiledRegex := regexp.MustCompile(`^\/api\/v1\/document\/(?P<documentId>.*)\/update$`)
864	assert.Equal(t, []attributeAction{
865		{Key: "one", Action: DELETE},
866		{Key: "two", Action: INSERT,
867			AttributeValue: &av,
868		},
869		{Key: "three", FromAttribute: "two", Action: UPDATE},
870		{Key: "five", FromAttribute: "two", Action: UPSERT},
871		{Key: "two", Regex: compiledRegex, AttrNames: []string{"", "documentId"}, Action: EXTRACT},
872	}, ap.actions)
873
874}
875
876func sha1Hash(b []byte) string {
877	// #nosec
878	h := sha1.New()
879	h.Write(b)
880	return fmt.Sprintf("%x", h.Sum(nil))
881}
882