@@ -5,6 +5,189 @@ import (
55 "testing"
66)
77
8+ func TestValidate_DuplicateFlagKeys (t * testing.T ) {
9+ tests := []struct {
10+ name string
11+ manifest string
12+ wantDuplicates []string
13+ }{
14+ {
15+ name : "no duplicates" ,
16+ manifest : `{
17+ "flags": {
18+ "flag-a": {"flagType": "boolean", "defaultValue": true},
19+ "flag-b": {"flagType": "string", "defaultValue": "hello"}
20+ }
21+ }` ,
22+ wantDuplicates : nil ,
23+ },
24+ {
25+ name : "single duplicate" ,
26+ manifest : `{
27+ "flags": {
28+ "my-flag": {"flagType": "boolean", "defaultValue": true},
29+ "my-flag": {"flagType": "string", "defaultValue": "hello"}
30+ }
31+ }` ,
32+ wantDuplicates : []string {"my-flag" },
33+ },
34+ {
35+ name : "multiple duplicates" ,
36+ manifest : `{
37+ "flags": {
38+ "flag-a": {"flagType": "boolean", "defaultValue": true},
39+ "flag-b": {"flagType": "string", "defaultValue": "hello"},
40+ "flag-a": {"flagType": "integer", "defaultValue": 42},
41+ "flag-b": {"flagType": "float", "defaultValue": 3.14}
42+ }
43+ }` ,
44+ wantDuplicates : []string {"flag-a" , "flag-b" },
45+ },
46+ {
47+ name : "triple duplicate of same key" ,
48+ manifest : `{
49+ "flags": {
50+ "repeated": {"flagType": "boolean", "defaultValue": true},
51+ "repeated": {"flagType": "string", "defaultValue": "hello"},
52+ "repeated": {"flagType": "integer", "defaultValue": 42}
53+ }
54+ }` ,
55+ wantDuplicates : []string {"repeated" , "repeated" },
56+ },
57+ {
58+ name : "empty flags object" ,
59+ manifest : `{
60+ "flags": {}
61+ }` ,
62+ wantDuplicates : nil ,
63+ },
64+ {
65+ name : "manifest with schema field" ,
66+ manifest : `{
67+ "$schema": "https://example.com/schema.json",
68+ "flags": {
69+ "dup": {"flagType": "boolean", "defaultValue": true},
70+ "dup": {"flagType": "boolean", "defaultValue": false}
71+ }
72+ }` ,
73+ wantDuplicates : []string {"dup" },
74+ },
75+ }
76+
77+ for _ , tt := range tests {
78+ t .Run (tt .name , func (t * testing.T ) {
79+ issues , err := Validate ([]byte (tt .manifest ))
80+ if err != nil {
81+ t .Fatalf ("Validate() error = %v" , err )
82+ }
83+
84+ var gotDuplicates []string
85+ for _ , issue := range issues {
86+ if issue .Type == "duplicate_key" {
87+ // Extract the flag key from the path (format: "flags.key")
88+ parts := strings .SplitN (issue .Path , "." , 2 )
89+ if len (parts ) == 2 {
90+ gotDuplicates = append (gotDuplicates , parts [1 ])
91+ }
92+ }
93+ }
94+
95+ if len (gotDuplicates ) != len (tt .wantDuplicates ) {
96+ t .Errorf ("got %d duplicates, want %d" , len (gotDuplicates ), len (tt .wantDuplicates ))
97+ t .Errorf ("got duplicates: %v" , gotDuplicates )
98+ t .Errorf ("want duplicates: %v" , tt .wantDuplicates )
99+ return
100+ }
101+
102+ for i , want := range tt .wantDuplicates {
103+ if gotDuplicates [i ] != want {
104+ t .Errorf ("duplicate[%d] = %q, want %q" , i , gotDuplicates [i ], want )
105+ }
106+ }
107+ })
108+ }
109+ }
110+
111+ func TestValidate_DuplicateKeyErrorMessage (t * testing.T ) {
112+ manifest := `{
113+ "flags": {
114+ "my-flag": {"flagType": "boolean", "defaultValue": true},
115+ "my-flag": {"flagType": "string", "defaultValue": "hello"}
116+ }
117+ }`
118+
119+ issues , err := Validate ([]byte (manifest ))
120+ if err != nil {
121+ t .Fatalf ("Validate() error = %v" , err )
122+ }
123+
124+ var found bool
125+ for _ , issue := range issues {
126+ if issue .Type == "duplicate_key" {
127+ found = true
128+ if issue .Path != "flags.my-flag" {
129+ t .Errorf ("expected path 'flags.my-flag', got %q" , issue .Path )
130+ }
131+ expectedMsg := "flag 'my-flag' is defined multiple times in the manifest"
132+ if issue .Message != expectedMsg {
133+ t .Errorf ("expected message %q, got %q" , expectedMsg , issue .Message )
134+ }
135+ }
136+ }
137+
138+ if ! found {
139+ t .Error ("expected to find a duplicate_key validation error" )
140+ }
141+ }
142+
143+ func TestFindDuplicateFlagKeys_EdgeCases (t * testing.T ) {
144+ tests := []struct {
145+ name string
146+ input string
147+ expected []string
148+ }{
149+ {
150+ name : "invalid JSON" ,
151+ input : "not valid json" ,
152+ expected : nil ,
153+ },
154+ {
155+ name : "array instead of object" ,
156+ input : `["a", "b", "c"]` ,
157+ expected : nil ,
158+ },
159+ {
160+ name : "no flags key" ,
161+ input : `{"other": "value"}` ,
162+ expected : nil ,
163+ },
164+ {
165+ name : "flags is not an object" ,
166+ input : `{"flags": "string value"}` ,
167+ expected : nil ,
168+ },
169+ {
170+ name : "flags is an array" ,
171+ input : `{"flags": [1, 2, 3]}` ,
172+ expected : nil ,
173+ },
174+ {
175+ name : "nested duplicates not detected in flag values" ,
176+ input : `{"flags": {"flag1": {"nested": 1, "nested": 2}}}` ,
177+ expected : nil ,
178+ },
179+ }
180+
181+ for _ , tt := range tests {
182+ t .Run (tt .name , func (t * testing.T ) {
183+ result := findDuplicateFlagKeys ([]byte (tt .input ))
184+ if len (result ) != len (tt .expected ) {
185+ t .Errorf ("got %v, want %v" , result , tt .expected )
186+ }
187+ })
188+ }
189+ }
190+
8191// Sample test for FormatValidationError
9192func TestFormatValidationError_SortsByPath (t * testing.T ) {
10193 issues := []ValidationError {
0 commit comments