Skip to content

Commit ef073ad

Browse files
authored
feat: add noDuplicates pipe function for duplicate data verification (#145)
1 parent 5b62886 commit ef073ad

3 files changed

Lines changed: 108 additions & 0 deletions

File tree

docs/en/setup/Configuration-File.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,19 @@ For example, if a generic expected entry (e.g., `notEmpty`) appears before a spe
248248
{{- end }}
249249
```
250250

251+
##### Duplicate Detection
252+
253+
`noDuplicates` is a pipe function that verifies a list contains no duplicate items. Two items are considered duplicates when all their fields are equal. It can be combined with `contains`, `containsOnce`, or `range`.
254+
255+
```yaml
256+
{{- contains (.metrics | noDuplicates) }}
257+
- name: {{ notEmpty .name }}
258+
value: {{ notEmpty .value }}
259+
{{- end }}
260+
```
261+
262+
When duplicates exist in the actual data, verification will fail with an error indicating the duplicated item.
263+
251264
##### Encoding
252265

253266
In order to make the program easier for users to read and use, some code conversions are provided.

internal/components/verifier/funcs.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ var customFuncMap = map[string]any{
5454

5555
// Calculation:
5656
"subtractor": subtractor,
57+
58+
// List:
59+
"noDuplicates": noDuplicates,
5760
}
5861

5962
func base64encode(s string) string {
@@ -103,3 +106,18 @@ func subtractor(total int, nums ...int) int {
103106
}
104107
return total
105108
}
109+
110+
func noDuplicates(list any) any {
111+
items, ok := list.([]any)
112+
if !ok {
113+
return list
114+
}
115+
for i := 0; i < len(items); i++ {
116+
for j := i + 1; j < len(items); j++ {
117+
if fmt.Sprint(items[i]) == fmt.Sprint(items[j]) {
118+
return fmt.Sprintf("<duplicate found: %v>", items[i])
119+
}
120+
}
121+
}
122+
return list
123+
}

internal/components/verifier/verifier_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,83 @@ metrics:
501501
},
502502
wantErr: true, // service-A does not exist in actual
503503
},
504+
{
505+
name: "noDuplicates should pass when list has no duplicates",
506+
args: args{
507+
actualData: `
508+
metrics:
509+
- name: service-A
510+
value: "100"
511+
- name: service-B
512+
value: "200"
513+
`,
514+
expectedTemplate: `
515+
metrics:
516+
{{- contains (.metrics | noDuplicates) }}
517+
- name: {{ notEmpty .name }}
518+
value: {{ notEmpty .value }}
519+
{{- end }}
520+
`,
521+
},
522+
wantErr: false,
523+
},
524+
{
525+
name: "noDuplicates should fail when list has duplicate items",
526+
args: args{
527+
actualData: `
528+
metrics:
529+
- name: service-A
530+
value: "100"
531+
- name: service-A
532+
value: "100"
533+
`,
534+
expectedTemplate: `
535+
metrics:
536+
{{- contains (.metrics | noDuplicates) }}
537+
- name: {{ notEmpty .name }}
538+
value: {{ notEmpty .value }}
539+
{{- end }}
540+
`,
541+
},
542+
wantErr: true, // duplicate entry exists
543+
},
544+
{
545+
name: "noDuplicates should work with containsOnce",
546+
args: args{
547+
actualData: `
548+
- name: service-A
549+
value: "100"
550+
- name: service-B
551+
value: "200"
552+
- name: service-A
553+
value: "100"
554+
`,
555+
expectedTemplate: `
556+
{{- containsOnce (. | noDuplicates) }}
557+
- name: {{ notEmpty .name }}
558+
value: {{ notEmpty .value }}
559+
{{- end }}
560+
`,
561+
},
562+
wantErr: true, // duplicate entry exists even though containsOnce would pass
563+
},
564+
{
565+
name: "noDuplicates should pass with range and no duplicates",
566+
args: args{
567+
actualData: `
568+
metrics:
569+
- name: service-A
570+
- name: service-B
571+
`,
572+
expectedTemplate: `
573+
metrics:
574+
{{- range (.metrics | noDuplicates) }}
575+
- name: {{ notEmpty .name }}
576+
{{- end }}
577+
`,
578+
},
579+
wantErr: false,
580+
},
504581
{
505582
name: "notEmpty with nil",
506583
args: args{

0 commit comments

Comments
 (0)