launchdarkly-server-sdk 6.1.1 → 6.4.0

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