1// Copyright 2025 CUE 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 json
16
17import (
18 "slices"
19 "testing"
20
21 "github.com/go-quicktest/qt"
22
23 "cuelang.org/go/cue"
24)
25
26func TestPointerFromTokens(t *testing.T) {
27 tests := []struct {
28 name string
29 tokens []string
30 want string
31 }{
32 {
33 name: "empty",
34 tokens: []string{},
35 want: "",
36 },
37 {
38 name: "single_simple_token",
39 tokens: []string{"foo"},
40 want: "/foo",
41 },
42 {
43 name: "multiple_simple_tokens",
44 tokens: []string{"foo", "bar", "baz"},
45 want: "/foo/bar/baz",
46 },
47 {
48 name: "tokens_with_slash",
49 tokens: []string{"foo/bar", "baz"},
50 want: "/foo~1bar/baz",
51 },
52 {
53 name: "tokens_with_tilde",
54 tokens: []string{"foo~bar", "baz"},
55 want: "/foo~0bar/baz",
56 },
57 {
58 name: "tokens_with_both_slash_and_tilde",
59 tokens: []string{"foo~/bar", "baz~test/more"},
60 want: "/foo~0~1bar/baz~0test~1more",
61 },
62 {
63 name: "empty_token",
64 tokens: []string{"foo", "", "bar"},
65 want: "/foo//bar",
66 },
67 {
68 name: "numeric_tokens",
69 tokens: []string{"0", "123", "foo"},
70 want: "/0/123/foo",
71 },
72 {
73 name: "special_chars",
74 tokens: []string{"foo bar", "test\tmore", "with\nnewline"},
75 want: "/foo bar/test\tmore/with\nnewline",
76 },
77 }
78
79 for _, tt := range tests {
80 t.Run(tt.name, func(t *testing.T) {
81 got := PointerFromTokens(slices.Values(tt.tokens))
82 qt.Check(t, qt.Equals(string(got), tt.want))
83 })
84 }
85}
86
87func TestPointerTokens(t *testing.T) {
88 tests := []struct {
89 name string
90 pointer string
91 want []string
92 }{
93 {
94 name: "empty",
95 pointer: "",
96 want: nil,
97 },
98 {
99 name: "root",
100 pointer: "/",
101 want: []string{""},
102 },
103 {
104 name: "single_token",
105 pointer: "/foo",
106 want: []string{"foo"},
107 },
108 {
109 name: "multiple_tokens",
110 pointer: "/foo/bar/baz",
111 want: []string{"foo", "bar", "baz"},
112 },
113 {
114 name: "escaped_slash",
115 pointer: "/foo~1bar/baz",
116 want: []string{"foo/bar", "baz"},
117 },
118 {
119 name: "escaped_tilde",
120 pointer: "/foo~0bar/baz",
121 want: []string{"foo~bar", "baz"},
122 },
123 {
124 name: "both_escapes",
125 pointer: "/foo~0~1bar/baz~0test~1more",
126 want: []string{"foo~/bar", "baz~test/more"},
127 },
128 {
129 name: "empty_tokens",
130 pointer: "/foo//bar",
131 want: []string{"foo", "", "bar"},
132 },
133 {
134 name: "numeric_tokens",
135 pointer: "/0/123/foo",
136 want: []string{"0", "123", "foo"},
137 },
138 {
139 name: "special_chars",
140 pointer: "/foo bar/test\tmore/with\nnewline",
141 want: []string{"foo bar", "test\tmore", "with\nnewline"},
142 },
143 {
144 name: "no_leading_slash",
145 pointer: "foo/bar",
146 want: []string{"foo", "bar"},
147 },
148 }
149
150 for _, tt := range tests {
151 t.Run(tt.name, func(t *testing.T) {
152 ptr := Pointer(tt.pointer)
153 got := slices.Collect(ptr.Tokens())
154 qt.Check(t, qt.DeepEquals(got, tt.want))
155 })
156 }
157}
158
159func TestPointerRoundTrip(t *testing.T) {
160 tests := []struct {
161 name string
162 tokens []string
163 }{
164 {"simple", []string{"foo", "bar", "baz"}},
165 {"with_slashes", []string{"foo/bar", "baz/qux"}},
166 {"with_tildes", []string{"foo~bar", "baz~qux"}},
167 {"with_both", []string{"foo~/bar", "baz~qux/more"}},
168 {"empty_tokens", []string{"foo", "", "bar"}},
169 {"numeric", []string{"0", "123", "456"}},
170 {"special_chars", []string{"foo bar", "test\tmore", "with\nnewline"}},
171 }
172
173 for _, tt := range tests {
174 t.Run(tt.name, func(t *testing.T) {
175 pointer := PointerFromTokens(slices.Values(tt.tokens))
176 roundTrip := slices.Collect(pointer.Tokens())
177 qt.Check(t, qt.DeepEquals(roundTrip, tt.tokens))
178 })
179 }
180}
181
182func TestPointerFromCUEPath(t *testing.T) {
183 tests := []struct {
184 name string
185 path string
186 want string
187 wantErr bool
188 }{
189 {
190 name: "empty_path",
191 path: "",
192 want: "",
193 },
194 {
195 name: "simple_string_field",
196 path: "foo",
197 want: "/foo",
198 },
199 {
200 name: "nested_string_fields",
201 path: "foo.bar.baz",
202 want: "/foo/bar/baz",
203 },
204 {
205 name: "string_field_with_index",
206 path: "foo[0]",
207 want: "/foo/0",
208 },
209 {
210 name: "complex_path",
211 path: "foo.bar[123].baz",
212 want: "/foo/bar/123/baz",
213 },
214 {
215 name: "string_with_special_chars",
216 path: `"foo/bar"."baz~qux"`,
217 want: "/foo~1bar/baz~0qux",
218 },
219 {
220 name: "multiple_indices",
221 path: "arr[0][1][2]",
222 want: "/arr/0/1/2",
223 },
224 }
225
226 for _, tt := range tests {
227 t.Run(tt.name, func(t *testing.T) {
228 path := cue.ParsePath(tt.path)
229 if path.Err() != nil {
230 t.Fatalf("failed to parse path %q: %v", tt.path, path.Err())
231 }
232
233 got, err := PointerFromCUEPath(path)
234 if tt.wantErr {
235 qt.Check(t, qt.IsNotNil(err))
236 return
237 }
238 qt.Check(t, qt.IsNil(err))
239 qt.Check(t, qt.Equals(string(got), tt.want))
240 })
241 }
242}
243
244func TestPointerFromCUEPathEdgeCases(t *testing.T) {
245 t.Run("empty_string_field", func(t *testing.T) {
246 path := cue.ParsePath(`""`)
247 qt.Check(t, qt.IsNil(path.Err()))
248
249 got, err := PointerFromCUEPath(path)
250 qt.Check(t, qt.IsNil(err))
251 qt.Check(t, qt.Equals(string(got), "/"))
252 })
253
254 t.Run("error_case", func(t *testing.T) {
255 // Test that unsupported selector types return an error
256 path := cue.MakePath(cue.Def("foo")) // Definition selector should error
257
258 got, err := PointerFromCUEPath(path)
259 qt.Check(t, qt.IsNotNil(err))
260 qt.Check(t, qt.Equals(string(got), ""))
261 qt.Check(t, qt.StringContains(err.Error(), "cannot convert selector"))
262 })
263}