1 package ldcontext
2
3 import (
4 "encoding/json"
5 "sort"
6 "testing"
7
8 "github.com/launchdarkly/go-sdk-common/v3/ldattr"
9 "github.com/launchdarkly/go-sdk-common/v3/lderrors"
10 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
11
12 "github.com/launchdarkly/go-test-helpers/v3/jsonhelpers"
13
14 "github.com/stretchr/testify/assert"
15 )
16
17
18
19
20 func TestUninitializedContext(t *testing.T) {
21 var c Context
22 assert.False(t, c.IsDefined())
23 assert.Equal(t, lderrors.ErrContextUninitialized{}, c.Err())
24 }
25
26 func TestMultiple(t *testing.T) {
27 sc := New("my-key")
28 mc := NewMulti(New("my-key"), NewWithKind("org", "my-key"))
29 assert.False(t, sc.Multiple())
30 assert.True(t, mc.Multiple())
31 }
32
33 func TestGetOptionalAttributeNames(t *testing.T) {
34 sorted := func(values []string) []string {
35 ret := append([]string(nil), values...)
36 sort.Strings(ret)
37 return ret
38 }
39
40 t.Run("none", func(t *testing.T) {
41 c := New("my-key")
42 an := c.GetOptionalAttributeNames(nil)
43 assert.Len(t, an, 0)
44 })
45
46 t.Run("meta not included", func(t *testing.T) {
47 c := NewBuilder("my-key").Private("x").Anonymous(true).Build()
48 an := c.GetOptionalAttributeNames(nil)
49 assert.Len(t, an, 0)
50 })
51
52 t.Run("name", func(t *testing.T) {
53 c := NewBuilder("my-key").Name("x").Build()
54 an := c.GetOptionalAttributeNames(nil)
55 assert.Equal(t, []string{"name"}, an)
56 })
57
58 t.Run("others", func(t *testing.T) {
59 c := NewBuilder("my-key").SetString("email", "x").SetBool("happy", true).Build()
60 an := c.GetOptionalAttributeNames(nil)
61 sort.Strings(an)
62 assert.Equal(t, []string{"email", "happy"}, an)
63 })
64
65 t.Run("none for multi-context", func(t *testing.T) {
66 c := NewMulti(NewWithKind("kind1", "key1"), NewWithKind("otherkind", "otherkey"))
67 an := c.GetOptionalAttributeNames(nil)
68 assert.Len(t, an, 0)
69 })
70
71 t.Run("capacity of preallocated slice can be reused", func(t *testing.T) {
72 c := NewBuilder("my-key").SetString("email", "x").SetBool("happy", true).Build()
73 preallocSlice := make([]string, 2, 2)
74 emptySlice := preallocSlice[0:0]
75 an := c.GetOptionalAttributeNames(emptySlice)
76 assert.Equal(t, []string{"email", "happy"}, sorted(an))
77 preallocSlice[0] = "x"
78 assert.Equal(t, "x", an[0])
79 })
80
81 t.Run("preallocated slice is overwritten rather than appended to", func(t *testing.T) {
82 c := NewBuilder("my-key").SetString("email", "x").SetBool("happy", true).Build()
83 preallocSlice := make([]string, 2, 2)
84 an := c.GetOptionalAttributeNames(preallocSlice)
85 assert.Equal(t, []string{"email", "happy"}, sorted(an))
86 preallocSlice[0] = "x"
87 assert.Equal(t, "x", an[0])
88 })
89
90 t.Run("preallocated slice without enough capacity is not reused", func(t *testing.T) {
91 c := NewBuilder("my-key").SetString("email", "x").SetBool("happy", true).Build()
92 preallocSlice := make([]string, 1, 1)
93 emptySlice := preallocSlice[0:0]
94 an := c.GetOptionalAttributeNames(emptySlice)
95 assert.Equal(t, []string{"email", "happy"}, sorted(an))
96 preallocSlice[0] = "x"
97 assert.NotEqual(t, "x", an[0])
98 })
99 }
100
101 func TestGetValue(t *testing.T) {
102 t.Run("equivalent to GetValueForRef for simple attribute name", func(t *testing.T) {
103 c := NewBuilder("my-key").Kind("org").Name("x").SetString("my-attr", "y").SetString("/starts-with-slash", "z").Build()
104 expectAttributeFoundForName(t, ldvalue.String("org"), c, "kind")
105 expectAttributeFoundForName(t, ldvalue.String("my-key"), c, "key")
106 expectAttributeFoundForName(t, ldvalue.String("x"), c, "name")
107 expectAttributeFoundForName(t, ldvalue.String("y"), c, "my-attr")
108 expectAttributeFoundForName(t, ldvalue.String("z"), c, "/starts-with-slash")
109 expectAttributeNotFoundForName(t, c, "/kind")
110 expectAttributeNotFoundForName(t, c, "/key")
111 expectAttributeNotFoundForName(t, c, "/name")
112 expectAttributeNotFoundForName(t, c, "/my-attr")
113 expectAttributeNotFoundForName(t, c, "other")
114
115 expectAttributeNotFoundForName(t, c, "")
116 expectAttributeNotFoundForName(t, c, "/")
117
118 mc := NewMulti(c, NewWithKind("otherkind", "otherkey"))
119 expectAttributeFoundForName(t, ldvalue.String("multi"), mc, "kind")
120 expectAttributeNotFoundForName(t, mc, "/kind")
121 expectAttributeNotFoundForName(t, mc, "key")
122 })
123
124 t.Run("does not allow querying of subpath/element", func(t *testing.T) {
125 objValue := ldvalue.ObjectBuild().Set("a", ldvalue.Int(1)).Build()
126 arrayValue := ldvalue.ArrayOf(ldvalue.Int(1))
127 c := makeBasicBuilder().SetValue("obj-attr", objValue).SetValue("array-attr", arrayValue).Build()
128 expectAttributeFoundForName(t, objValue, c, "obj-attr")
129 expectAttributeFoundForName(t, arrayValue, c, "array-attr")
130 expectAttributeNotFoundForName(t, c, "/obj-attr/a")
131 expectAttributeNotFoundForName(t, c, "/array-attr/0")
132 })
133 }
134 func TestGetValueForRefSpecialTopLevelAttributes(t *testing.T) {
135 t.Run("kind", func(t *testing.T) {
136 t.Run("single-kind", func(t *testing.T) {
137 c := NewWithKind("org", "my-key")
138 expectAttributeFoundForRef(t, ldvalue.String("org"), c, "kind")
139 })
140
141 t.Run("multi-kind", func(t *testing.T) {
142 c := NewMulti(New("my-key"), NewWithKind("otherkind", "otherkey"))
143 expectAttributeFoundForRef(t, ldvalue.String("multi"), c, "kind")
144 })
145 })
146
147 t.Run("key", func(t *testing.T) {
148 t.Run("single-kind", func(t *testing.T) {
149 c := New("my-key")
150 expectAttributeFoundForRef(t, ldvalue.String("my-key"), c, "key")
151 })
152
153 t.Run("multi-kind", func(t *testing.T) {
154 c := NewMulti(New("my-key"), NewWithKind("otherkind", "otherkey"))
155 expectAttributeNotFoundForRef(t, c, "key")
156 })
157 })
158
159 t.Run("name", func(t *testing.T) {
160 t.Run("single-kind, defined", func(t *testing.T) {
161 c := makeBasicBuilder().Name("my-name").Build()
162 expectAttributeFoundForRef(t, ldvalue.String("my-name"), c, "name")
163 })
164
165 t.Run("single-kind, undefined", func(t *testing.T) {
166 c := makeBasicBuilder().Build()
167 expectAttributeNotFoundForRef(t, c, "name")
168 })
169
170 t.Run("multi-kind", func(t *testing.T) {
171 c := NewMulti(makeBasicBuilder().Name("my-name").Build(), NewWithKind("otherkind", "otherkey"))
172 expectAttributeNotFoundForRef(t, c, "name")
173 })
174 })
175
176 t.Run("anonymous", func(t *testing.T) {
177 t.Run("single-kind, defined, true", func(t *testing.T) {
178 c := makeBasicBuilder().Anonymous(true).Build()
179 expectAttributeFoundForRef(t, ldvalue.Bool(true), c, "anonymous")
180 })
181
182 t.Run("single-kind, defined, false", func(t *testing.T) {
183 c := makeBasicBuilder().Anonymous(false).Build()
184 expectAttributeFoundForRef(t, ldvalue.Bool(false), c, "anonymous")
185 })
186
187 t.Run("single-kind, undefined", func(t *testing.T) {
188 c := makeBasicBuilder().Build()
189 expectAttributeFoundForRef(t, ldvalue.Bool(false), c, "anonymous")
190 })
191
192 t.Run("multi-kind", func(t *testing.T) {
193 c := NewMulti(makeBasicBuilder().Anonymous(true).Build(), NewWithKind("otherkind", "otherkey"))
194 expectAttributeNotFoundForRef(t, c, "anonymous")
195 })
196 })
197 }
198
199 func TestGetValueForRefCannotGetMetaProperties(t *testing.T) {
200 t.Run("privateAttributes", func(t *testing.T) {
201 t.Run("single-kind, defined", func(t *testing.T) {
202 c := makeBasicBuilder().Private("attr").Build()
203 expectAttributeNotFoundForRef(t, c, "privateAttributes")
204 })
205
206 t.Run("single-kind, undefined", func(t *testing.T) {
207 c := makeBasicBuilder().Build()
208 expectAttributeNotFoundForRef(t, c, "privateAttributes")
209 })
210
211 t.Run("multi-kind", func(t *testing.T) {
212 c := NewMultiBuilder().Add(makeBasicBuilder().Private("attr").Build()).Build()
213 expectAttributeNotFoundForRef(t, c, "privateAttributes")
214 })
215 })
216 }
217
218 func TestGetValueForRefCustomAttributeSingleKind(t *testing.T) {
219 t.Run("simple attribute name", func(t *testing.T) {
220 expected := ldvalue.String("abc")
221 c := makeBasicBuilder().SetValue("my-attr", expected).Build()
222 expectAttributeFoundForRef(t, expected, c, "my-attr")
223 })
224
225 t.Run("simple attribute name not found", func(t *testing.T) {
226 c := makeBasicBuilder().Build()
227 expectAttributeNotFoundForRef(t, c, "my-attr")
228 })
229
230 t.Run("property in object", func(t *testing.T) {
231 expected := ldvalue.String("abc")
232 object := ldvalue.ObjectBuild().Set("my-prop", expected).Build()
233 c := makeBasicBuilder().SetValue("my-attr", object).Build()
234 expectAttributeFoundForRef(t, expected, c, "/my-attr/my-prop")
235 })
236
237 t.Run("property in raw JSON object", func(t *testing.T) {
238 expected := ldvalue.String("abc")
239 object := ldvalue.Raw(json.RawMessage(`{"my-prop": "abc"}`))
240 c := makeBasicBuilder().SetValue("my-attr", object).Build()
241 expectAttributeFoundForRef(t, expected, c, "/my-attr/my-prop")
242 })
243
244 t.Run("property in object not found", func(t *testing.T) {
245 expected := ldvalue.String("abc")
246 object := ldvalue.ObjectBuild().Set("my-prop", expected).Build()
247 c := makeBasicBuilder().SetValue("my-attr", object).Build()
248 expectAttributeNotFoundForRef(t, c, "/my-attr/other-prop")
249 })
250
251 t.Run("property in nested object", func(t *testing.T) {
252 expected := ldvalue.String("abc")
253 object := ldvalue.ObjectBuild().Set("my-prop", ldvalue.ObjectBuild().Set("sub-prop", expected).Build()).Build()
254 c := makeBasicBuilder().SetValue("my-attr", object).Build()
255 expectAttributeFoundForRef(t, expected, c, "/my-attr/my-prop/sub-prop")
256 })
257
258 t.Run("property in value that is not an object", func(t *testing.T) {
259 c := makeBasicBuilder().SetValue("my-attr", ldvalue.String("xyz")).Build()
260 expectAttributeNotFoundForRef(t, c, "/my-attr/my-prop")
261 })
262
263 t.Run("property whose name is a numeric string", func(t *testing.T) {
264 expected := ldvalue.String("good")
265 object := ldvalue.ObjectBuild().Set("1", expected).Build()
266 c := makeBasicBuilder().SetValue("my-attr", object).Build()
267 expectAttributeFoundForRef(t, expected, c, "/my-attr/1")
268 })
269
270 t.Run("property not applicable to array", func(t *testing.T) {
271 array := ldvalue.ArrayOf(ldvalue.String("bad"), ldvalue.String("worse"))
272 c := makeBasicBuilder().SetValue("my-attr", array).Build()
273 expectAttributeNotFoundForRef(t, c, "/my-attr/1")
274 })
275
276 t.Run("property not applicable to simple value", func(t *testing.T) {
277 c := makeBasicBuilder().SetValue("my-attr", ldvalue.String("xyz")).Build()
278 expectAttributeNotFoundForRef(t, c, "/my-attr/a")
279 })
280 }
281
282 func TestContextString(t *testing.T) {
283 c := makeBasicBuilder().Name("x").Anonymous(true).SetString("attr", "value").Build()
284 j, _ := json.Marshal(c)
285 s := c.String()
286 jsonhelpers.AssertEqual(t, j, s)
287 }
288
289 func TestGetValueForInvalidRef(t *testing.T) {
290 c := makeBasicBuilder().Build()
291 expectAttributeNotFoundForRef(t, c, "/")
292 }
293
294 func TestIndividualContextCount(t *testing.T) {
295 t.Run("single", func(t *testing.T) {
296 c := New("my-key")
297 assert.Equal(t, 1, c.IndividualContextCount())
298 })
299
300 t.Run("multi", func(t *testing.T) {
301 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
302 c := NewMulti(sub1, sub2)
303 assert.Equal(t, 2, c.IndividualContextCount())
304 })
305 }
306
307 func TestIndividualContextByIndex(t *testing.T) {
308 t.Run("single", func(t *testing.T) {
309 c := New("my-key")
310
311 assert.Equal(t, c, c.IndividualContextByIndex(0))
312 assert.Equal(t, Context{}, c.IndividualContextByIndex(1))
313 assert.Equal(t, Context{}, c.IndividualContextByIndex(-1))
314 })
315
316 t.Run("multi", func(t *testing.T) {
317 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
318 c := NewMulti(sub1, sub2)
319
320
321 assert.Equal(t, sub1, c.IndividualContextByIndex(0))
322 assert.Equal(t, sub2, c.IndividualContextByIndex(1))
323 assert.Equal(t, Context{}, c.IndividualContextByIndex(2))
324 assert.Equal(t, Context{}, c.IndividualContextByIndex(-1))
325 })
326 }
327
328 func TestIndividualContextByKind(t *testing.T) {
329 t.Run("single", func(t *testing.T) {
330 c := NewWithKind("kind1", "my-key")
331
332 assert.Equal(t, c, c.IndividualContextByKind("kind1"))
333 assert.Equal(t, Context{}, c.IndividualContextByKind("other"))
334 })
335
336 t.Run("multi", func(t *testing.T) {
337 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
338 c := NewMulti(sub1, sub2)
339
340 assert.Equal(t, sub1, c.IndividualContextByKind("kind1"))
341 assert.Equal(t, sub2, c.IndividualContextByKind("kind2"))
342 assert.Equal(t, Context{}, c.IndividualContextByKind("other"))
343 })
344
345 t.Run("default", func(t *testing.T) {
346 sub1, sub2 := New("userkey"), NewWithKind("kind2", "key2")
347 c := NewMulti(sub1, sub2)
348
349 assert.Equal(t, sub1, c.IndividualContextByKind(""))
350 })
351 }
352
353 func TestIndividualContextKeyByKind(t *testing.T) {
354 t.Run("single", func(t *testing.T) {
355 c := NewWithKind("kind1", "my-key")
356
357 assert.Equal(t, "my-key", c.IndividualContextKeyByKind("kind1"))
358 assert.Equal(t, "", c.IndividualContextKeyByKind("other"))
359 })
360
361 t.Run("multi", func(t *testing.T) {
362 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
363 c := NewMulti(sub1, sub2)
364
365 assert.Equal(t, "key1", c.IndividualContextKeyByKind("kind1"))
366 assert.Equal(t, "key2", c.IndividualContextKeyByKind("kind2"))
367 assert.Equal(t, "", c.IndividualContextKeyByKind("other"))
368 })
369
370 t.Run("default", func(t *testing.T) {
371 sub1, sub2 := New("userkey"), NewWithKind("kind2", "key2")
372 c := NewMulti(sub1, sub2)
373
374 assert.Equal(t, "userkey", c.IndividualContextKeyByKind(""))
375 })
376 }
377
378 func TestGetAllIndividualContexts(t *testing.T) {
379 t.Run("single", func(t *testing.T) {
380 c := NewWithKind("kind1", "my-key")
381
382 assert.Equal(t, []Context{c}, c.GetAllIndividualContexts(nil))
383 })
384
385 t.Run("multi", func(t *testing.T) {
386 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
387 c := NewMulti(sub1, sub2)
388
389
390 assert.Equal(t, []Context{sub1, sub2}, c.GetAllIndividualContexts(nil))
391 })
392
393 t.Run("capacity of preallocated slice can be reused", func(t *testing.T) {
394 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
395 c := NewMulti(sub1, sub2)
396
397 preallocSlice := make([]Context, 2, 2)
398 emptySlice := preallocSlice[0:0]
399 all := c.GetAllIndividualContexts(emptySlice)
400 assert.Equal(t, []Context{sub1, sub2}, all)
401 preallocSlice[0] = New("different")
402 assert.Equal(t, preallocSlice[0], all[0])
403 })
404
405 t.Run("preallocated slice is overwritten rather than appended to", func(t *testing.T) {
406 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
407 c := NewMulti(sub1, sub2)
408
409 preallocSlice := make([]Context, 2, 2)
410 all := c.GetAllIndividualContexts(preallocSlice)
411 assert.Equal(t, []Context{sub1, sub2}, all)
412 preallocSlice[0] = New("different")
413 assert.Equal(t, preallocSlice[0], all[0])
414 })
415
416 t.Run("preallocated slice without enough capacity is not reused", func(t *testing.T) {
417 sub1, sub2 := NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2")
418 c := NewMulti(sub1, sub2)
419
420 preallocSlice := make([]Context, 1, 1)
421 emptySlice := preallocSlice[0:0]
422 all := c.GetAllIndividualContexts(emptySlice)
423 assert.Equal(t, []Context{sub1, sub2}, all)
424 preallocSlice[0] = New("different")
425 assert.NotEqual(t, preallocSlice[0], all[0])
426 })
427 }
428
429 func TestCanGetSecondaryKeyIfPrivatelySet(t *testing.T) {
430 c1, c2 := New("key1"), New("key2")
431 c2.secondary = ldvalue.NewOptionalString("x")
432 assert.Equal(t, ldvalue.OptionalString{}, c1.Secondary())
433 assert.Equal(t, c2.secondary, c2.Secondary())
434 }
435
436 func TestContextEqual(t *testing.T) {
437
438
439 makeInstances := [][]func() Context{
440 {func() Context { return Context{} }},
441 {func() Context { return New("a") }},
442 {func() Context { return New("b") }},
443 {func() Context { return NewWithKind("k1", "a") }},
444 {func() Context { return NewWithKind("k2", "a") }},
445 {func() Context { return NewBuilder("a").Name("b").Build() }},
446 {func() Context { return NewBuilder("a").Name("c").Build() }},
447 {func() Context { return NewBuilder("a").Anonymous(true).Build() }},
448 {func() Context { return NewBuilder("a").SetBool("b", true).Build() }},
449 {func() Context { return NewBuilder("a").SetBool("b", false).Build() }},
450 {func() Context { return NewBuilder("a").SetInt("b", 0).Build() }},
451 {func() Context { return NewBuilder("a").SetInt("b", 1).Build() }},
452 {func() Context { return NewBuilder("a").SetString("b", "").Build() }},
453 {func() Context { return NewBuilder("a").SetString("b", "c").Build() }},
454 {func() Context { return NewBuilder("a").SetBool("b", true).SetBool("c", false).Build() },
455 func() Context { return NewBuilder("a").SetBool("c", false).SetBool("b", true).Build() }},
456 {func() Context { return NewBuilder("a").Name("b").Private("name").Build() }},
457 {func() Context { return NewBuilder("a").Name("b").SetBool("c", true).Private("name").Build() }},
458 {func() Context { return NewBuilder("a").Name("b").SetBool("c", true).Private("name", "c").Build() },
459 func() Context { return NewBuilder("a").Name("b").SetBool("c", true).Private("c", "name").Build() }},
460 {func() Context { return NewBuilder("a").Name("b").SetBool("c", true).Private("name", "d").Build() }},
461 {func() Context { return NewMulti(NewWithKind("k1", "a"), NewWithKind("k2", "b")) },
462 func() Context { return NewMulti(NewWithKind("k2", "b"), NewWithKind("k1", "a")) }},
463 {func() Context { return NewMulti(NewWithKind("k1", "a"), NewWithKind("k2", "c")) }},
464 {func() Context { return NewMulti(NewWithKind("k1", "a"), NewWithKind("k3", "b")) }},
465 {func() Context {
466 return NewMulti(NewWithKind("k1", "a"), NewWithKind("k2", "b"), NewWithKind("k3", "c"))
467 }},
468 }
469 for i, equalGroup := range makeInstances {
470 for _, factory1 := range equalGroup {
471 c1 := factory1()
472 for _, factory2 := range equalGroup {
473 c2 := factory2()
474 assert.True(t, c1.Equal(c2), "%s should have equaled %s", c1, c2)
475 }
476 for j, unequalGroup := range makeInstances {
477 if i == j {
478 continue
479 }
480 c2 := unequalGroup[0]()
481 assert.False(t, c1.Equal(c2), "%s should not have equaled %s", c1, c2)
482 }
483 }
484 }
485 }
486
487 func expectAttributeFoundForName(t *testing.T, expected ldvalue.Value, c Context, attrName string) {
488 t.Helper()
489 value := c.GetValue(attrName)
490 assert.True(t, value.IsDefined(), "attribute %q should have been found, but was not", attrName)
491 jsonhelpers.AssertEqual(t, expected, value)
492 }
493
494 func expectAttributeNotFoundForName(t *testing.T, c Context, attrName string) {
495 t.Helper()
496 value := c.GetValue(attrName)
497 assert.False(t, value.IsDefined(), "attribute %q should not have been found, but was", attrName)
498 jsonhelpers.AssertEqual(t, `null`, value)
499 }
500
501 func expectAttributeFoundForRef(t *testing.T, expected ldvalue.Value, c Context, attrRefString string) {
502 t.Helper()
503 value := c.GetValueForRef(ldattr.NewRef(attrRefString))
504 assert.True(t, value.IsDefined(), "attribute %q should have been found, but was not", attrRefString)
505 jsonhelpers.AssertEqual(t, expected, value)
506 }
507
508 func expectAttributeNotFoundForRef(t *testing.T, c Context, attrRefString string) {
509 t.Helper()
510 value := c.GetValueForRef(ldattr.NewRef(attrRefString))
511 assert.False(t, value.IsDefined(), "attribute %q should not have been found, but was", attrRefString)
512 jsonhelpers.AssertEqual(t, `null`, value)
513 }
514
View as plain text