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.
- checksums.yaml +4 -4
- data/README.md +4 -5
- data/lib/ldclient-rb/config.rb +118 -4
- data/lib/ldclient-rb/evaluation_detail.rb +104 -14
- data/lib/ldclient-rb/events.rb +201 -107
- data/lib/ldclient-rb/file_data_source.rb +9 -300
- data/lib/ldclient-rb/flags_state.rb +23 -12
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +1 -1
- data/lib/ldclient-rb/impl/evaluator.rb +116 -62
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +22 -9
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +53 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +1 -1
- data/lib/ldclient-rb/impl/event_summarizer.rb +63 -0
- data/lib/ldclient-rb/impl/event_types.rb +90 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +177 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +7 -37
- data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
- data/lib/ldclient-rb/impl/util.rb +62 -1
- data/lib/ldclient-rb/integrations/consul.rb +8 -1
- data/lib/ldclient-rb/integrations/dynamodb.rb +48 -3
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +42 -2
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
- data/lib/ldclient-rb/integrations/test_data.rb +209 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
- data/lib/ldclient-rb/integrations.rb +2 -51
- data/lib/ldclient-rb/interfaces.rb +152 -2
- data/lib/ldclient-rb/ldclient.rb +131 -33
- data/lib/ldclient-rb/polling.rb +22 -41
- data/lib/ldclient-rb/requestor.rb +3 -3
- data/lib/ldclient-rb/stream.rb +4 -3
- data/lib/ldclient-rb/util.rb +10 -1
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +0 -1
- metadata +35 -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 -351
- 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/lib/ldclient-rb/event_summarizer.rb +0 -55
- data/lib/ldclient-rb/impl/event_factory.rb +0 -120
- 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 -111
- 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 -96
- data/spec/impl/evaluator_segment_spec.rb +0 -125
- data/spec/impl/evaluator_spec.rb +0 -305
- data/spec/impl/evaluator_spec_base.rb +0 -75
- 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 -643
- 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
@@ -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,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
|