1 package ldclient
2
3 import (
4 "errors"
5 "testing"
6
7 "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest/mocks"
8
9 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
10 "github.com/launchdarkly/go-sdk-common/v3/ldlogtest"
11 "github.com/launchdarkly/go-sdk-common/v3/ldreason"
12 "github.com/launchdarkly/go-sdk-common/v3/ldtime"
13 "github.com/launchdarkly/go-sdk-common/v3/lduser"
14 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
15 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
16 "github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate"
17 "github.com/launchdarkly/go-server-sdk/v6/internal/datakinds"
18 "github.com/launchdarkly/go-server-sdk/v6/internal/datastore"
19 "github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
20 "github.com/launchdarkly/go-server-sdk/v6/ldcomponents"
21 "github.com/launchdarkly/go-server-sdk/v6/subsystems"
22
23 "github.com/stretchr/testify/assert"
24 )
25
26 func TestAllFlagsStateGetsState(t *testing.T) {
27 flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).OffVariation(0).
28 Variations(ldvalue.String("value1")).Build()
29 flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).
30 Variations(ldvalue.String("x"), ldvalue.String("value2")).
31 TrackEvents(true).DebugEventsUntilDate(1000).Build()
32
33
34 flag3 := ldbuilders.NewFlagBuilder("key3").Version(300).On(true).FallthroughVariation(1).
35 Variations(ldvalue.String("x"), ldvalue.String("value3")).
36 TrackEvents(false).TrackEventsFallthrough(true).Build()
37
38 withClientEvalTestParams(func(p clientEvalTestParams) {
39 p.data.UsePreconfiguredFlag(flag1)
40 p.data.UsePreconfiguredFlag(flag2)
41 p.data.UsePreconfiguredFlag(flag3)
42
43 state := p.client.AllFlagsState(lduser.NewUser("userkey"))
44 assert.True(t, state.IsValid())
45
46 expected := flagstate.NewAllFlagsBuilder().
47 AddFlag("key1", flagstate.FlagState{
48 Value: ldvalue.String("value1"),
49 Variation: ldvalue.NewOptionalInt(0),
50 Version: 100,
51 }).
52 AddFlag("key2", flagstate.FlagState{
53 Value: ldvalue.String("value2"),
54 Variation: ldvalue.NewOptionalInt(1),
55 Version: 200,
56 TrackEvents: true,
57 DebugEventsUntilDate: ldtime.UnixMillisecondTime(1000),
58 }).
59 AddFlag("key3", flagstate.FlagState{
60 Value: ldvalue.String("value3"),
61 Variation: ldvalue.NewOptionalInt(1),
62 Version: 300,
63 Reason: ldreason.NewEvalReasonFallthrough(),
64 TrackEvents: true,
65 TrackReason: true,
66 }).
67 Build()
68 assert.Equal(t, expected, state)
69 })
70 }
71
72 func TestAllFlagsStateGetsStateWithReasons(t *testing.T) {
73 flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).On(false).OffVariation(0).
74 Variations(ldvalue.String("value1")).Build()
75 flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).
76 Variations(ldvalue.String("x"), ldvalue.String("value2")).
77 TrackEvents(true).DebugEventsUntilDate(1000).Build()
78
79 withClientEvalTestParams(func(p clientEvalTestParams) {
80 p.data.UsePreconfiguredFlag(flag1)
81 p.data.UsePreconfiguredFlag(flag2)
82
83 state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionWithReasons())
84 assert.True(t, state.IsValid())
85
86 expected := flagstate.NewAllFlagsBuilder(flagstate.OptionWithReasons()).
87 AddFlag("key1", flagstate.FlagState{
88 Value: ldvalue.String("value1"),
89 Variation: ldvalue.NewOptionalInt(0),
90 Version: 100,
91 Reason: ldreason.NewEvalReasonOff(),
92 }).
93 AddFlag("key2", flagstate.FlagState{
94 Value: ldvalue.String("value2"),
95 Variation: ldvalue.NewOptionalInt(1),
96 Version: 200,
97 Reason: ldreason.NewEvalReasonOff(),
98 TrackEvents: true,
99 DebugEventsUntilDate: ldtime.UnixMillisecondTime(1000),
100 }).
101 Build()
102 assert.Equal(t, expected, state)
103 })
104 }
105
106 func TestAllFlagsStateCanFilterForOnlyClientSideFlags(t *testing.T) {
107 flag1 := ldbuilders.NewFlagBuilder("server-side-1").Build()
108 flag2 := ldbuilders.NewFlagBuilder("server-side-2").Build()
109 flag3 := ldbuilders.NewFlagBuilder("client-side-1").SingleVariation(ldvalue.String("value1")).
110 ClientSideUsingEnvironmentID(true).Build()
111 flag4 := ldbuilders.NewFlagBuilder("client-side-2").SingleVariation(ldvalue.String("value2")).
112 ClientSideUsingEnvironmentID(true).Build()
113
114 withClientEvalTestParams(func(p clientEvalTestParams) {
115 p.data.UsePreconfiguredFlag(flag1)
116 p.data.UsePreconfiguredFlag(flag2)
117 p.data.UsePreconfiguredFlag(flag3)
118 p.data.UsePreconfiguredFlag(flag4)
119
120 state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionClientSideOnly())
121 assert.True(t, state.IsValid())
122
123 expectedValues := map[string]ldvalue.Value{"client-side-1": ldvalue.String("value1"), "client-side-2": ldvalue.String("value2")}
124 assert.Equal(t, expectedValues, state.ToValuesMap())
125 })
126 }
127
128 func TestAllFlagsStateCanOmitDetailForUntrackedFlags(t *testing.T) {
129 futureTime := ldtime.UnixMillisNow() + 100000
130
131
132 flag1 := ldbuilders.NewFlagBuilder("key1").Version(100).OffVariation(0).Variations(ldvalue.String("value1")).Build()
133
134
135 flag2 := ldbuilders.NewFlagBuilder("key2").Version(200).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value2")).
136 TrackEvents(true).Build()
137
138
139 flag3 := ldbuilders.NewFlagBuilder("key3").Version(300).OffVariation(1).Variations(ldvalue.String("x"), ldvalue.String("value3")).
140 TrackEvents(false).DebugEventsUntilDate(futureTime).Build()
141
142
143 flag4 := ldbuilders.NewFlagBuilder("key4").Version(400).On(true).FallthroughVariation(1).
144 Variations(ldvalue.String("x"), ldvalue.String("value4")).
145 TrackEvents(false).TrackEventsFallthrough(true).Build()
146
147 withClientEvalTestParams(func(p clientEvalTestParams) {
148 p.data.UsePreconfiguredFlag(flag1)
149 p.data.UsePreconfiguredFlag(flag2)
150 p.data.UsePreconfiguredFlag(flag3)
151 p.data.UsePreconfiguredFlag(flag4)
152
153 state := p.client.AllFlagsState(lduser.NewUser("userkey"), flagstate.OptionWithReasons(),
154 flagstate.OptionDetailsOnlyForTrackedFlags())
155 assert.True(t, state.IsValid())
156
157 expected := flagstate.NewAllFlagsBuilder(flagstate.OptionWithReasons()).
158 AddFlag("key1", flagstate.FlagState{
159 Value: ldvalue.String("value1"),
160 Variation: ldvalue.NewOptionalInt(0),
161 Version: 100,
162 Reason: ldreason.NewEvalReasonOff(),
163 OmitDetails: true,
164 }).
165 AddFlag("key2", flagstate.FlagState{
166 Value: ldvalue.String("value2"),
167 Variation: ldvalue.NewOptionalInt(1),
168 Version: 200,
169 Reason: ldreason.NewEvalReasonOff(),
170 TrackEvents: true,
171 }).
172 AddFlag("key3", flagstate.FlagState{
173 Value: ldvalue.String("value3"),
174 Variation: ldvalue.NewOptionalInt(1),
175 Version: 300,
176 Reason: ldreason.NewEvalReasonOff(),
177 DebugEventsUntilDate: futureTime,
178 }).
179 AddFlag("key4", flagstate.FlagState{
180 Value: ldvalue.String("value4"),
181 Variation: ldvalue.NewOptionalInt(1),
182 Version: 400,
183 Reason: ldreason.NewEvalReasonFallthrough(),
184 TrackEvents: true,
185 TrackReason: true,
186 }).
187 Build()
188 assert.Equal(t, expected, state)
189 })
190 }
191
192 func TestAllFlagsStateReturnsInvalidStateIfClientAndStoreAreNotInitialized(t *testing.T) {
193 mockLoggers := ldlogtest.NewMockLog()
194
195 client := makeTestClientWithConfig(func(c *Config) {
196 c.DataSource = mocks.DataSourceThatNeverInitializes()
197 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
198 })
199 defer client.Close()
200
201 state := client.AllFlagsState(evalTestUser)
202 assert.False(t, state.IsValid())
203 assert.Len(t, state.ToValuesMap(), 0)
204 }
205
206 func TestAllFlagsStateUsesStoreAndLogsWarningIfClientIsNotInitializedButStoreIsInitialized(t *testing.T) {
207 mockLoggers := ldlogtest.NewMockLog()
208 flag := ldbuilders.NewFlagBuilder(evalFlagKey).SingleVariation(ldvalue.Bool(true)).Build()
209 store := datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers())
210 _ = store.Init(nil)
211 _, _ = store.Upsert(datakinds.Features, flag.Key, sharedtest.FlagDescriptor(flag))
212
213 client := makeTestClientWithConfig(func(c *Config) {
214 c.DataSource = mocks.DataSourceThatNeverInitializes()
215 c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
216 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
217 })
218 defer client.Close()
219
220 state := client.AllFlagsState(evalTestUser)
221 assert.True(t, state.IsValid())
222 assert.Len(t, state.ToValuesMap(), 1)
223
224 assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
225 assert.Contains(t, mockLoggers.GetOutput(ldlog.Warn)[0], "using last known values")
226 }
227
228 func TestAllFlagsStateReturnsInvalidStateIfStoreReturnsError(t *testing.T) {
229 myError := errors.New("sorry")
230 store := mocks.NewCapturingDataStore(datastore.NewInMemoryDataStore(sharedtest.NewTestLoggers()))
231 _ = store.Init(nil)
232 store.SetFakeError(myError)
233 mockLoggers := ldlogtest.NewMockLog()
234
235 client := makeTestClientWithConfig(func(c *Config) {
236 c.DataSource = mocks.DataSourceThatIsAlwaysInitialized()
237 c.DataStore = mocks.SingleComponentConfigurer[subsystems.DataStore]{Instance: store}
238 c.Logging = ldcomponents.Logging().Loggers(mockLoggers.Loggers)
239 })
240 defer client.Close()
241
242 state := client.AllFlagsState(evalTestUser)
243 assert.False(t, state.IsValid())
244 assert.Len(t, state.ToValuesMap(), 0)
245
246 assert.Len(t, mockLoggers.GetOutput(ldlog.Warn), 1)
247 assert.Contains(t, mockLoggers.GetOutput(ldlog.Warn)[0], "Unable to fetch flags")
248 }
249
View as plain text