launchdarkly-server-sdk 6.2.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/ldclient-rb/config.rb +81 -4
  4. data/lib/ldclient-rb/evaluation_detail.rb +67 -8
  5. data/lib/ldclient-rb/file_data_source.rb +9 -300
  6. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  7. data/lib/ldclient-rb/impl/diagnostic_events.rb +1 -1
  8. data/lib/ldclient-rb/impl/evaluator.rb +80 -28
  9. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
  10. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  11. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
  12. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  13. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  14. data/lib/ldclient-rb/impl/util.rb +4 -1
  15. data/lib/ldclient-rb/integrations/consul.rb +7 -0
  16. data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
  17. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  18. data/lib/ldclient-rb/integrations/redis.rb +41 -1
  19. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
  20. data/lib/ldclient-rb/integrations/test_data.rb +209 -0
  21. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
  22. data/lib/ldclient-rb/integrations.rb +2 -51
  23. data/lib/ldclient-rb/interfaces.rb +152 -2
  24. data/lib/ldclient-rb/ldclient.rb +21 -7
  25. data/lib/ldclient-rb/polling.rb +22 -41
  26. data/lib/ldclient-rb/util.rb +1 -1
  27. data/lib/ldclient-rb/version.rb +1 -1
  28. metadata +31 -132
  29. data/.circleci/config.yml +0 -40
  30. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
  31. data/.github/ISSUE_TEMPLATE/config.yml +0 -5
  32. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  33. data/.github/pull_request_template.md +0 -21
  34. data/.gitignore +0 -16
  35. data/.hound.yml +0 -2
  36. data/.ldrelease/build-docs.sh +0 -18
  37. data/.ldrelease/circleci/linux/execute.sh +0 -18
  38. data/.ldrelease/circleci/mac/execute.sh +0 -18
  39. data/.ldrelease/circleci/template/build.sh +0 -29
  40. data/.ldrelease/circleci/template/publish.sh +0 -23
  41. data/.ldrelease/circleci/template/set-gem-home.sh +0 -7
  42. data/.ldrelease/circleci/template/test.sh +0 -10
  43. data/.ldrelease/circleci/template/update-version.sh +0 -8
  44. data/.ldrelease/circleci/windows/execute.ps1 +0 -19
  45. data/.ldrelease/config.yml +0 -29
  46. data/.rspec +0 -2
  47. data/.rubocop.yml +0 -600
  48. data/.simplecov +0 -4
  49. data/CHANGELOG.md +0 -363
  50. data/CODEOWNERS +0 -1
  51. data/CONTRIBUTING.md +0 -37
  52. data/Gemfile +0 -3
  53. data/azure-pipelines.yml +0 -51
  54. data/docs/Makefile +0 -26
  55. data/docs/index.md +0 -9
  56. data/launchdarkly-server-sdk.gemspec +0 -45
  57. data/spec/config_spec.rb +0 -63
  58. data/spec/diagnostic_events_spec.rb +0 -163
  59. data/spec/evaluation_detail_spec.rb +0 -135
  60. data/spec/event_sender_spec.rb +0 -197
  61. data/spec/event_summarizer_spec.rb +0 -63
  62. data/spec/events_spec.rb +0 -607
  63. data/spec/expiring_cache_spec.rb +0 -76
  64. data/spec/feature_store_spec_base.rb +0 -213
  65. data/spec/file_data_source_spec.rb +0 -283
  66. data/spec/fixtures/feature.json +0 -37
  67. data/spec/fixtures/feature1.json +0 -36
  68. data/spec/fixtures/user.json +0 -9
  69. data/spec/flags_state_spec.rb +0 -81
  70. data/spec/http_util.rb +0 -132
  71. data/spec/impl/evaluator_bucketing_spec.rb +0 -216
  72. data/spec/impl/evaluator_clause_spec.rb +0 -55
  73. data/spec/impl/evaluator_operators_spec.rb +0 -141
  74. data/spec/impl/evaluator_rule_spec.rb +0 -128
  75. data/spec/impl/evaluator_segment_spec.rb +0 -125
  76. data/spec/impl/evaluator_spec.rb +0 -349
  77. data/spec/impl/evaluator_spec_base.rb +0 -75
  78. data/spec/impl/event_factory_spec.rb +0 -108
  79. data/spec/impl/model/serialization_spec.rb +0 -41
  80. data/spec/in_memory_feature_store_spec.rb +0 -12
  81. data/spec/integrations/consul_feature_store_spec.rb +0 -40
  82. data/spec/integrations/dynamodb_feature_store_spec.rb +0 -103
  83. data/spec/integrations/store_wrapper_spec.rb +0 -276
  84. data/spec/launchdarkly-server-sdk_spec.rb +0 -13
  85. data/spec/launchdarkly-server-sdk_spec_autoloadtest.rb +0 -9
  86. data/spec/ldclient_end_to_end_spec.rb +0 -157
  87. data/spec/ldclient_spec.rb +0 -635
  88. data/spec/newrelic_spec.rb +0 -5
  89. data/spec/polling_spec.rb +0 -120
  90. data/spec/redis_feature_store_spec.rb +0 -121
  91. data/spec/requestor_spec.rb +0 -209
  92. data/spec/segment_store_spec_base.rb +0 -95
  93. data/spec/simple_lru_cache_spec.rb +0 -24
  94. data/spec/spec_helper.rb +0 -9
  95. data/spec/store_spec.rb +0 -10
  96. data/spec/stream_spec.rb +0 -45
  97. data/spec/user_filter_spec.rb +0 -91
  98. data/spec/util_spec.rb +0 -17
  99. data/spec/version_spec.rb +0 -7
@@ -1,41 +0,0 @@
1
- require "spec_helper"
2
-
3
- module LaunchDarkly
4
- module Impl
5
- module Model
6
- describe "model serialization" do
7
- it "serializes flag" do
8
- flag = { key: "flagkey", version: 1 }
9
- json = Model.serialize(FEATURES, flag)
10
- expect(JSON.parse(json, symbolize_names: true)).to eq flag
11
- end
12
-
13
- it "serializes segment" do
14
- segment = { key: "segkey", version: 1 }
15
- json = Model.serialize(SEGMENTS, segment)
16
- expect(JSON.parse(json, symbolize_names: true)).to eq segment
17
- end
18
-
19
- it "serializes arbitrary data kind" do
20
- thing = { key: "thingkey", name: "me" }
21
- json = Model.serialize({ name: "things" }, thing)
22
- expect(JSON.parse(json, symbolize_names: true)).to eq thing
23
- end
24
-
25
- it "deserializes flag with no rules or prerequisites" do
26
- flag_in = { key: "flagkey", version: 1 }
27
- json = Model.serialize(FEATURES, flag_in)
28
- flag_out = Model.deserialize(FEATURES, json)
29
- expect(flag_out).to eq flag_in
30
- end
31
-
32
- it "deserializes segment" do
33
- segment_in = { key: "segkey", version: 1 }
34
- json = Model.serialize(SEGMENTS, segment_in)
35
- segment_out = Model.deserialize(SEGMENTS, json)
36
- expect(segment_out).to eq segment_in
37
- end
38
- end
39
- end
40
- end
41
- end
@@ -1,12 +0,0 @@
1
- require "feature_store_spec_base"
2
- require "spec_helper"
3
-
4
- def create_in_memory_store(opts = {})
5
- LaunchDarkly::InMemoryFeatureStore.new
6
- end
7
-
8
- describe LaunchDarkly::InMemoryFeatureStore do
9
- subject { LaunchDarkly::InMemoryFeatureStore }
10
-
11
- include_examples "feature_store", method(:create_in_memory_store)
12
- end
@@ -1,40 +0,0 @@
1
- require "feature_store_spec_base"
2
- require "diplomat"
3
- require "spec_helper"
4
-
5
-
6
- $my_prefix = 'testprefix'
7
-
8
- $consul_base_opts = {
9
- prefix: $my_prefix,
10
- logger: $null_log
11
- }
12
-
13
- def create_consul_store(opts = {})
14
- LaunchDarkly::Integrations::Consul::new_feature_store(
15
- $consul_base_opts.merge(opts).merge({ expiration: 60 }))
16
- end
17
-
18
- def create_consul_store_uncached(opts = {})
19
- LaunchDarkly::Integrations::Consul::new_feature_store(
20
- $consul_base_opts.merge(opts).merge({ expiration: 0 }))
21
- end
22
-
23
- def clear_all_data
24
- Diplomat::Kv.delete($my_prefix + '/', recurse: true)
25
- end
26
-
27
-
28
- describe "Consul feature store" do
29
- break if ENV['LD_SKIP_DATABASE_TESTS'] == '1'
30
-
31
- # These tests will all fail if there isn't a local Consul instance running.
32
-
33
- context "with local cache" do
34
- include_examples "feature_store", method(:create_consul_store), method(:clear_all_data)
35
- end
36
-
37
- context "without local cache" do
38
- include_examples "feature_store", method(:create_consul_store_uncached), method(:clear_all_data)
39
- end
40
- end
@@ -1,103 +0,0 @@
1
- require "feature_store_spec_base"
2
- require "aws-sdk-dynamodb"
3
- require "spec_helper"
4
-
5
-
6
- $table_name = 'LD_DYNAMODB_TEST_TABLE'
7
- $endpoint = 'http://localhost:8000'
8
- $my_prefix = 'testprefix'
9
-
10
- $dynamodb_opts = {
11
- credentials: Aws::Credentials.new("key", "secret"),
12
- region: "us-east-1",
13
- endpoint: $endpoint
14
- }
15
-
16
- $ddb_base_opts = {
17
- dynamodb_opts: $dynamodb_opts,
18
- prefix: $my_prefix,
19
- logger: $null_log
20
- }
21
-
22
- def create_dynamodb_store(opts = {})
23
- LaunchDarkly::Integrations::DynamoDB::new_feature_store($table_name,
24
- $ddb_base_opts.merge(opts).merge({ expiration: 60 }))
25
- end
26
-
27
- def create_dynamodb_store_uncached(opts = {})
28
- LaunchDarkly::Integrations::DynamoDB::new_feature_store($table_name,
29
- $ddb_base_opts.merge(opts).merge({ expiration: 0 }))
30
- end
31
-
32
- def clear_all_data
33
- client = create_test_client
34
- items_to_delete = []
35
- req = {
36
- table_name: $table_name,
37
- projection_expression: '#namespace, #key',
38
- expression_attribute_names: {
39
- '#namespace' => 'namespace',
40
- '#key' => 'key'
41
- }
42
- }
43
- while true
44
- resp = client.scan(req)
45
- items_to_delete = items_to_delete + resp.items
46
- break if resp.last_evaluated_key.nil? || resp.last_evaluated_key.length == 0
47
- req.exclusive_start_key = resp.last_evaluated_key
48
- end
49
- requests = items_to_delete.map do |item|
50
- { delete_request: { key: item } }
51
- end
52
- LaunchDarkly::Impl::Integrations::DynamoDB::DynamoDBUtil.batch_write_requests(client, $table_name, requests)
53
- end
54
-
55
- def create_table_if_necessary
56
- client = create_test_client
57
- begin
58
- client.describe_table({ table_name: $table_name })
59
- return # no error, table exists
60
- rescue Aws::DynamoDB::Errors::ResourceNotFoundException
61
- # fall through to code below - we'll create the table
62
- end
63
-
64
- req = {
65
- table_name: $table_name,
66
- key_schema: [
67
- { attribute_name: "namespace", key_type: "HASH" },
68
- { attribute_name: "key", key_type: "RANGE" }
69
- ],
70
- attribute_definitions: [
71
- { attribute_name: "namespace", attribute_type: "S" },
72
- { attribute_name: "key", attribute_type: "S" }
73
- ],
74
- provisioned_throughput: {
75
- read_capacity_units: 1,
76
- write_capacity_units: 1
77
- }
78
- }
79
- client.create_table(req)
80
-
81
- # When DynamoDB creates a table, it may not be ready to use immediately
82
- end
83
-
84
- def create_test_client
85
- Aws::DynamoDB::Client.new($dynamodb_opts)
86
- end
87
-
88
-
89
- describe "DynamoDB feature store" do
90
- break if ENV['LD_SKIP_DATABASE_TESTS'] == '1'
91
-
92
- # These tests will all fail if there isn't a local DynamoDB instance running.
93
-
94
- create_table_if_necessary
95
-
96
- context "with local cache" do
97
- include_examples "feature_store", method(:create_dynamodb_store), method(:clear_all_data)
98
- end
99
-
100
- context "without local cache" do
101
- include_examples "feature_store", method(:create_dynamodb_store_uncached), method(:clear_all_data)
102
- end
103
- end
@@ -1,276 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe LaunchDarkly::Integrations::Util::CachingStoreWrapper do
4
- subject { LaunchDarkly::Integrations::Util::CachingStoreWrapper }
5
-
6
- THINGS = { namespace: "things" }
7
-
8
- shared_examples "tests" do |cached|
9
- opts = cached ? { expiration: 30 } : { expiration: 0 }
10
-
11
- it "gets item" do
12
- core = MockCore.new
13
- wrapper = subject.new(core, opts)
14
- key = "flag"
15
- itemv1 = { key: key, version: 1 }
16
- itemv2 = { key: key, version: 2 }
17
-
18
- core.force_set(THINGS, itemv1)
19
- expect(wrapper.get(THINGS, key)).to eq itemv1
20
-
21
- core.force_set(THINGS, itemv2)
22
- expect(wrapper.get(THINGS, key)).to eq (cached ? itemv1 : itemv2) # if cached, we will not see the new underlying value yet
23
- end
24
-
25
- it "gets deleted item" do
26
- core = MockCore.new
27
- wrapper = subject.new(core, opts)
28
- key = "flag"
29
- itemv1 = { key: key, version: 1, deleted: true }
30
- itemv2 = { key: key, version: 2, deleted: false }
31
-
32
- core.force_set(THINGS, itemv1)
33
- expect(wrapper.get(THINGS, key)).to eq nil # item is filtered out because deleted is true
34
-
35
- core.force_set(THINGS, itemv2)
36
- expect(wrapper.get(THINGS, key)).to eq (cached ? nil : itemv2) # if cached, we will not see the new underlying value yet
37
- end
38
-
39
- it "gets missing item" do
40
- core = MockCore.new
41
- wrapper = subject.new(core, opts)
42
- key = "flag"
43
- item = { key: key, version: 1 }
44
-
45
- expect(wrapper.get(THINGS, key)).to eq nil
46
-
47
- core.force_set(THINGS, item)
48
- expect(wrapper.get(THINGS, key)).to eq (cached ? nil : item) # the cache can retain a nil result
49
- end
50
-
51
- it "gets all items" do
52
- core = MockCore.new
53
- wrapper = subject.new(core, opts)
54
- item1 = { key: "flag1", version: 1 }
55
- item2 = { key: "flag2", version: 1 }
56
-
57
- core.force_set(THINGS, item1)
58
- core.force_set(THINGS, item2)
59
- expect(wrapper.all(THINGS)).to eq({ item1[:key] => item1, item2[:key] => item2 })
60
-
61
- core.force_remove(THINGS, item2[:key])
62
- expect(wrapper.all(THINGS)).to eq (cached ?
63
- { item1[:key] => item1, item2[:key] => item2 } :
64
- { item1[:key] => item1 })
65
- end
66
-
67
- it "gets all items filtering out deleted items" do
68
- core = MockCore.new
69
- wrapper = subject.new(core, opts)
70
- item1 = { key: "flag1", version: 1 }
71
- item2 = { key: "flag2", version: 1, deleted: true }
72
-
73
- core.force_set(THINGS, item1)
74
- core.force_set(THINGS, item2)
75
- expect(wrapper.all(THINGS)).to eq({ item1[:key] => item1 })
76
- end
77
-
78
- it "upserts item successfully" do
79
- core = MockCore.new
80
- wrapper = subject.new(core, opts)
81
- key = "flag"
82
- itemv1 = { key: key, version: 1 }
83
- itemv2 = { key: key, version: 2 }
84
-
85
- wrapper.upsert(THINGS, itemv1)
86
- expect(core.data[THINGS][key]).to eq itemv1
87
-
88
- wrapper.upsert(THINGS, itemv2)
89
- expect(core.data[THINGS][key]).to eq itemv2
90
-
91
- # if we have a cache, verify that the new item is now cached by writing a different value
92
- # to the underlying data - Get should still return the cached item
93
- if cached
94
- itemv3 = { key: key, version: 3 }
95
- core.force_set(THINGS, itemv3)
96
- end
97
-
98
- expect(wrapper.get(THINGS, key)).to eq itemv2
99
- end
100
-
101
- it "deletes item" do
102
- core = MockCore.new
103
- wrapper = subject.new(core, opts)
104
- key = "flag"
105
- itemv1 = { key: key, version: 1 }
106
- itemv2 = { key: key, version: 2, deleted: true }
107
- itemv3 = { key: key, version: 3 }
108
-
109
- core.force_set(THINGS, itemv1)
110
- expect(wrapper.get(THINGS, key)).to eq itemv1
111
-
112
- wrapper.delete(THINGS, key, 2)
113
- expect(core.data[THINGS][key]).to eq itemv2
114
-
115
- core.force_set(THINGS, itemv3) # make a change that bypasses the cache
116
-
117
- expect(wrapper.get(THINGS, key)).to eq (cached ? nil : itemv3)
118
- end
119
- end
120
-
121
- context "cached" do
122
- include_examples "tests", true
123
-
124
- cached_opts = { expiration: 30 }
125
-
126
- it "get uses values from init" do
127
- core = MockCore.new
128
- wrapper = subject.new(core, cached_opts)
129
- item1 = { key: "flag1", version: 1 }
130
- item2 = { key: "flag2", version: 1 }
131
-
132
- wrapper.init({ THINGS => { item1[:key] => item1, item2[:key] => item2 } })
133
- core.force_remove(THINGS, item1[:key])
134
-
135
- expect(wrapper.get(THINGS, item1[:key])).to eq item1
136
- end
137
-
138
- it "get all uses values from init" do
139
- core = MockCore.new
140
- wrapper = subject.new(core, cached_opts)
141
- item1 = { key: "flag1", version: 1 }
142
- item2 = { key: "flag2", version: 1 }
143
-
144
- wrapper.init({ THINGS => { item1[:key] => item1, item2[:key] => item2 } })
145
- core.force_remove(THINGS, item1[:key])
146
-
147
- expect(wrapper.all(THINGS)).to eq ({ item1[:key] => item1, item2[:key] => item2 })
148
- end
149
-
150
- it "upsert doesn't update cache if unsuccessful" do
151
- # This is for an upsert where the data in the store has a higher version. In an uncached
152
- # store, this is just a no-op as far as the wrapper is concerned so there's nothing to
153
- # test here. In a cached store, we need to verify that the cache has been refreshed
154
- # using the data that was found in the store.
155
- core = MockCore.new
156
- wrapper = subject.new(core, cached_opts)
157
- key = "flag"
158
- itemv1 = { key: key, version: 1 }
159
- itemv2 = { key: key, version: 2 }
160
-
161
- wrapper.upsert(THINGS, itemv2)
162
- expect(core.data[THINGS][key]).to eq itemv2
163
-
164
- wrapper.upsert(THINGS, itemv1)
165
- expect(core.data[THINGS][key]).to eq itemv2 # value in store remains the same
166
-
167
- itemv3 = { key: key, version: 3 }
168
- core.force_set(THINGS, itemv3) # bypasses cache so we can verify that itemv2 is in the cache
169
- expect(wrapper.get(THINGS, key)).to eq itemv2
170
- end
171
-
172
- it "initialized? can cache false result" do
173
- core = MockCore.new
174
- wrapper = subject.new(core, { expiration: 0.2 }) # use a shorter cache TTL for this test
175
-
176
- expect(wrapper.initialized?).to eq false
177
- expect(core.inited_query_count).to eq 1
178
-
179
- core.inited = true
180
- expect(wrapper.initialized?).to eq false
181
- expect(core.inited_query_count).to eq 1
182
-
183
- sleep(0.5)
184
-
185
- expect(wrapper.initialized?).to eq true
186
- expect(core.inited_query_count).to eq 2
187
-
188
- # From this point on it should remain true and the method should not be called
189
- expect(wrapper.initialized?).to eq true
190
- expect(core.inited_query_count).to eq 2
191
- end
192
- end
193
-
194
- context "uncached" do
195
- include_examples "tests", false
196
-
197
- uncached_opts = { expiration: 0 }
198
-
199
- it "queries internal initialized state only if not already inited" do
200
- core = MockCore.new
201
- wrapper = subject.new(core, uncached_opts)
202
-
203
- expect(wrapper.initialized?).to eq false
204
- expect(core.inited_query_count).to eq 1
205
-
206
- core.inited = true
207
- expect(wrapper.initialized?).to eq true
208
- expect(core.inited_query_count).to eq 2
209
-
210
- core.inited = false
211
- expect(wrapper.initialized?).to eq true
212
- expect(core.inited_query_count).to eq 2
213
- end
214
-
215
- it "does not query internal initialized state if init has been called" do
216
- core = MockCore.new
217
- wrapper = subject.new(core, uncached_opts)
218
-
219
- expect(wrapper.initialized?).to eq false
220
- expect(core.inited_query_count).to eq 1
221
-
222
- wrapper.init({})
223
-
224
- expect(wrapper.initialized?).to eq true
225
- expect(core.inited_query_count).to eq 1
226
- end
227
- end
228
-
229
- class MockCore
230
- def initialize
231
- @data = {}
232
- @inited = false
233
- @inited_query_count = 0
234
- end
235
-
236
- attr_reader :data
237
- attr_reader :inited_query_count
238
- attr_accessor :inited
239
-
240
- def force_set(kind, item)
241
- @data[kind] = {} if !@data.has_key?(kind)
242
- @data[kind][item[:key]] = item
243
- end
244
-
245
- def force_remove(kind, key)
246
- @data[kind].delete(key) if @data.has_key?(kind)
247
- end
248
-
249
- def init_internal(all_data)
250
- @data = all_data
251
- @inited = true
252
- end
253
-
254
- def get_internal(kind, key)
255
- items = @data[kind]
256
- items.nil? ? nil : items[key]
257
- end
258
-
259
- def get_all_internal(kind)
260
- @data[kind]
261
- end
262
-
263
- def upsert_internal(kind, item)
264
- @data[kind] = {} if !@data.has_key?(kind)
265
- old_item = @data[kind][item[:key]]
266
- return old_item if !old_item.nil? && old_item[:version] >= item[:version]
267
- @data[kind][item[:key]] = item
268
- item
269
- end
270
-
271
- def initialized_internal?
272
- @inited_query_count = @inited_query_count + 1
273
- @inited
274
- end
275
- end
276
- end
@@ -1,13 +0,0 @@
1
- require "spec_helper"
2
- require "bundler"
3
-
4
- describe LaunchDarkly do
5
- it "can be automatically loaded by Bundler.require" do
6
- ldclient_loaded =
7
- Bundler.with_unbundled_env do
8
- Kernel.system("ruby", "./spec/launchdarkly-server-sdk_spec_autoloadtest.rb")
9
- end
10
-
11
- expect(ldclient_loaded).to be true
12
- end
13
- end
@@ -1,9 +0,0 @@
1
- require "bundler/setup"
2
- require "bundler/inline"
3
-
4
- gemfile do
5
- gem "launchdarkly-server-sdk", path: "."
6
- end
7
-
8
- Bundler.require(:development)
9
- abort unless $LOADED_FEATURES.any? { |file| file =~ /ldclient-rb\.rb/ }
@@ -1,157 +0,0 @@
1
- require "http_util"
2
- require "spec_helper"
3
-
4
-
5
- SDK_KEY = "sdk-key"
6
-
7
- USER = { key: 'userkey' }
8
-
9
- ALWAYS_TRUE_FLAG = { key: 'flagkey', version: 1, on: false, offVariation: 1, variations: [ false, true ] }
10
- DATA_WITH_ALWAYS_TRUE_FLAG = {
11
- flags: { ALWAYS_TRUE_FLAG[:key ].to_sym => ALWAYS_TRUE_FLAG },
12
- segments: {}
13
- }
14
- PUT_EVENT_WITH_ALWAYS_TRUE_FLAG = "event: put\ndata:{\"data\":#{DATA_WITH_ALWAYS_TRUE_FLAG.to_json}}\n\n'"
15
-
16
- def with_client(config)
17
- client = LaunchDarkly::LDClient.new(SDK_KEY, config)
18
- begin
19
- yield client
20
- ensure
21
- client.close
22
- end
23
- end
24
-
25
- module LaunchDarkly
26
- # Note that we can't do end-to-end tests in streaming mode until we have a test server that can do streaming
27
- # responses, which is difficult in WEBrick.
28
-
29
- describe "LDClient end-to-end" do
30
- it "starts in polling mode" do
31
- with_server do |poll_server|
32
- poll_server.setup_ok_response("/sdk/latest-all", DATA_WITH_ALWAYS_TRUE_FLAG.to_json, "application/json")
33
-
34
- config = Config.new(
35
- stream: false,
36
- base_uri: poll_server.base_uri.to_s,
37
- send_events: false,
38
- logger: NullLogger.new
39
- )
40
- with_client(config) do |client|
41
- expect(client.initialized?).to be true
42
- expect(client.variation(ALWAYS_TRUE_FLAG[:key], USER, false)).to be true
43
- end
44
- end
45
- end
46
-
47
- it "fails in polling mode with 401 error" do
48
- with_server do |poll_server|
49
- poll_server.setup_status_response("/sdk/latest-all", 401)
50
-
51
- config = Config.new(
52
- stream: false,
53
- base_uri: poll_server.base_uri.to_s,
54
- send_events: false,
55
- logger: NullLogger.new
56
- )
57
- with_client(config) do |client|
58
- expect(client.initialized?).to be false
59
- expect(client.variation(ALWAYS_TRUE_FLAG[:key], USER, false)).to be false
60
- end
61
- end
62
- end
63
-
64
- it "sends event without diagnostics" do
65
- with_server do |poll_server|
66
- with_server do |events_server|
67
- events_server.setup_ok_response("/bulk", "")
68
- poll_server.setup_ok_response("/sdk/latest-all", '{"flags":{},"segments":{}}', "application/json")
69
-
70
- config = Config.new(
71
- stream: false,
72
- base_uri: poll_server.base_uri.to_s,
73
- events_uri: events_server.base_uri.to_s,
74
- diagnostic_opt_out: true,
75
- logger: NullLogger.new
76
- )
77
- with_client(config) do |client|
78
- client.identify(USER)
79
- client.flush
80
-
81
- req, body = events_server.await_request_with_body
82
- expect(req.header['authorization']).to eq [ SDK_KEY ]
83
- expect(req.header['connection']).to eq [ "Keep-Alive" ]
84
- data = JSON.parse(body)
85
- expect(data.length).to eq 1
86
- expect(data[0]["kind"]).to eq "identify"
87
- end
88
- end
89
- end
90
- end
91
-
92
- it "sends diagnostic event" do
93
- with_server do |poll_server|
94
- with_server do |events_server|
95
- events_server.setup_ok_response("/bulk", "")
96
- events_server.setup_ok_response("/diagnostic", "")
97
- poll_server.setup_ok_response("/sdk/latest-all", '{"flags":{},"segments":{}}', "application/json")
98
-
99
- config = Config.new(
100
- stream: false,
101
- base_uri: poll_server.base_uri.to_s,
102
- events_uri: events_server.base_uri.to_s,
103
- logger: NullLogger.new
104
- )
105
- with_client(config) do |client|
106
- user = { key: 'userkey' }
107
- client.identify(user)
108
- client.flush
109
-
110
- req0, body0 = events_server.await_request_with_body
111
- req1, body1 = events_server.await_request_with_body
112
- req = req0.path == "/diagnostic" ? req0 : req1
113
- body = req0.path == "/diagnostic" ? body0 : body1
114
- expect(req.header['authorization']).to eq [ SDK_KEY ]
115
- expect(req.header['connection']).to eq [ "Keep-Alive" ]
116
- data = JSON.parse(body)
117
- expect(data["kind"]).to eq "diagnostic-init"
118
- end
119
- end
120
- end
121
- end
122
-
123
- it "can use socket factory" do
124
- with_server do |poll_server|
125
- with_server do |events_server|
126
- events_server.setup_ok_response("/bulk", "")
127
- poll_server.setup_ok_response("/sdk/latest-all", '{"flags":{},"segments":{}}', "application/json")
128
-
129
- config = Config.new(
130
- stream: false,
131
- base_uri: "http://polling.com",
132
- events_uri: "http://events.com",
133
- diagnostic_opt_out: true,
134
- logger: NullLogger.new,
135
- socket_factory: SocketFactoryFromHash.new({
136
- "polling.com" => poll_server.port,
137
- "events.com" => events_server.port
138
- })
139
- )
140
- with_client(config) do |client|
141
- client.identify(USER)
142
- client.flush
143
-
144
- req, body = events_server.await_request_with_body
145
- expect(req.header['authorization']).to eq [ SDK_KEY ]
146
- expect(req.header['connection']).to eq [ "Keep-Alive" ]
147
- data = JSON.parse(body)
148
- expect(data.length).to eq 1
149
- expect(data[0]["kind"]).to eq "identify"
150
- end
151
- end
152
- end
153
- end
154
-
155
- # TODO: TLS tests with self-signed cert
156
- end
157
- end