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.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/ldclient-rb/config.rb +81 -4
- data/lib/ldclient-rb/evaluation_detail.rb +67 -8
- data/lib/ldclient-rb/file_data_source.rb +9 -300
- 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 +80 -28
- 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/repeating_task.rb +47 -0
- data/lib/ldclient-rb/impl/util.rb +4 -1
- data/lib/ldclient-rb/integrations/consul.rb +7 -0
- data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +41 -1
- 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 +21 -7
- data/lib/ldclient-rb/polling.rb +22 -41
- data/lib/ldclient-rb/util.rb +1 -1
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +31 -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 -363
- 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 -209
- 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,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,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
|