package ldtestdata import ( "encoding/json" "testing" "time" "github.com/launchdarkly/go-sdk-common/v3/ldcontext" "github.com/launchdarkly/go-sdk-common/v3/ldvalue" "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders" "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel" "github.com/launchdarkly/go-server-sdk/v6/subsystems" "github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoreimpl" m "github.com/launchdarkly/go-test-helpers/v3/matchers" ) const ( trueVar = 0 falseVar = 1 ) func verifyFlag(t *testing.T, configureFlag func(*FlagBuilder), expectedFlag *ldbuilders.FlagBuilder) { t.Helper() expectedJSON, _ := json.Marshal(expectedFlag.Build()) testDataSourceTest(t, func(p testDataSourceTestParams) { t.Helper() p.withDataSource(t, func(subsystems.DataSource) { t.Helper() f := p.td.Flag("flagkey") configureFlag(f) p.td.Update(f) up := p.updates.DataStore.WaitForUpsert(t, ldstoreimpl.Features(), "flagkey", 1, time.Millisecond) upJSON := ldstoreimpl.Features().Serialize(up.Item) m.In(t).Assert(string(upJSON), m.JSONStrEqual(string(expectedJSON))) }) }) } func basicBool() *ldbuilders.FlagBuilder { return ldbuilders.NewFlagBuilder("flagkey").Version(1).Variations(ldvalue.Bool(true), ldvalue.Bool(false)). OffVariation(1) } func TestFlagConfig(t *testing.T) { basicString := func() *ldbuilders.FlagBuilder { return ldbuilders.NewFlagBuilder("flagkey").Version(1).On(true).Variations(threeStringValues...) } t.Run("simple boolean flag", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) {}, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.BooleanFlag() }, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.On(true) }, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.On(false) }, basicBool().On(false).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.VariationForAll(false) }, basicBool().On(true).FallthroughVariation(falseVar)) verifyFlag(t, func(f *FlagBuilder) { f.VariationForAll(true) }, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.FallthroughVariation(true).OffVariation(false) }, basicBool().On(true).FallthroughVariation(trueVar).OffVariation(falseVar)) verifyFlag(t, func(f *FlagBuilder) { f.FallthroughVariation(false).OffVariation(true) }, basicBool().On(true).FallthroughVariation(falseVar).OffVariation(trueVar)) }) t.Run("using boolean config methods forces flag to be boolean", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.Variations(ldvalue.Int(1), ldvalue.Int(2)) f.BooleanFlag() }, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.Variations(ldvalue.Bool(true), ldvalue.Int(2)) f.BooleanFlag() }, basicBool().On(true).FallthroughVariation(trueVar)) verifyFlag(t, func(f *FlagBuilder) { f.ValueForAll(ldvalue.String("x")) f.BooleanFlag() }, basicBool().On(true).FallthroughVariation(trueVar)) }) t.Run("flag with string variations", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.Variations(threeStringValues...).OffVariationIndex(0).FallthroughVariationIndex(2) }, basicString().OffVariation(0).FallthroughVariation(2)) verifyFlag(t, func(f *FlagBuilder) { f.Variations(threeStringValues...).VariationForAllIndex(1) }, basicString().OffVariation(1).FallthroughVariation(1)) }) } func TestFlagTargets(t *testing.T) { basicString := func() *ldbuilders.FlagBuilder { return ldbuilders.NewFlagBuilder("flagkey").Version(1).On(true).Variations(threeStringValues...) } t.Run("user targets", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.VariationForUser("a", true).VariationForUser("b", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddTarget(0, "a", "b").AddContextTarget(ldcontext.DefaultKind, 0)) verifyFlag(t, func(f *FlagBuilder) { f.VariationForUser("a", true).VariationForUser("a", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddTarget(0, "a").AddContextTarget(ldcontext.DefaultKind, 0)) verifyFlag(t, func(f *FlagBuilder) { f.VariationForUser("a", false).VariationForUser("b", true).VariationForUser("c", false) }, basicBool().On(true).FallthroughVariation(trueVar). AddTarget(0, "b").AddContextTarget(ldcontext.DefaultKind, 0). AddTarget(1, "a", "c").AddContextTarget(ldcontext.DefaultKind, 1)) verifyFlag(t, func(f *FlagBuilder) { f.VariationForUser("a", true).VariationForUser("b", true).VariationForUser("a", false) }, basicBool().On(true).FallthroughVariation(trueVar). AddTarget(0, "b").AddContextTarget(ldcontext.DefaultKind, 0). AddTarget(1, "a").AddContextTarget(ldcontext.DefaultKind, 1)) verifyFlag(t, func(f *FlagBuilder) { f.Variations(threeStringValues...).OffVariationIndex(0).FallthroughVariationIndex(2). VariationIndexForUser("a", 2).VariationIndexForUser("b", 2) }, basicString().On(true).OffVariation(0).FallthroughVariation(2). AddTarget(2, "a", "b").AddContextTarget(ldcontext.DefaultKind, 2)) verifyFlag(t, func(f *FlagBuilder) { f.Variations(threeStringValues...).OffVariationIndex(0).FallthroughVariationIndex(2). VariationIndexForUser("a", 2).VariationIndexForUser("b", 1).VariationIndexForUser("c", 2) }, basicString().On(true).OffVariation(0).FallthroughVariation(2). AddTarget(1, "b").AddContextTarget(ldcontext.DefaultKind, 1). AddTarget(2, "a", "c").AddContextTarget(ldcontext.DefaultKind, 2)) }) t.Run("context targets", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.VariationForKey("org", "a", true).VariationForKey("org", "b", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddContextTarget("org", 0, "a", "b")) verifyFlag(t, func(f *FlagBuilder) { f.VariationForKey("org", "a", true).VariationForKey("other", "a", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddContextTarget("org", 0, "a"). AddContextTarget("other", 0, "a")) verifyFlag(t, func(f *FlagBuilder) { f.VariationForKey("org", "a", true).VariationForKey("org", "a", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddContextTarget("org", 0, "a")) verifyFlag(t, func(f *FlagBuilder) { f.Variations(threeStringValues...).OffVariationIndex(0).FallthroughVariationIndex(2). VariationIndexForKey("org", "a", 2).VariationIndexForKey("org", "b", 2) }, basicString().On(true).OffVariation(0).FallthroughVariation(2). AddContextTarget("org", 2, "a", "b")) verifyFlag(t, func(f *FlagBuilder) { f.VariationForKey("", "a", true).VariationForKey("", "b", true) }, basicBool().On(true).FallthroughVariation(trueVar). AddTarget(0, "a", "b").AddContextTarget("user", 0)) }) } func TestRuleConfig(t *testing.T) { t.Run("simple match returning variation 0/true", func(t *testing.T) { matchReturnsVariation0 := basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Lucy")), ), ) verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")).ThenReturn(true) }, matchReturnsVariation0) verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")).ThenReturnIndex(0) }, matchReturnsVariation0) }) t.Run("simple match returning variation 1/false", func(t *testing.T) { matchReturnsVariation1 := basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(falseVar).Clauses( ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Lucy")), ), ) verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")).ThenReturn(false) }, matchReturnsVariation1) verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")).ThenReturnIndex(1) }, matchReturnsVariation1) }) t.Run("negated match", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.IfNotMatch("name", ldvalue.String("Lucy")).ThenReturn(true) }, basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.Negate(ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Lucy"))), ), )) }) t.Run("multiple clauses", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")). AndMatch("country", ldvalue.String("gb")). ThenReturn(true) }, basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Lucy")), ldbuilders.ClauseWithKind("user", "country", ldmodel.OperatorIn, ldvalue.String("gb")), ), )) }) t.Run("multiple rules", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.IfMatch("name", ldvalue.String("Lucy")).ThenReturn(true). IfMatch("name", ldvalue.String("Mina")).ThenReturn(true) }, basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Lucy")), ), ).AddRule( ldbuilders.NewRuleBuilder().ID("rule1").Variation(trueVar).Clauses( ldbuilders.ClauseWithKind("user", "name", ldmodel.OperatorIn, ldvalue.String("Mina")), ), )) }) t.Run("simple match with context kind", func(t *testing.T) { matchReturnsVariation0 := basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.ClauseWithKind("org", "name", ldmodel.OperatorIn, ldvalue.String("Catco")), ), ) verifyFlag(t, func(f *FlagBuilder) { f.IfMatchContext("org", "name", ldvalue.String("Catco")).ThenReturn(true) }, matchReturnsVariation0) verifyFlag(t, func(f *FlagBuilder) { f.IfMatchContext("org", "name", ldvalue.String("Catco")).ThenReturnIndex(0) }, matchReturnsVariation0) }) t.Run("negated match with context kind", func(t *testing.T) { verifyFlag(t, func(f *FlagBuilder) { f.IfNotMatchContext("org", "name", ldvalue.String("Catco")).ThenReturn(true) }, basicBool().On(true).FallthroughVariation(0).AddRule( ldbuilders.NewRuleBuilder().ID("rule0").Variation(trueVar).Clauses( ldbuilders.Negate(ldbuilders.ClauseWithKind("org", "name", ldmodel.OperatorIn, ldvalue.String("Catco"))), ), )) }) }