...

Source file src/github.com/launchdarkly/go-sdk-common/v3/ldcontext/builder_simple_test.go

Documentation: github.com/launchdarkly/go-sdk-common/v3/ldcontext

     1  package ldcontext
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldattr"
     8  	"github.com/launchdarkly/go-sdk-common/v3/lderrors"
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    10  
    11  	"github.com/launchdarkly/go-test-helpers/v3/jsonhelpers"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  )
    15  
    16  type invalidKindTestParams struct {
    17  	kind string
    18  	err  error
    19  }
    20  
    21  func makeInvalidKindTestParams() []invalidKindTestParams {
    22  	return []invalidKindTestParams{
    23  		{"kind", lderrors.ErrContextKindCannotBeKind{}},
    24  		{"multi", lderrors.ErrContextKindMultiForSingleKind{}},
    25  		{"örg", lderrors.ErrContextKindInvalidChars{}},
    26  		{"o~rg", lderrors.ErrContextKindInvalidChars{}},
    27  		{"😀rg", lderrors.ErrContextKindInvalidChars{}},
    28  		{"o\trg", lderrors.ErrContextKindInvalidChars{}},
    29  	}
    30  }
    31  
    32  func makeBasicBuilder() *Builder {
    33  	// for test cases where the kind and key are unimportant
    34  	return NewBuilder("my-key")
    35  }
    36  
    37  func TestBuilderDefaultProperties(t *testing.T) {
    38  	c := NewBuilder("my-key").Build()
    39  	assert.True(t, c.IsDefined())
    40  	assert.NoError(t, c.Err())
    41  	assert.Equal(t, DefaultKind, c.Kind())
    42  	assert.Equal(t, "my-key", c.Key())
    43  
    44  	assert.Equal(t, ldvalue.OptionalString{}, c.Name())
    45  	assert.False(t, c.Anonymous())
    46  	assert.Equal(t, ldvalue.OptionalString{}, c.Secondary())
    47  	assert.Len(t, c.GetOptionalAttributeNames(nil), 0)
    48  }
    49  
    50  func TestBuilderKindValidation(t *testing.T) {
    51  	for _, p := range makeInvalidKindTestParams() {
    52  		t.Run(p.kind, func(t *testing.T) {
    53  			b := NewBuilder("my-key").Kind(Kind(p.kind))
    54  
    55  			c0 := b.Build()
    56  			assert.True(t, c0.IsDefined())
    57  			assert.Equal(t, p.err, c0.Err())
    58  
    59  			c1, err := b.TryBuild()
    60  			assert.True(t, c1.IsDefined())
    61  			assert.Equal(t, p.err, c1.Err())
    62  			assert.Equal(t, p.err, err)
    63  		})
    64  	}
    65  }
    66  
    67  func TestBuilderKeyValidation(t *testing.T) {
    68  	b := NewBuilder("")
    69  
    70  	c0 := b.Build()
    71  	assert.True(t, c0.IsDefined())
    72  	assert.Equal(t, lderrors.ErrContextKeyEmpty{}, c0.Err())
    73  
    74  	c1, err := b.TryBuild()
    75  	assert.True(t, c1.IsDefined())
    76  	assert.Equal(t, lderrors.ErrContextKeyEmpty{}, c1.Err())
    77  	assert.Equal(t, lderrors.ErrContextKeyEmpty{}, err)
    78  }
    79  
    80  func TestBuilderFullyQualifiedKey(t *testing.T) {
    81  	t.Run("kind is user", func(t *testing.T) {
    82  		c := New("my-user-key")
    83  		assert.Equal(t, "my-user-key", c.FullyQualifiedKey())
    84  	})
    85  
    86  	t.Run("kind is not user", func(t *testing.T) {
    87  		c := NewWithKind("org", "my-org-key")
    88  		assert.Equal(t, "org:my-org-key", c.FullyQualifiedKey())
    89  	})
    90  
    91  	t.Run("key is escaped", func(t *testing.T) {
    92  		c := NewWithKind("org", "my:key%x/y")
    93  		assert.Equal(t, "org:my%3Akey%25x/y", c.FullyQualifiedKey())
    94  	})
    95  }
    96  
    97  func TestBuilderBasicSetters(t *testing.T) {
    98  	t.Run("Kind", func(t *testing.T) {
    99  		assert.Equal(t, Kind("org"), NewBuilder("my-key").Kind("org").Build().Kind())
   100  
   101  		assert.Equal(t, DefaultKind, NewBuilder("my-key").Kind("").Build().Kind())
   102  	})
   103  
   104  	t.Run("Key", func(t *testing.T) {
   105  		assert.Equal(t, "other-key", NewBuilder("my-key").Key("other-key").Build().Key())
   106  	})
   107  
   108  	t.Run("Name", func(t *testing.T) {
   109  		c0 := makeBasicBuilder().Build()
   110  		assert.Equal(t, ldvalue.OptionalString{}, c0.Name())
   111  
   112  		c1 := makeBasicBuilder().Name("my-name").Build()
   113  		assert.Equal(t, ldvalue.NewOptionalString("my-name"), c1.Name())
   114  
   115  		c2 := makeBasicBuilder().OptName(ldvalue.OptionalString{}).Build()
   116  		assert.Equal(t, ldvalue.OptionalString{}, c2.Name())
   117  
   118  		c3 := makeBasicBuilder().OptName(ldvalue.NewOptionalString("my-name")).Build()
   119  		assert.Equal(t, ldvalue.NewOptionalString("my-name"), c3.Name())
   120  	})
   121  
   122  	t.Run("Secondary", func(t *testing.T) {
   123  		c0 := makeBasicBuilder().Build()
   124  		assert.Equal(t, ldvalue.OptionalString{}, c0.Secondary())
   125  	})
   126  
   127  	t.Run("Anonymous", func(t *testing.T) {
   128  		c0 := makeBasicBuilder().Build()
   129  		assert.False(t, c0.Anonymous())
   130  
   131  		c1 := makeBasicBuilder().Anonymous(false).Build()
   132  		assert.False(t, c1.Anonymous())
   133  
   134  		c2 := makeBasicBuilder().Anonymous(true).Build()
   135  		assert.True(t, c2.Anonymous())
   136  	})
   137  }
   138  
   139  func TestBuilderSetCustomAttributes(t *testing.T) {
   140  	t.Run("SetValue", func(t *testing.T) {
   141  		otherValue := ldvalue.String("other-value")
   142  		for _, value := range []ldvalue.Value{
   143  			ldvalue.Bool(true),
   144  			ldvalue.Bool(false),
   145  			ldvalue.Int(0),
   146  			ldvalue.Int(1),
   147  			ldvalue.String(""),
   148  			ldvalue.String("x"),
   149  			ldvalue.ArrayOf(ldvalue.Int(1), ldvalue.Int(2)),
   150  			ldvalue.ObjectBuild().Set("a", ldvalue.Int(1)).Build(),
   151  		} {
   152  			t.Run(value.JSONString(), func(t *testing.T) {
   153  				c := makeBasicBuilder().
   154  					SetValue("my-attr", value).
   155  					SetValue("other-attr", otherValue).
   156  					Build()
   157  				assert.Len(t, c.attributes.Keys(nil), 2)
   158  				jsonhelpers.AssertEqual(t, value, c.attributes.Get("my-attr"))
   159  				jsonhelpers.AssertEqual(t, otherValue, c.attributes.Get("other-attr"))
   160  			})
   161  		}
   162  	})
   163  
   164  	t.Run("typed setters", func(t *testing.T) {
   165  		// For the typed setters, just verify that they produce the same builder state as SetValue
   166  		assert.Equal(t,
   167  			makeBasicBuilder().SetValue("my-attr", ldvalue.Bool(true)),
   168  			makeBasicBuilder().SetBool("my-attr", true))
   169  		assert.Equal(t,
   170  			makeBasicBuilder().SetValue("my-attr", ldvalue.Int(100)),
   171  			makeBasicBuilder().SetInt("my-attr", 100))
   172  		assert.Equal(t,
   173  			makeBasicBuilder().SetValue("my-attr", ldvalue.Float64(1.5)),
   174  			makeBasicBuilder().SetFloat64("my-attr", 1.5))
   175  		assert.Equal(t,
   176  			makeBasicBuilder().SetValue("my-attr", ldvalue.String("x")),
   177  			makeBasicBuilder().SetString("my-attr", "x"))
   178  	})
   179  
   180  	t.Run("setting to null does not add attribute", func(t *testing.T) {
   181  		assert.Equal(t,
   182  			makeBasicBuilder().SetString("attr1", "value1").SetString("attr3", "value3"),
   183  			makeBasicBuilder().SetString("attr1", "value1").SetValue("attr2", ldvalue.Null()).SetString("attr3", "value3"))
   184  	})
   185  
   186  	t.Run("setting to null removes existing attribute", func(t *testing.T) {
   187  		assert.Equal(t,
   188  			makeBasicBuilder().SetString("attr1", "value1").SetString("attr3", "value3"),
   189  			makeBasicBuilder().SetString("attr1", "value1").SetString("attr2", "value2").SetString("attr3", "value3").
   190  				SetValue("attr2", ldvalue.Null()))
   191  	})
   192  
   193  	t.Run("cannot add attribute with empty name", func(t *testing.T) {
   194  		assert.Equal(t, makeBasicBuilder().Build(), makeBasicBuilder().SetBool("", true).Build())
   195  		assert.Equal(t, makeBasicBuilder().Build(), makeBasicBuilder().SetInt("", 1).Build())
   196  		assert.Equal(t, makeBasicBuilder().Build(), makeBasicBuilder().SetFloat64("", 1).Build())
   197  		assert.Equal(t, makeBasicBuilder().Build(), makeBasicBuilder().SetString("", "x").Build())
   198  		assert.Equal(t, makeBasicBuilder().Build(), makeBasicBuilder().SetValue("", ldvalue.ArrayOf()).Build())
   199  	})
   200  }
   201  
   202  func TestBuilderSetBuiltInAttributesByName(t *testing.T) {
   203  	var boolFalse, boolTrue, stringEmpty, stringNonEmpty = ldvalue.Bool(false), ldvalue.Bool(true),
   204  		ldvalue.String("x"), ldvalue.String("")
   205  	var nullValue, intValue, floatValue, arrayValue, objectValue = ldvalue.Null(),
   206  		ldvalue.Int(1), ldvalue.Float64(1.5), ldvalue.ArrayOf(), ldvalue.ObjectBuild().Build()
   207  
   208  	type params struct {
   209  		name             string
   210  		equivalentSetter func(*Builder, ldvalue.Value)
   211  		good, bad        []ldvalue.Value
   212  	}
   213  
   214  	for _, p := range []params{
   215  		{
   216  			name:             "kind",
   217  			equivalentSetter: func(b *Builder, v ldvalue.Value) { b.Kind(Kind(v.StringValue())) },
   218  			good:             []ldvalue.Value{stringNonEmpty, stringEmpty},
   219  			bad:              []ldvalue.Value{nullValue, boolFalse, intValue, floatValue, arrayValue, objectValue},
   220  		},
   221  		{
   222  			name:             "key",
   223  			equivalentSetter: func(b *Builder, v ldvalue.Value) { b.Key(v.StringValue()) },
   224  			good:             []ldvalue.Value{stringNonEmpty, stringEmpty},
   225  			bad:              []ldvalue.Value{nullValue, boolFalse, intValue, floatValue, arrayValue, objectValue},
   226  		},
   227  		{
   228  			name:             "name",
   229  			equivalentSetter: func(b *Builder, v ldvalue.Value) { b.OptName(v.AsOptionalString()) },
   230  			good:             []ldvalue.Value{stringNonEmpty, stringEmpty, nullValue},
   231  			bad:              []ldvalue.Value{boolFalse, intValue, floatValue, arrayValue, objectValue},
   232  		},
   233  		{
   234  			name:             "anonymous",
   235  			equivalentSetter: func(b *Builder, v ldvalue.Value) { b.Anonymous(v.BoolValue()) },
   236  			good:             []ldvalue.Value{boolTrue, boolFalse},
   237  			bad:              []ldvalue.Value{nullValue, intValue, floatValue, stringEmpty, stringNonEmpty, arrayValue, objectValue},
   238  		},
   239  	} {
   240  		t.Run(p.name, func(t *testing.T) {
   241  			builder := makeBasicBuilder() // we will reuse this to prove that SetValue overwrites previous values
   242  			var lastGoodNonNullValue ldvalue.Value
   243  
   244  			for _, goodValue := range p.good {
   245  				t.Run(fmt.Sprintf("can set to %s", goodValue.JSONString()), func(t *testing.T) {
   246  					previousState := *builder
   247  
   248  					if !goodValue.IsNull() {
   249  						lastGoodNonNullValue = goodValue
   250  					}
   251  					expected := makeBasicBuilder()
   252  					p.equivalentSetter(expected, goodValue)
   253  
   254  					builder.SetValue(p.name, goodValue)
   255  					assert.Equal(t, expected, builder)
   256  
   257  					b1 := previousState
   258  					assert.True(t, b1.TrySetValue(p.name, goodValue))
   259  					assert.Equal(t, *expected, b1)
   260  
   261  					b2 := previousState
   262  					switch goodValue.Type() {
   263  					case ldvalue.BoolType:
   264  						assert.Equal(t, expected, b2.SetBool(p.name, goodValue.BoolValue()))
   265  					case ldvalue.StringType:
   266  						assert.Equal(t, expected, b2.SetString(p.name, goodValue.StringValue()))
   267  					}
   268  				})
   269  			}
   270  			for _, badValue := range p.bad {
   271  				t.Run(fmt.Sprintf("cannot set to %s", badValue.JSONString()), func(t *testing.T) {
   272  					startingState := func() *Builder {
   273  						if lastGoodNonNullValue.IsDefined() {
   274  							return makeBasicBuilder().SetValue(p.name, lastGoodNonNullValue)
   275  						}
   276  						return makeBasicBuilder()
   277  					}
   278  
   279  					assert.Equal(t, startingState(), startingState().SetValue(p.name, badValue))
   280  
   281  					b := startingState()
   282  					assert.False(t, b.TrySetValue(p.name, badValue))
   283  					assert.Equal(t, startingState(), b)
   284  
   285  					switch badValue.Type() {
   286  					case ldvalue.BoolType:
   287  						assert.Equal(t, startingState(), startingState().SetBool(p.name, badValue.BoolValue()))
   288  					case ldvalue.NumberType:
   289  						if badValue.IsInt() {
   290  							assert.Equal(t, startingState(), startingState().SetInt(p.name, badValue.IntValue()))
   291  						} else {
   292  							assert.Equal(t, startingState(), startingState().SetFloat64(p.name, badValue.Float64Value()))
   293  						}
   294  					case ldvalue.StringType:
   295  						assert.Equal(t, startingState(), makeBasicBuilder().SetString(p.name, badValue.StringValue()))
   296  					}
   297  				})
   298  			}
   299  		})
   300  	}
   301  }
   302  
   303  func TestBuilderSetValueCannotSetMetaProperties(t *testing.T) {
   304  	for _, p := range []struct {
   305  		name  string
   306  		value ldvalue.Value
   307  	}{
   308  		{"secondary", ldvalue.String("x")},
   309  		{"privateAttributes", ldvalue.ArrayOf(ldvalue.String("x"))},
   310  	} {
   311  		t.Run(p.name, func(t *testing.T) {
   312  			c := makeBasicBuilder().SetValue(p.name, p.value).Build()
   313  			assert.Equal(t, p.value, c.attributes.Get(p.name))
   314  			assert.Equal(t, ldvalue.OptionalString{}, c.secondary)
   315  			assert.Len(t, c.privateAttrs, 0)
   316  		})
   317  	}
   318  
   319  	t.Run("_meta", func(t *testing.T) {
   320  		b := makeBasicBuilder()
   321  		assert.False(t, b.TrySetValue("_meta", ldvalue.String("hi")))
   322  		assert.Equal(t, 0, b.Build().attributes.Count())
   323  	})
   324  }
   325  
   326  func TestBuilderAttributesCopyOnWrite(t *testing.T) {
   327  	value1, value2 := ldvalue.String("value1"), ldvalue.String("value2")
   328  
   329  	b := makeBasicBuilder().SetValue("attr", value1)
   330  
   331  	c1 := b.Build()
   332  	jsonhelpers.AssertEqual(t, value1, c1.attributes.Get("attr"))
   333  
   334  	b.SetValue("attr", value2)
   335  
   336  	c2 := b.Build()
   337  	jsonhelpers.AssertEqual(t, value2, c2.attributes.Get("attr"))
   338  	jsonhelpers.AssertEqual(t, value1, c1.attributes.Get("attr")) // unchanged
   339  }
   340  
   341  func TestBuilderPrivate(t *testing.T) {
   342  	expectPrivateRefsToBe := func(t *testing.T, c Context, expectedRefs ...ldattr.Ref) {
   343  		if assert.Equal(t, len(expectedRefs), c.PrivateAttributeCount()) {
   344  			for i, expectedRef := range expectedRefs {
   345  				a, ok := c.PrivateAttributeByIndex(i)
   346  				assert.True(t, ok)
   347  				assert.Equal(t, expectedRef, a)
   348  			}
   349  			_, ok := c.PrivateAttributeByIndex(len(expectedRefs))
   350  			assert.False(t, ok)
   351  		}
   352  		_, ok := c.PrivateAttributeByIndex(-1)
   353  		assert.False(t, ok)
   354  	}
   355  
   356  	t.Run("using Refs", func(t *testing.T) {
   357  		attrRef1, attrRef2, attrRef3 := ldattr.NewRef("a"), ldattr.NewRef("/b/c"), ldattr.NewRef("d")
   358  		c := makeBasicBuilder().
   359  			PrivateRef(attrRef1, attrRef2).PrivateRef(attrRef3).
   360  			Build()
   361  
   362  		expectPrivateRefsToBe(t, c, attrRef1, attrRef2, attrRef3)
   363  	})
   364  
   365  	t.Run("using strings", func(t *testing.T) {
   366  		s1, s2, s3 := "a", "/b/c", "d"
   367  		b0 := makeBasicBuilder().
   368  			PrivateRef(ldattr.NewRef(s1), ldattr.NewRef(s2)).PrivateRef(ldattr.NewRef(s3))
   369  		b1 := makeBasicBuilder().
   370  			Private(s1, s2, s3)
   371  		assert.Equal(t, b0, b1)
   372  	})
   373  
   374  	t.Run("RemovePrivate", func(t *testing.T) {
   375  		b := makeBasicBuilder().Private("a", "/b/c", "d", "/b/c")
   376  		b.RemovePrivate("/b/c")
   377  		c := b.Build()
   378  
   379  		expectPrivateRefsToBe(t, c, ldattr.NewRef("a"), ldattr.NewRef("d"))
   380  	})
   381  
   382  	t.Run("RemovePrivateRef", func(t *testing.T) {
   383  		b := makeBasicBuilder().Private("a", "/b/c", "d", "/b/c")
   384  		b.RemovePrivateRef(ldattr.NewRef("/b/c"))
   385  		c := b.Build()
   386  
   387  		expectPrivateRefsToBe(t, c, ldattr.NewRef("a"), ldattr.NewRef("d"))
   388  	})
   389  
   390  	t.Run("copy on write", func(t *testing.T) {
   391  		b0 := makeBasicBuilder().Private("a")
   392  
   393  		c0 := b0.Build()
   394  		expectPrivateRefsToBe(t, c0, ldattr.NewRef("a"))
   395  
   396  		b0.Private("b")
   397  		c1 := b0.Build()
   398  		expectPrivateRefsToBe(t, c1, ldattr.NewRef("a"), ldattr.NewRef("b"))
   399  		expectPrivateRefsToBe(t, c0, ldattr.NewRef("a")) // unchanged
   400  
   401  		b0.RemovePrivateRef(ldattr.NewRef("a"))
   402  		c2 := b0.Build()
   403  		expectPrivateRefsToBe(t, c2, ldattr.NewRef("b"))
   404  		expectPrivateRefsToBe(t, c1, ldattr.NewRef("a"), ldattr.NewRef("b")) // unchanged
   405  		expectPrivateRefsToBe(t, c0, ldattr.NewRef("a"))                     // unchanged
   406  	})
   407  }
   408  
   409  func TestNewBuilderFromContext(t *testing.T) {
   410  	value1, value2 := ldvalue.String("value1"), ldvalue.String("value2")
   411  
   412  	b1 := NewBuilder("key1").Kind("kind1").Name("name1").Anonymous(true).SetValue("attr", value1)
   413  	b1.Private("private1")
   414  	c1 := b1.Build()
   415  	jsonhelpers.AssertEqual(t, value1, c1.attributes.Get("attr"))
   416  	assert.Len(t, c1.privateAttrs, 1)
   417  
   418  	b2 := NewBuilderFromContext(c1)
   419  	c2 := b2.Build()
   420  	assert.Equal(t, Kind("kind1"), c2.Kind())
   421  	assert.Equal(t, "key1", c2.Key())
   422  	assert.True(t, c2.Anonymous())
   423  	jsonhelpers.AssertEqual(t, value1, c2.attributes.Get("attr"))
   424  	assert.Equal(t, c1.privateAttrs, c2.privateAttrs)
   425  
   426  	b3 := NewBuilderFromContext(c1)
   427  	b3.SetValue("attr", value2)
   428  	b3.Private("private2")
   429  	c3 := b3.Build()
   430  	jsonhelpers.AssertEqual(t, value2, c3.attributes.Get("attr"))
   431  	jsonhelpers.AssertEqual(t, value1, c1.attributes.Get("attr")) // unchanged
   432  	assert.Len(t, c3.privateAttrs, 2)
   433  	assert.Len(t, c1.privateAttrs, 1) // unchanged
   434  
   435  	multi := NewMulti(NewWithKind("kind1", "key1"), NewWithKind("kind2", "key2"))
   436  	assert.NoError(t, multi.Err())
   437  	c4 := NewBuilderFromContext(multi).Build()
   438  	assert.Error(t, c4.Err()) // can't copy Builder from multi-context
   439  }
   440  
   441  func TestBuilderSafety(t *testing.T) {
   442  	// empty instance is safe to use
   443  	var emptyInstance Builder
   444  	emptyInstance.Key("a")
   445  	assert.Equal(t, New("a"), emptyInstance.Build())
   446  
   447  	// nil pointer is safe to use
   448  	var nilPtr *Builder
   449  	assert.Nil(t, nilPtr.Key("a"))
   450  	assert.Nil(t, nilPtr.Name("a"))
   451  	assert.Nil(t, nilPtr.Anonymous(true))
   452  	assert.Nil(t, nilPtr.SetValue("a", ldvalue.Bool(true)))
   453  	assert.Nil(t, nilPtr.Private("a"))
   454  	assert.Nil(t, nilPtr.RemovePrivate("a"))
   455  	assert.Equal(t, Context{}, nilPtr.Build())
   456  }
   457  

View as plain text