launchdarkly-server-sdk 6.2.1 → 6.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/ldclient-rb/config.rb +3 -3
- data/lib/ldclient-rb/impl/diagnostic_events.rb +1 -1
- data/lib/ldclient-rb/integrations/dynamodb.rb +1 -1
- data/lib/ldclient-rb/integrations/redis.rb +1 -1
- data/lib/ldclient-rb/interfaces.rb +1 -1
- data/lib/ldclient-rb/ldclient.rb +5 -5
- data/lib/ldclient-rb/requestor.rb +1 -1
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +24 -132
- data/.circleci/config.yml +0 -40
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
- data/.github/ISSUE_TEMPLATE/config.yml +0 -5
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- data/.github/pull_request_template.md +0 -21
- data/.gitignore +0 -16
- data/.hound.yml +0 -2
- data/.ldrelease/build-docs.sh +0 -18
- data/.ldrelease/circleci/linux/execute.sh +0 -18
- data/.ldrelease/circleci/mac/execute.sh +0 -18
- data/.ldrelease/circleci/template/build.sh +0 -29
- data/.ldrelease/circleci/template/publish.sh +0 -23
- data/.ldrelease/circleci/template/set-gem-home.sh +0 -7
- data/.ldrelease/circleci/template/test.sh +0 -10
- data/.ldrelease/circleci/template/update-version.sh +0 -8
- data/.ldrelease/circleci/windows/execute.ps1 +0 -19
- data/.ldrelease/config.yml +0 -29
- data/.rspec +0 -2
- data/.rubocop.yml +0 -600
- data/.simplecov +0 -4
- data/CHANGELOG.md +0 -359
- data/CODEOWNERS +0 -1
- data/CONTRIBUTING.md +0 -37
- data/Gemfile +0 -3
- data/azure-pipelines.yml +0 -51
- data/docs/Makefile +0 -26
- data/docs/index.md +0 -9
- data/launchdarkly-server-sdk.gemspec +0 -45
- data/spec/config_spec.rb +0 -63
- data/spec/diagnostic_events_spec.rb +0 -163
- data/spec/evaluation_detail_spec.rb +0 -135
- data/spec/event_sender_spec.rb +0 -197
- data/spec/event_summarizer_spec.rb +0 -63
- data/spec/events_spec.rb +0 -607
- data/spec/expiring_cache_spec.rb +0 -76
- data/spec/feature_store_spec_base.rb +0 -213
- data/spec/file_data_source_spec.rb +0 -283
- data/spec/fixtures/feature.json +0 -37
- data/spec/fixtures/feature1.json +0 -36
- data/spec/fixtures/user.json +0 -9
- data/spec/flags_state_spec.rb +0 -81
- data/spec/http_util.rb +0 -132
- data/spec/impl/evaluator_bucketing_spec.rb +0 -216
- data/spec/impl/evaluator_clause_spec.rb +0 -55
- data/spec/impl/evaluator_operators_spec.rb +0 -141
- data/spec/impl/evaluator_rule_spec.rb +0 -128
- data/spec/impl/evaluator_segment_spec.rb +0 -125
- data/spec/impl/evaluator_spec.rb +0 -349
- data/spec/impl/evaluator_spec_base.rb +0 -75
- data/spec/impl/event_factory_spec.rb +0 -108
- data/spec/impl/model/serialization_spec.rb +0 -41
- data/spec/in_memory_feature_store_spec.rb +0 -12
- data/spec/integrations/consul_feature_store_spec.rb +0 -40
- data/spec/integrations/dynamodb_feature_store_spec.rb +0 -103
- data/spec/integrations/store_wrapper_spec.rb +0 -276
- data/spec/launchdarkly-server-sdk_spec.rb +0 -13
- data/spec/launchdarkly-server-sdk_spec_autoloadtest.rb +0 -9
- data/spec/ldclient_end_to_end_spec.rb +0 -157
- data/spec/ldclient_spec.rb +0 -635
- data/spec/newrelic_spec.rb +0 -5
- data/spec/polling_spec.rb +0 -120
- data/spec/redis_feature_store_spec.rb +0 -121
- data/spec/requestor_spec.rb +0 -196
- data/spec/segment_store_spec_base.rb +0 -95
- data/spec/simple_lru_cache_spec.rb +0 -24
- data/spec/spec_helper.rb +0 -9
- data/spec/store_spec.rb +0 -10
- data/spec/stream_spec.rb +0 -45
- data/spec/user_filter_spec.rb +0 -91
- data/spec/util_spec.rb +0 -17
- data/spec/version_spec.rb +0 -7
data/spec/ldclient_spec.rb
DELETED
@@ -1,635 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
|
4
|
-
describe LaunchDarkly::LDClient do
|
5
|
-
subject { LaunchDarkly::LDClient }
|
6
|
-
let(:offline_config) { LaunchDarkly::Config.new({offline: true}) }
|
7
|
-
let(:offline_client) do
|
8
|
-
subject.new("secret", offline_config)
|
9
|
-
end
|
10
|
-
let(:null_data) { LaunchDarkly::NullUpdateProcessor.new }
|
11
|
-
let(:logger) { double().as_null_object }
|
12
|
-
let(:config) { LaunchDarkly::Config.new({ send_events: false, data_source: null_data, logger: logger }) }
|
13
|
-
let(:client) do
|
14
|
-
subject.new("secret", config)
|
15
|
-
end
|
16
|
-
let(:feature) do
|
17
|
-
data = File.read(File.join("spec", "fixtures", "feature.json"))
|
18
|
-
JSON.parse(data, symbolize_names: true)
|
19
|
-
end
|
20
|
-
let(:user) do
|
21
|
-
{
|
22
|
-
key: "user@test.com",
|
23
|
-
custom: {
|
24
|
-
groups: [ "microsoft", "google" ]
|
25
|
-
}
|
26
|
-
}
|
27
|
-
end
|
28
|
-
let(:user_anonymous) do
|
29
|
-
{
|
30
|
-
key: "anonymous@test.com",
|
31
|
-
anonymous: true
|
32
|
-
}
|
33
|
-
end
|
34
|
-
let(:numeric_key_user) do
|
35
|
-
{
|
36
|
-
key: 33,
|
37
|
-
custom: {
|
38
|
-
groups: [ "microsoft", "google" ]
|
39
|
-
}
|
40
|
-
}
|
41
|
-
end
|
42
|
-
let(:sanitized_numeric_key_user) do
|
43
|
-
{
|
44
|
-
key: "33",
|
45
|
-
custom: {
|
46
|
-
groups: [ "microsoft", "google" ]
|
47
|
-
}
|
48
|
-
}
|
49
|
-
end
|
50
|
-
let(:user_without_key) do
|
51
|
-
{ name: "Keyless Joe" }
|
52
|
-
end
|
53
|
-
|
54
|
-
def event_processor
|
55
|
-
client.instance_variable_get(:@event_processor)
|
56
|
-
end
|
57
|
-
|
58
|
-
describe "constructor requirement of non-nil sdk key" do
|
59
|
-
it "is not enforced when offline" do
|
60
|
-
subject.new(nil, offline_config)
|
61
|
-
end
|
62
|
-
|
63
|
-
it "is not enforced if use_ldd is true and send_events is false" do
|
64
|
-
subject.new(nil, LaunchDarkly::Config.new({ use_ldd: true, send_events: false }))
|
65
|
-
end
|
66
|
-
|
67
|
-
it "is not enforced if using file data and send_events is false" do
|
68
|
-
source = LaunchDarkly::FileDataSource.factory({})
|
69
|
-
subject.new(nil, LaunchDarkly::Config.new({ data_source: source, send_events: false }))
|
70
|
-
end
|
71
|
-
|
72
|
-
it "is enforced in streaming mode even if send_events is false" do
|
73
|
-
expect {
|
74
|
-
subject.new(nil, LaunchDarkly::Config.new({ send_events: false }))
|
75
|
-
}.to raise_error(ArgumentError)
|
76
|
-
end
|
77
|
-
|
78
|
-
it "is enforced in polling mode even if send_events is false" do
|
79
|
-
expect {
|
80
|
-
subject.new(nil, LaunchDarkly::Config.new({ stream: false, send_events: false }))
|
81
|
-
}.to raise_error(ArgumentError)
|
82
|
-
end
|
83
|
-
|
84
|
-
it "is enforced if use_ldd is true and send_events is true" do
|
85
|
-
expect {
|
86
|
-
subject.new(nil, LaunchDarkly::Config.new({ use_ldd: true }))
|
87
|
-
}.to raise_error(ArgumentError)
|
88
|
-
end
|
89
|
-
|
90
|
-
it "is enforced if using file data and send_events is true" do
|
91
|
-
source = LaunchDarkly::FileDataSource.factory({})
|
92
|
-
expect {
|
93
|
-
subject.new(nil, LaunchDarkly::Config.new({ data_source: source }))
|
94
|
-
}.to raise_error(ArgumentError)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
describe '#variation' do
|
99
|
-
feature_with_value = { key: "key", on: false, offVariation: 0, variations: ["value"], version: 100,
|
100
|
-
trackEvents: true, debugEventsUntilDate: 1000 }
|
101
|
-
|
102
|
-
it "returns the default value if the client is offline" do
|
103
|
-
result = offline_client.variation("doesntmatter", user, "default")
|
104
|
-
expect(result).to eq "default"
|
105
|
-
end
|
106
|
-
|
107
|
-
it "returns the default value for an unknown feature" do
|
108
|
-
expect(client.variation("badkey", user, "default")).to eq "default"
|
109
|
-
end
|
110
|
-
|
111
|
-
it "queues a feature request event for an unknown feature" do
|
112
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
113
|
-
kind: "feature", key: "badkey", user: user, value: "default", default: "default"
|
114
|
-
))
|
115
|
-
client.variation("badkey", user, "default")
|
116
|
-
end
|
117
|
-
|
118
|
-
it "returns the value for an existing feature" do
|
119
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
120
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
121
|
-
expect(client.variation("key", user, "default")).to eq "value"
|
122
|
-
end
|
123
|
-
|
124
|
-
it "returns the default value if a feature evaluates to nil" do
|
125
|
-
empty_feature = { key: "key", on: false, offVariation: nil }
|
126
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
127
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, empty_feature)
|
128
|
-
expect(client.variation("key", user, "default")).to eq "default"
|
129
|
-
end
|
130
|
-
|
131
|
-
it "queues a feature request event for an existing feature" do
|
132
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
133
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
134
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
135
|
-
kind: "feature",
|
136
|
-
key: "key",
|
137
|
-
version: 100,
|
138
|
-
user: user,
|
139
|
-
variation: 0,
|
140
|
-
value: "value",
|
141
|
-
default: "default",
|
142
|
-
trackEvents: true,
|
143
|
-
debugEventsUntilDate: 1000
|
144
|
-
))
|
145
|
-
client.variation("key", user, "default")
|
146
|
-
end
|
147
|
-
|
148
|
-
it "does not send an event if user is nil" do
|
149
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
150
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
151
|
-
expect(event_processor).not_to receive(:add_event)
|
152
|
-
expect(logger).to receive(:error)
|
153
|
-
client.variation("key", nil, "default")
|
154
|
-
end
|
155
|
-
|
156
|
-
it "queues a feature event for an existing feature when user is anonymous" do
|
157
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
158
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
159
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
160
|
-
kind: "feature",
|
161
|
-
key: "key",
|
162
|
-
version: 100,
|
163
|
-
contextKind: "anonymousUser",
|
164
|
-
user: user_anonymous,
|
165
|
-
variation: 0,
|
166
|
-
value: "value",
|
167
|
-
default: "default",
|
168
|
-
trackEvents: true,
|
169
|
-
debugEventsUntilDate: 1000
|
170
|
-
))
|
171
|
-
client.variation("key", user_anonymous, "default")
|
172
|
-
end
|
173
|
-
|
174
|
-
it "does not queue a feature event for an existing feature when user key is nil" do
|
175
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
176
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
177
|
-
bad_user = { name: "Bob" }
|
178
|
-
expect(event_processor).not_to receive(:add_event)
|
179
|
-
expect(logger).to receive(:warn)
|
180
|
-
client.variation("key", bad_user, "default")
|
181
|
-
end
|
182
|
-
|
183
|
-
it "sets trackEvents and reason if trackEvents is set for matched rule" do
|
184
|
-
flag = {
|
185
|
-
key: 'flag',
|
186
|
-
on: true,
|
187
|
-
variations: [ 'value' ],
|
188
|
-
version: 100,
|
189
|
-
rules: [
|
190
|
-
clauses: [
|
191
|
-
{ attribute: 'key', op: 'in', values: [ user[:key] ] }
|
192
|
-
],
|
193
|
-
variation: 0,
|
194
|
-
id: 'id',
|
195
|
-
trackEvents: true
|
196
|
-
]
|
197
|
-
}
|
198
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
199
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, flag)
|
200
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
201
|
-
kind: 'feature',
|
202
|
-
key: 'flag',
|
203
|
-
version: 100,
|
204
|
-
user: user,
|
205
|
-
value: 'value',
|
206
|
-
default: 'default',
|
207
|
-
trackEvents: true,
|
208
|
-
reason: LaunchDarkly::EvaluationReason::rule_match(0, 'id')
|
209
|
-
))
|
210
|
-
client.variation('flag', user, 'default')
|
211
|
-
end
|
212
|
-
|
213
|
-
it "sets trackEvents and reason if trackEventsFallthrough is set and we fell through" do
|
214
|
-
flag = {
|
215
|
-
key: 'flag',
|
216
|
-
on: true,
|
217
|
-
variations: [ 'value' ],
|
218
|
-
fallthrough: { variation: 0 },
|
219
|
-
version: 100,
|
220
|
-
rules: [],
|
221
|
-
trackEventsFallthrough: true
|
222
|
-
}
|
223
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
224
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, flag)
|
225
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
226
|
-
kind: 'feature',
|
227
|
-
key: 'flag',
|
228
|
-
version: 100,
|
229
|
-
user: user,
|
230
|
-
value: 'value',
|
231
|
-
default: 'default',
|
232
|
-
trackEvents: true,
|
233
|
-
reason: LaunchDarkly::EvaluationReason::fallthrough
|
234
|
-
))
|
235
|
-
client.variation('flag', user, 'default')
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
describe '#variation_detail' do
|
240
|
-
feature_with_value = { key: "key", on: false, offVariation: 0, variations: ["value"], version: 100,
|
241
|
-
trackEvents: true, debugEventsUntilDate: 1000 }
|
242
|
-
|
243
|
-
it "returns the default value if the client is offline" do
|
244
|
-
result = offline_client.variation_detail("doesntmatter", user, "default")
|
245
|
-
expected = LaunchDarkly::EvaluationDetail.new("default", nil,
|
246
|
-
LaunchDarkly::EvaluationReason::error(LaunchDarkly::EvaluationReason::ERROR_CLIENT_NOT_READY))
|
247
|
-
expect(result).to eq expected
|
248
|
-
end
|
249
|
-
|
250
|
-
it "returns the default value for an unknown feature" do
|
251
|
-
result = client.variation_detail("badkey", user, "default")
|
252
|
-
expected = LaunchDarkly::EvaluationDetail.new("default", nil,
|
253
|
-
LaunchDarkly::EvaluationReason::error(LaunchDarkly::EvaluationReason::ERROR_FLAG_NOT_FOUND))
|
254
|
-
expect(result).to eq expected
|
255
|
-
end
|
256
|
-
|
257
|
-
it "queues a feature request event for an unknown feature" do
|
258
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
259
|
-
kind: "feature", key: "badkey", user: user, value: "default", default: "default",
|
260
|
-
reason: LaunchDarkly::EvaluationReason::error(LaunchDarkly::EvaluationReason::ERROR_FLAG_NOT_FOUND)
|
261
|
-
))
|
262
|
-
client.variation_detail("badkey", user, "default")
|
263
|
-
end
|
264
|
-
|
265
|
-
it "returns a value for an existing feature" do
|
266
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
267
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
268
|
-
result = client.variation_detail("key", user, "default")
|
269
|
-
expected = LaunchDarkly::EvaluationDetail.new("value", 0, LaunchDarkly::EvaluationReason::off)
|
270
|
-
expect(result).to eq expected
|
271
|
-
end
|
272
|
-
|
273
|
-
it "returns the default value if a feature evaluates to nil" do
|
274
|
-
empty_feature = { key: "key", on: false, offVariation: nil }
|
275
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
276
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, empty_feature)
|
277
|
-
result = client.variation_detail("key", user, "default")
|
278
|
-
expected = LaunchDarkly::EvaluationDetail.new("default", nil, LaunchDarkly::EvaluationReason::off)
|
279
|
-
expect(result).to eq expected
|
280
|
-
expect(result.default_value?).to be true
|
281
|
-
end
|
282
|
-
|
283
|
-
it "queues a feature request event for an existing feature" do
|
284
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
285
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
286
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
287
|
-
kind: "feature",
|
288
|
-
key: "key",
|
289
|
-
version: 100,
|
290
|
-
user: user,
|
291
|
-
variation: 0,
|
292
|
-
value: "value",
|
293
|
-
default: "default",
|
294
|
-
trackEvents: true,
|
295
|
-
debugEventsUntilDate: 1000,
|
296
|
-
reason: LaunchDarkly::EvaluationReason::off
|
297
|
-
))
|
298
|
-
client.variation_detail("key", user, "default")
|
299
|
-
end
|
300
|
-
|
301
|
-
it "does not send an event if user is nil" do
|
302
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {} })
|
303
|
-
config.feature_store.upsert(LaunchDarkly::FEATURES, feature_with_value)
|
304
|
-
expect(event_processor).not_to receive(:add_event)
|
305
|
-
expect(logger).to receive(:error)
|
306
|
-
client.variation_detail("key", nil, "default")
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
describe '#all_flags' do
|
311
|
-
let(:flag1) { { key: "key1", offVariation: 0, variations: [ 'value1' ] } }
|
312
|
-
let(:flag2) { { key: "key2", offVariation: 0, variations: [ 'value2' ] } }
|
313
|
-
|
314
|
-
it "returns flag values" do
|
315
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
316
|
-
|
317
|
-
result = client.all_flags({ key: 'userkey' })
|
318
|
-
expect(result).to eq({ 'key1' => 'value1', 'key2' => 'value2' })
|
319
|
-
end
|
320
|
-
|
321
|
-
it "returns empty map for nil user" do
|
322
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
323
|
-
|
324
|
-
result = client.all_flags(nil)
|
325
|
-
expect(result).to eq({})
|
326
|
-
end
|
327
|
-
|
328
|
-
it "returns empty map for nil user key" do
|
329
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
330
|
-
|
331
|
-
result = client.all_flags({})
|
332
|
-
expect(result).to eq({})
|
333
|
-
end
|
334
|
-
|
335
|
-
it "returns empty map if offline" do
|
336
|
-
offline_config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
337
|
-
|
338
|
-
result = offline_client.all_flags(nil)
|
339
|
-
expect(result).to eq({})
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
describe '#all_flags_state' do
|
344
|
-
let(:flag1) { { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false } }
|
345
|
-
let(:flag2) { { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true, debugEventsUntilDate: 1000 } }
|
346
|
-
|
347
|
-
it "returns flags state" do
|
348
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
349
|
-
|
350
|
-
state = client.all_flags_state({ key: 'userkey' })
|
351
|
-
expect(state.valid?).to be true
|
352
|
-
|
353
|
-
values = state.values_map
|
354
|
-
expect(values).to eq({ 'key1' => 'value1', 'key2' => 'value2' })
|
355
|
-
|
356
|
-
result = state.as_json
|
357
|
-
expect(result).to eq({
|
358
|
-
'key1' => 'value1',
|
359
|
-
'key2' => 'value2',
|
360
|
-
'$flagsState' => {
|
361
|
-
'key1' => {
|
362
|
-
:variation => 0,
|
363
|
-
:version => 100
|
364
|
-
},
|
365
|
-
'key2' => {
|
366
|
-
:variation => 1,
|
367
|
-
:version => 200,
|
368
|
-
:trackEvents => true,
|
369
|
-
:debugEventsUntilDate => 1000
|
370
|
-
}
|
371
|
-
},
|
372
|
-
'$valid' => true
|
373
|
-
})
|
374
|
-
end
|
375
|
-
|
376
|
-
it "can be filtered for only client-side flags" do
|
377
|
-
flag1 = { key: "server-side-1", offVariation: 0, variations: [ 'a' ], clientSide: false }
|
378
|
-
flag2 = { key: "server-side-2", offVariation: 0, variations: [ 'b' ], clientSide: false }
|
379
|
-
flag3 = { key: "client-side-1", offVariation: 0, variations: [ 'value1' ], clientSide: true }
|
380
|
-
flag4 = { key: "client-side-2", offVariation: 0, variations: [ 'value2' ], clientSide: true }
|
381
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => {
|
382
|
-
flag1[:key] => flag1, flag2[:key] => flag2, flag3[:key] => flag3, flag4[:key] => flag4
|
383
|
-
}})
|
384
|
-
|
385
|
-
state = client.all_flags_state({ key: 'userkey' }, client_side_only: true)
|
386
|
-
expect(state.valid?).to be true
|
387
|
-
|
388
|
-
values = state.values_map
|
389
|
-
expect(values).to eq({ 'client-side-1' => 'value1', 'client-side-2' => 'value2' })
|
390
|
-
end
|
391
|
-
|
392
|
-
it "can omit details for untracked flags" do
|
393
|
-
future_time = (Time.now.to_f * 1000).to_i + 100000
|
394
|
-
flag1 = { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false }
|
395
|
-
flag2 = { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true }
|
396
|
-
flag3 = { key: "key3", version: 300, offVariation: 1, variations: [ 'x', 'value3' ], debugEventsUntilDate: future_time }
|
397
|
-
|
398
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2, 'key3' => flag3 } })
|
399
|
-
|
400
|
-
state = client.all_flags_state({ key: 'userkey' }, { details_only_for_tracked_flags: true })
|
401
|
-
expect(state.valid?).to be true
|
402
|
-
|
403
|
-
values = state.values_map
|
404
|
-
expect(values).to eq({ 'key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3' })
|
405
|
-
|
406
|
-
result = state.as_json
|
407
|
-
expect(result).to eq({
|
408
|
-
'key1' => 'value1',
|
409
|
-
'key2' => 'value2',
|
410
|
-
'key3' => 'value3',
|
411
|
-
'$flagsState' => {
|
412
|
-
'key1' => {
|
413
|
-
:variation => 0
|
414
|
-
},
|
415
|
-
'key2' => {
|
416
|
-
:variation => 1,
|
417
|
-
:version => 200,
|
418
|
-
:trackEvents => true
|
419
|
-
},
|
420
|
-
'key3' => {
|
421
|
-
:variation => 1,
|
422
|
-
:version => 300,
|
423
|
-
:debugEventsUntilDate => future_time
|
424
|
-
}
|
425
|
-
},
|
426
|
-
'$valid' => true
|
427
|
-
})
|
428
|
-
end
|
429
|
-
|
430
|
-
it "returns empty state for nil user" do
|
431
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
432
|
-
|
433
|
-
state = client.all_flags_state(nil)
|
434
|
-
expect(state.valid?).to be false
|
435
|
-
expect(state.values_map).to eq({})
|
436
|
-
end
|
437
|
-
|
438
|
-
it "returns empty state for nil user key" do
|
439
|
-
config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
440
|
-
|
441
|
-
state = client.all_flags_state({})
|
442
|
-
expect(state.valid?).to be false
|
443
|
-
expect(state.values_map).to eq({})
|
444
|
-
end
|
445
|
-
|
446
|
-
it "returns empty state if offline" do
|
447
|
-
offline_config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
|
448
|
-
|
449
|
-
state = offline_client.all_flags_state({ key: 'userkey' })
|
450
|
-
expect(state.valid?).to be false
|
451
|
-
expect(state.values_map).to eq({})
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
describe '#secure_mode_hash' do
|
456
|
-
it "will return the expected value for a known message and secret" do
|
457
|
-
result = client.secure_mode_hash({key: :Message})
|
458
|
-
expect(result).to eq "aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"
|
459
|
-
end
|
460
|
-
end
|
461
|
-
|
462
|
-
describe '#track' do
|
463
|
-
it "queues up an custom event" do
|
464
|
-
expect(event_processor).to receive(:add_event).with(hash_including(kind: "custom", key: "custom_event_name", user: user, data: 42))
|
465
|
-
client.track("custom_event_name", user, 42)
|
466
|
-
end
|
467
|
-
|
468
|
-
it "can include a metric value" do
|
469
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
470
|
-
kind: "custom", key: "custom_event_name", user: user, metricValue: 1.5))
|
471
|
-
client.track("custom_event_name", user, nil, 1.5)
|
472
|
-
end
|
473
|
-
|
474
|
-
it "includes contextKind with anonymous user" do
|
475
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
476
|
-
kind: "custom", key: "custom_event_name", user: user_anonymous, metricValue: 2.2, contextKind: "anonymousUser"))
|
477
|
-
client.track("custom_event_name", user_anonymous, nil, 2.2)
|
478
|
-
end
|
479
|
-
|
480
|
-
it "sanitizes the user in the event" do
|
481
|
-
expect(event_processor).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
|
482
|
-
client.track("custom_event_name", numeric_key_user, nil)
|
483
|
-
end
|
484
|
-
|
485
|
-
it "does not send an event, and logs a warning, if user is nil" do
|
486
|
-
expect(event_processor).not_to receive(:add_event)
|
487
|
-
expect(logger).to receive(:warn)
|
488
|
-
client.track("custom_event_name", nil, nil)
|
489
|
-
end
|
490
|
-
|
491
|
-
it "does not send an event, and logs a warning, if user key is nil" do
|
492
|
-
expect(event_processor).not_to receive(:add_event)
|
493
|
-
expect(logger).to receive(:warn)
|
494
|
-
client.track("custom_event_name", user_without_key, nil)
|
495
|
-
end
|
496
|
-
end
|
497
|
-
|
498
|
-
describe '#alias' do
|
499
|
-
it "queues up an alias event" do
|
500
|
-
expect(event_processor).to receive(:add_event).with(hash_including(
|
501
|
-
kind: "alias", key: user[:key], contextKind: "user", previousKey: user_anonymous[:key], previousContextKind: "anonymousUser"))
|
502
|
-
client.alias(user, user_anonymous)
|
503
|
-
end
|
504
|
-
|
505
|
-
it "does not send an event, and logs a warning, if user is nil" do
|
506
|
-
expect(event_processor).not_to receive(:add_event)
|
507
|
-
expect(logger).to receive(:warn)
|
508
|
-
client.alias(nil, nil)
|
509
|
-
end
|
510
|
-
|
511
|
-
it "does not send an event, and logs a warning, if user key is nil" do
|
512
|
-
expect(event_processor).not_to receive(:add_event)
|
513
|
-
expect(logger).to receive(:warn)
|
514
|
-
client.alias(user_without_key, user_without_key)
|
515
|
-
end
|
516
|
-
end
|
517
|
-
|
518
|
-
describe '#identify' do
|
519
|
-
it "queues up an identify event" do
|
520
|
-
expect(event_processor).to receive(:add_event).with(hash_including(kind: "identify", key: user[:key], user: user))
|
521
|
-
client.identify(user)
|
522
|
-
end
|
523
|
-
|
524
|
-
it "does not send an event, and logs a warning, if user is nil" do
|
525
|
-
expect(event_processor).not_to receive(:add_event)
|
526
|
-
expect(logger).to receive(:warn)
|
527
|
-
client.identify(nil)
|
528
|
-
end
|
529
|
-
|
530
|
-
it "does not send an event, and logs a warning, if user key is nil" do
|
531
|
-
expect(event_processor).not_to receive(:add_event)
|
532
|
-
expect(logger).to receive(:warn)
|
533
|
-
client.identify(user_without_key)
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
|
-
describe 'with send_events: false' do
|
538
|
-
let(:config) { LaunchDarkly::Config.new({offline: true, send_events: false, data_source: null_data}) }
|
539
|
-
let(:client) { subject.new("secret", config) }
|
540
|
-
|
541
|
-
it "uses a NullEventProcessor" do
|
542
|
-
ep = client.instance_variable_get(:@event_processor)
|
543
|
-
expect(ep).to be_a(LaunchDarkly::NullEventProcessor)
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
describe 'with send_events: true' do
|
548
|
-
let(:config_with_events) { LaunchDarkly::Config.new({offline: false, send_events: true, diagnostic_opt_out: true, data_source: null_data}) }
|
549
|
-
let(:client_with_events) { subject.new("secret", config_with_events) }
|
550
|
-
|
551
|
-
it "does not use a NullEventProcessor" do
|
552
|
-
ep = client_with_events.instance_variable_get(:@event_processor)
|
553
|
-
expect(ep).not_to be_a(LaunchDarkly::NullEventProcessor)
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
describe "feature store data ordering" do
|
558
|
-
let(:dependency_ordering_test_data) {
|
559
|
-
{
|
560
|
-
LaunchDarkly::FEATURES => {
|
561
|
-
a: { key: "a", prerequisites: [ { key: "b" }, { key: "c" } ] },
|
562
|
-
b: { key: "b", prerequisites: [ { key: "c" }, { key: "e" } ] },
|
563
|
-
c: { key: "c" },
|
564
|
-
d: { key: "d" },
|
565
|
-
e: { key: "e" },
|
566
|
-
f: { key: "f" }
|
567
|
-
},
|
568
|
-
LaunchDarkly::SEGMENTS => {
|
569
|
-
o: { key: "o" }
|
570
|
-
}
|
571
|
-
}
|
572
|
-
}
|
573
|
-
|
574
|
-
class FakeFeatureStore
|
575
|
-
attr_reader :received_data
|
576
|
-
|
577
|
-
def init(all_data)
|
578
|
-
@received_data = all_data
|
579
|
-
end
|
580
|
-
end
|
581
|
-
|
582
|
-
class FakeUpdateProcessor
|
583
|
-
def initialize(store, data)
|
584
|
-
@store = store
|
585
|
-
@data = data
|
586
|
-
end
|
587
|
-
|
588
|
-
def start
|
589
|
-
@store.init(@data)
|
590
|
-
ev = Concurrent::Event.new
|
591
|
-
ev.set
|
592
|
-
ev
|
593
|
-
end
|
594
|
-
|
595
|
-
def stop
|
596
|
-
end
|
597
|
-
|
598
|
-
def initialized?
|
599
|
-
true
|
600
|
-
end
|
601
|
-
end
|
602
|
-
|
603
|
-
it "passes data set to feature store in correct order on init" do
|
604
|
-
store = FakeFeatureStore.new
|
605
|
-
data_source_factory = lambda { |sdk_key, config| FakeUpdateProcessor.new(config.feature_store,
|
606
|
-
dependency_ordering_test_data) }
|
607
|
-
config = LaunchDarkly::Config.new(send_events: false, feature_store: store, data_source: data_source_factory)
|
608
|
-
client = subject.new("secret", config)
|
609
|
-
|
610
|
-
data = store.received_data
|
611
|
-
expect(data).not_to be_nil
|
612
|
-
expect(data.count).to eq(2)
|
613
|
-
|
614
|
-
# Segments should always come first
|
615
|
-
expect(data.keys[0]).to be(LaunchDarkly::SEGMENTS)
|
616
|
-
expect(data.values[0].count).to eq(dependency_ordering_test_data[LaunchDarkly::SEGMENTS].count)
|
617
|
-
|
618
|
-
# Features should be ordered so that a flag always appears after its prerequisites, if any
|
619
|
-
expect(data.keys[1]).to be(LaunchDarkly::FEATURES)
|
620
|
-
flags_map = data.values[1]
|
621
|
-
flags_list = flags_map.values
|
622
|
-
expect(flags_list.count).to eq(dependency_ordering_test_data[LaunchDarkly::FEATURES].count)
|
623
|
-
flags_list.each_with_index do |item, item_index|
|
624
|
-
(item[:prerequisites] || []).each do |prereq|
|
625
|
-
prereq = flags_map[prereq[:key].to_sym]
|
626
|
-
prereq_index = flags_list.index(prereq)
|
627
|
-
if prereq_index > item_index
|
628
|
-
all_keys = (flags_list.map { |f| f[:key] }).join(", ")
|
629
|
-
raise "#{item[:key]} depends on #{prereq[:key]}, but #{item[:key]} was listed first; keys in order are [#{all_keys}]"
|
630
|
-
end
|
631
|
-
end
|
632
|
-
end
|
633
|
-
end
|
634
|
-
end
|
635
|
-
end
|