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
data/spec/polling_spec.rb
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
require 'ostruct'
|
3
|
-
|
4
|
-
describe LaunchDarkly::PollingProcessor do
|
5
|
-
subject { LaunchDarkly::PollingProcessor }
|
6
|
-
let(:requestor) { double() }
|
7
|
-
|
8
|
-
def with_processor(store)
|
9
|
-
config = LaunchDarkly::Config.new(feature_store: store, logger: $null_log)
|
10
|
-
processor = subject.new(config, requestor)
|
11
|
-
begin
|
12
|
-
yield processor
|
13
|
-
ensure
|
14
|
-
processor.stop
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
describe 'successful request' do
|
19
|
-
flag = { key: 'flagkey', version: 1 }
|
20
|
-
segment = { key: 'segkey', version: 1 }
|
21
|
-
all_data = {
|
22
|
-
LaunchDarkly::FEATURES => {
|
23
|
-
flagkey: flag
|
24
|
-
},
|
25
|
-
LaunchDarkly::SEGMENTS => {
|
26
|
-
segkey: segment
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
it 'puts feature data in store' do
|
31
|
-
allow(requestor).to receive(:request_all_data).and_return(all_data)
|
32
|
-
store = LaunchDarkly::InMemoryFeatureStore.new
|
33
|
-
with_processor(store) do |processor|
|
34
|
-
ready = processor.start
|
35
|
-
ready.wait
|
36
|
-
expect(store.get(LaunchDarkly::FEATURES, "flagkey")).to eq(flag)
|
37
|
-
expect(store.get(LaunchDarkly::SEGMENTS, "segkey")).to eq(segment)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'sets initialized to true' do
|
42
|
-
allow(requestor).to receive(:request_all_data).and_return(all_data)
|
43
|
-
store = LaunchDarkly::InMemoryFeatureStore.new
|
44
|
-
with_processor(store) do |processor|
|
45
|
-
ready = processor.start
|
46
|
-
ready.wait
|
47
|
-
expect(processor.initialized?).to be true
|
48
|
-
expect(store.initialized?).to be true
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe 'connection error' do
|
54
|
-
it 'does not cause immediate failure, does not set initialized' do
|
55
|
-
allow(requestor).to receive(:request_all_data).and_raise(StandardError.new("test error"))
|
56
|
-
store = LaunchDarkly::InMemoryFeatureStore.new
|
57
|
-
with_processor(store) do |processor|
|
58
|
-
ready = processor.start
|
59
|
-
finished = ready.wait(0.2)
|
60
|
-
expect(finished).to be false
|
61
|
-
expect(processor.initialized?).to be false
|
62
|
-
expect(store.initialized?).to be false
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe 'HTTP errors' do
|
68
|
-
def verify_unrecoverable_http_error(status)
|
69
|
-
allow(requestor).to receive(:request_all_data).and_raise(LaunchDarkly::UnexpectedResponseError.new(status))
|
70
|
-
with_processor(LaunchDarkly::InMemoryFeatureStore.new) do |processor|
|
71
|
-
ready = processor.start
|
72
|
-
finished = ready.wait(0.2)
|
73
|
-
expect(finished).to be true
|
74
|
-
expect(processor.initialized?).to be false
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
def verify_recoverable_http_error(status)
|
79
|
-
allow(requestor).to receive(:request_all_data).and_raise(LaunchDarkly::UnexpectedResponseError.new(status))
|
80
|
-
with_processor(LaunchDarkly::InMemoryFeatureStore.new) do |processor|
|
81
|
-
ready = processor.start
|
82
|
-
finished = ready.wait(0.2)
|
83
|
-
expect(finished).to be false
|
84
|
-
expect(processor.initialized?).to be false
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
it 'stops immediately for error 401' do
|
89
|
-
verify_unrecoverable_http_error(401)
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'stops immediately for error 403' do
|
93
|
-
verify_unrecoverable_http_error(403)
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'does not stop immediately for error 408' do
|
97
|
-
verify_recoverable_http_error(408)
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'does not stop immediately for error 429' do
|
101
|
-
verify_recoverable_http_error(429)
|
102
|
-
end
|
103
|
-
|
104
|
-
it 'does not stop immediately for error 503' do
|
105
|
-
verify_recoverable_http_error(503)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe 'stop' do
|
110
|
-
it 'stops promptly rather than continuing to wait for poll interval' do
|
111
|
-
with_processor(LaunchDarkly::InMemoryFeatureStore.new) do |processor|
|
112
|
-
sleep(1) # somewhat arbitrary, but should ensure that it has started polling
|
113
|
-
start_time = Time.now
|
114
|
-
processor.stop
|
115
|
-
end_time = Time.now
|
116
|
-
expect(end_time - start_time).to be <(LaunchDarkly::Config.default_poll_interval - 5)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
@@ -1,121 +0,0 @@
|
|
1
|
-
require "feature_store_spec_base"
|
2
|
-
require "connection_pool"
|
3
|
-
require "json"
|
4
|
-
require "redis"
|
5
|
-
require "spec_helper"
|
6
|
-
|
7
|
-
|
8
|
-
$my_prefix = 'testprefix'
|
9
|
-
|
10
|
-
$base_opts = {
|
11
|
-
prefix: $my_prefix,
|
12
|
-
logger: $null_log
|
13
|
-
}
|
14
|
-
|
15
|
-
def create_redis_store(opts = {})
|
16
|
-
LaunchDarkly::RedisFeatureStore.new($base_opts.merge(opts).merge({ expiration: 60 }))
|
17
|
-
end
|
18
|
-
|
19
|
-
def create_redis_store_uncached(opts = {})
|
20
|
-
LaunchDarkly::RedisFeatureStore.new($base_opts.merge(opts).merge({ expiration: 0 }))
|
21
|
-
end
|
22
|
-
|
23
|
-
def clear_all_data
|
24
|
-
client = Redis.new
|
25
|
-
client.flushdb
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
describe LaunchDarkly::RedisFeatureStore do
|
30
|
-
subject { LaunchDarkly::RedisFeatureStore }
|
31
|
-
|
32
|
-
break if ENV['LD_SKIP_DATABASE_TESTS'] == '1'
|
33
|
-
|
34
|
-
# These tests will all fail if there isn't a Redis instance running on the default port.
|
35
|
-
|
36
|
-
context "real Redis with local cache" do
|
37
|
-
include_examples "feature_store", method(:create_redis_store), method(:clear_all_data)
|
38
|
-
end
|
39
|
-
|
40
|
-
context "real Redis without local cache" do
|
41
|
-
include_examples "feature_store", method(:create_redis_store_uncached), method(:clear_all_data)
|
42
|
-
end
|
43
|
-
|
44
|
-
def make_concurrent_modifier_test_hook(other_client, flag, start_version, end_version)
|
45
|
-
test_hook = Object.new
|
46
|
-
version_counter = start_version
|
47
|
-
expect(test_hook).to receive(:before_update_transaction) { |base_key, key|
|
48
|
-
if version_counter <= end_version
|
49
|
-
new_flag = flag.clone
|
50
|
-
new_flag[:version] = version_counter
|
51
|
-
other_client.hset(base_key, key, new_flag.to_json)
|
52
|
-
version_counter = version_counter + 1
|
53
|
-
end
|
54
|
-
}.at_least(:once)
|
55
|
-
test_hook
|
56
|
-
end
|
57
|
-
|
58
|
-
it "handles upsert race condition against external client with lower version" do
|
59
|
-
other_client = Redis.new({ url: "redis://localhost:6379" })
|
60
|
-
flag = { key: "foo", version: 1 }
|
61
|
-
test_hook = make_concurrent_modifier_test_hook(other_client, flag, 2, 4)
|
62
|
-
store = create_redis_store({ test_hook: test_hook })
|
63
|
-
|
64
|
-
begin
|
65
|
-
store.init(LaunchDarkly::FEATURES => { flag[:key] => flag })
|
66
|
-
|
67
|
-
my_ver = { key: "foo", version: 10 }
|
68
|
-
store.upsert(LaunchDarkly::FEATURES, my_ver)
|
69
|
-
result = store.get(LaunchDarkly::FEATURES, flag[:key])
|
70
|
-
expect(result[:version]).to eq 10
|
71
|
-
ensure
|
72
|
-
other_client.close
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
it "handles upsert race condition against external client with higher version" do
|
77
|
-
other_client = Redis.new({ url: "redis://localhost:6379" })
|
78
|
-
flag = { key: "foo", version: 1 }
|
79
|
-
test_hook = make_concurrent_modifier_test_hook(other_client, flag, 3, 3)
|
80
|
-
store = create_redis_store({ test_hook: test_hook })
|
81
|
-
|
82
|
-
begin
|
83
|
-
store.init(LaunchDarkly::FEATURES => { flag[:key] => flag })
|
84
|
-
|
85
|
-
my_ver = { key: "foo", version: 2 }
|
86
|
-
store.upsert(LaunchDarkly::FEATURES, my_ver)
|
87
|
-
result = store.get(LaunchDarkly::FEATURES, flag[:key])
|
88
|
-
expect(result[:version]).to eq 3
|
89
|
-
ensure
|
90
|
-
other_client.close
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
it "shuts down a custom Redis pool by default" do
|
95
|
-
unowned_pool = ConnectionPool.new(size: 1, timeout: 1) { Redis.new({ url: "redis://localhost:6379" }) }
|
96
|
-
store = create_redis_store({ pool: unowned_pool })
|
97
|
-
|
98
|
-
begin
|
99
|
-
store.init(LaunchDarkly::FEATURES => { })
|
100
|
-
store.stop
|
101
|
-
|
102
|
-
expect { unowned_pool.with {} }.to raise_error(ConnectionPool::PoolShuttingDownError)
|
103
|
-
ensure
|
104
|
-
unowned_pool.shutdown { |conn| conn.close }
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
it "doesn't shut down a custom Redis pool if pool_shutdown_on_close = false" do
|
109
|
-
unowned_pool = ConnectionPool.new(size: 1, timeout: 1) { Redis.new({ url: "redis://localhost:6379" }) }
|
110
|
-
store = create_redis_store({ pool: unowned_pool, pool_shutdown_on_close: false })
|
111
|
-
|
112
|
-
begin
|
113
|
-
store.init(LaunchDarkly::FEATURES => { })
|
114
|
-
store.stop
|
115
|
-
|
116
|
-
expect { unowned_pool.with {} }.not_to raise_error(ConnectionPool::PoolShuttingDownError)
|
117
|
-
ensure
|
118
|
-
unowned_pool.shutdown { |conn| conn.close }
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
data/spec/requestor_spec.rb
DELETED
@@ -1,209 +0,0 @@
|
|
1
|
-
require "http_util"
|
2
|
-
require "spec_helper"
|
3
|
-
|
4
|
-
$sdk_key = "secret"
|
5
|
-
|
6
|
-
describe LaunchDarkly::Requestor do
|
7
|
-
def with_requestor(base_uri, opts = {})
|
8
|
-
r = LaunchDarkly::Requestor.new($sdk_key, LaunchDarkly::Config.new({ base_uri: base_uri }.merge(opts)))
|
9
|
-
begin
|
10
|
-
yield r
|
11
|
-
ensure
|
12
|
-
r.stop
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "request_all_flags" do
|
17
|
-
it "uses expected URI and headers" do
|
18
|
-
with_server do |server|
|
19
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
20
|
-
server.setup_ok_response("/", "{}")
|
21
|
-
requestor.request_all_data()
|
22
|
-
expect(server.requests.count).to eq 1
|
23
|
-
expect(server.requests[0].unparsed_uri).to eq "/sdk/latest-all"
|
24
|
-
expect(server.requests[0].header).to include({
|
25
|
-
"authorization" => [ $sdk_key ],
|
26
|
-
"user-agent" => [ "RubyClient/" + LaunchDarkly::VERSION ]
|
27
|
-
})
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
it "parses response" do
|
33
|
-
expected_data = { flags: { x: { key: "x" } } }
|
34
|
-
with_server do |server|
|
35
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
36
|
-
server.setup_ok_response("/", expected_data.to_json)
|
37
|
-
data = requestor.request_all_data()
|
38
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
it "logs debug output" do
|
44
|
-
logger = ::Logger.new($stdout)
|
45
|
-
logger.level = ::Logger::DEBUG
|
46
|
-
with_server do |server|
|
47
|
-
with_requestor(server.base_uri.to_s, { logger: logger }) do |requestor|
|
48
|
-
server.setup_ok_response("/", { flags: { x: { key: "y" } } }.to_json)
|
49
|
-
expect do
|
50
|
-
requestor.request_all_data()
|
51
|
-
end.to output(/\[LDClient\] Got response from uri\:/).to_stdout_from_any_process
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
it "sends etag from previous response" do
|
57
|
-
etag = "xyz"
|
58
|
-
with_server do |server|
|
59
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
60
|
-
server.setup_response("/") do |req, res|
|
61
|
-
res.status = 200
|
62
|
-
res.body = "{}"
|
63
|
-
res["ETag"] = etag
|
64
|
-
end
|
65
|
-
requestor.request_all_data()
|
66
|
-
expect(server.requests.count).to eq 1
|
67
|
-
|
68
|
-
requestor.request_all_data()
|
69
|
-
expect(server.requests.count).to eq 2
|
70
|
-
expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
it "sends wrapper header if configured" do
|
76
|
-
with_server do |server|
|
77
|
-
with_requestor(server.base_uri.to_s, { wrapper_name: 'MyWrapper', wrapper_version: '1.0' }) do |requestor|
|
78
|
-
server.setup_ok_response("/", "{}")
|
79
|
-
requestor.request_all_data()
|
80
|
-
expect(server.requests.count).to eq 1
|
81
|
-
expect(server.requests[0].header).to include({
|
82
|
-
"x-launchdarkly-wrapper" => [ "MyWrapper/1.0" ]
|
83
|
-
})
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
it "can reuse cached data" do
|
89
|
-
etag = "xyz"
|
90
|
-
expected_data = { flags: { x: { key: "x" } } }
|
91
|
-
with_server do |server|
|
92
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
93
|
-
server.setup_response("/") do |req, res|
|
94
|
-
res.status = 200
|
95
|
-
res.body = expected_data.to_json
|
96
|
-
res["ETag"] = etag
|
97
|
-
end
|
98
|
-
requestor.request_all_data()
|
99
|
-
expect(server.requests.count).to eq 1
|
100
|
-
|
101
|
-
server.setup_response("/") do |req, res|
|
102
|
-
res.status = 304
|
103
|
-
end
|
104
|
-
data = requestor.request_all_data()
|
105
|
-
expect(server.requests.count).to eq 2
|
106
|
-
expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
|
107
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
it "replaces cached data with new data" do
|
113
|
-
etag1 = "abc"
|
114
|
-
etag2 = "xyz"
|
115
|
-
expected_data1 = { flags: { x: { key: "x" } } }
|
116
|
-
expected_data2 = { flags: { y: { key: "y" } } }
|
117
|
-
with_server do |server|
|
118
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
119
|
-
server.setup_response("/") do |req, res|
|
120
|
-
res.status = 200
|
121
|
-
res.body = expected_data1.to_json
|
122
|
-
res["ETag"] = etag1
|
123
|
-
end
|
124
|
-
data = requestor.request_all_data()
|
125
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data1)
|
126
|
-
expect(server.requests.count).to eq 1
|
127
|
-
|
128
|
-
server.setup_response("/") do |req, res|
|
129
|
-
res.status = 304
|
130
|
-
end
|
131
|
-
data = requestor.request_all_data()
|
132
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data1)
|
133
|
-
expect(server.requests.count).to eq 2
|
134
|
-
expect(server.requests[1].header).to include({ "if-none-match" => [ etag1 ] })
|
135
|
-
|
136
|
-
server.setup_response("/") do |req, res|
|
137
|
-
res.status = 200
|
138
|
-
res.body = expected_data2.to_json
|
139
|
-
res["ETag"] = etag2
|
140
|
-
end
|
141
|
-
data = requestor.request_all_data()
|
142
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data2)
|
143
|
-
expect(server.requests.count).to eq 3
|
144
|
-
expect(server.requests[2].header).to include({ "if-none-match" => [ etag1 ] })
|
145
|
-
|
146
|
-
server.setup_response("/") do |req, res|
|
147
|
-
res.status = 304
|
148
|
-
end
|
149
|
-
data = requestor.request_all_data()
|
150
|
-
expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data2)
|
151
|
-
expect(server.requests.count).to eq 4
|
152
|
-
expect(server.requests[3].header).to include({ "if-none-match" => [ etag2 ] })
|
153
|
-
end
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
it "uses UTF-8 encoding by default" do
|
158
|
-
content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["blue", "grėeń"]}}}'
|
159
|
-
with_server do |server|
|
160
|
-
server.setup_ok_response("/sdk/latest-all", content, "application/json")
|
161
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
162
|
-
data = requestor.request_all_data
|
163
|
-
expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(JSON.parse(content, symbolize_names: true)))
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
it "detects other encodings from Content-Type" do
|
169
|
-
content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["proszę", "dziękuję"]}}}'
|
170
|
-
with_server do |server|
|
171
|
-
server.setup_ok_response("/sdk/latest-all", content.encode(Encoding::ISO_8859_2),
|
172
|
-
"text/plain; charset=ISO-8859-2")
|
173
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
174
|
-
data = requestor.request_all_data
|
175
|
-
expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(JSON.parse(content, symbolize_names: true)))
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
|
180
|
-
it "throws exception for error status" do
|
181
|
-
with_server do |server|
|
182
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
183
|
-
server.setup_response("/") do |req, res|
|
184
|
-
res.status = 400
|
185
|
-
end
|
186
|
-
expect { requestor.request_all_data() }.to raise_error(LaunchDarkly::UnexpectedResponseError)
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
it "can use a proxy server" do
|
192
|
-
expected_data = { flags: { flagkey: { key: "flagkey" } } }
|
193
|
-
with_server do |server|
|
194
|
-
server.setup_ok_response("/sdk/latest-all", expected_data.to_json, "application/json", { "etag" => "x" })
|
195
|
-
with_server(StubProxyServer.new) do |proxy|
|
196
|
-
begin
|
197
|
-
ENV["http_proxy"] = proxy.base_uri.to_s
|
198
|
-
with_requestor(server.base_uri.to_s) do |requestor|
|
199
|
-
data = requestor.request_all_data
|
200
|
-
expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(expected_data))
|
201
|
-
end
|
202
|
-
ensure
|
203
|
-
ENV["http_proxy"] = nil
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
@@ -1,95 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
RSpec.shared_examples "segment_store" do |create_store_method|
|
4
|
-
|
5
|
-
let(:segment0) {
|
6
|
-
{
|
7
|
-
key: "test-segment",
|
8
|
-
version: 11,
|
9
|
-
salt: "718ea30a918a4eba8734b57ab1a93227",
|
10
|
-
rules: []
|
11
|
-
}
|
12
|
-
}
|
13
|
-
let(:key0) { segment0[:key].to_sym }
|
14
|
-
|
15
|
-
let!(:store) do
|
16
|
-
s = create_store_method.call()
|
17
|
-
s.init({ key0 => segment0 })
|
18
|
-
s
|
19
|
-
end
|
20
|
-
|
21
|
-
def new_version_plus(f, deltaVersion, attrs = {})
|
22
|
-
f1 = f.clone
|
23
|
-
f1[:version] = f[:version] + deltaVersion
|
24
|
-
f1.update(attrs)
|
25
|
-
f1
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
it "is initialized" do
|
30
|
-
expect(store.initialized?).to eq true
|
31
|
-
end
|
32
|
-
|
33
|
-
it "can get existing feature with symbol key" do
|
34
|
-
expect(store.get(key0)).to eq segment0
|
35
|
-
end
|
36
|
-
|
37
|
-
it "can get existing feature with string key" do
|
38
|
-
expect(store.get(key0.to_s)).to eq segment0
|
39
|
-
end
|
40
|
-
|
41
|
-
it "gets nil for nonexisting feature" do
|
42
|
-
expect(store.get('nope')).to be_nil
|
43
|
-
end
|
44
|
-
|
45
|
-
it "can get all features" do
|
46
|
-
feature1 = segment0.clone
|
47
|
-
feature1[:key] = "test-feature-flag1"
|
48
|
-
feature1[:version] = 5
|
49
|
-
feature1[:on] = false
|
50
|
-
store.upsert(:"test-feature-flag1", feature1)
|
51
|
-
expect(store.all).to eq ({ key0 => segment0, :"test-feature-flag1" => feature1 })
|
52
|
-
end
|
53
|
-
|
54
|
-
it "can add new feature" do
|
55
|
-
feature1 = segment0.clone
|
56
|
-
feature1[:key] = "test-feature-flag1"
|
57
|
-
feature1[:version] = 5
|
58
|
-
feature1[:on] = false
|
59
|
-
store.upsert(:"test-feature-flag1", feature1)
|
60
|
-
expect(store.get(:"test-feature-flag1")).to eq feature1
|
61
|
-
end
|
62
|
-
|
63
|
-
it "can update feature with newer version" do
|
64
|
-
f1 = new_version_plus(segment0, 1, { on: !segment0[:on] })
|
65
|
-
store.upsert(key0, f1)
|
66
|
-
expect(store.get(key0)).to eq f1
|
67
|
-
end
|
68
|
-
|
69
|
-
it "cannot update feature with same version" do
|
70
|
-
f1 = new_version_plus(segment0, 0, { on: !segment0[:on] })
|
71
|
-
store.upsert(key0, f1)
|
72
|
-
expect(store.get(key0)).to eq segment0
|
73
|
-
end
|
74
|
-
|
75
|
-
it "cannot update feature with older version" do
|
76
|
-
f1 = new_version_plus(segment0, -1, { on: !segment0[:on] })
|
77
|
-
store.upsert(key0, f1)
|
78
|
-
expect(store.get(key0)).to eq segment0
|
79
|
-
end
|
80
|
-
|
81
|
-
it "can delete feature with newer version" do
|
82
|
-
store.delete(key0, segment0[:version] + 1)
|
83
|
-
expect(store.get(key0)).to be_nil
|
84
|
-
end
|
85
|
-
|
86
|
-
it "cannot delete feature with same version" do
|
87
|
-
store.delete(key0, segment0[:version])
|
88
|
-
expect(store.get(key0)).to eq segment0
|
89
|
-
end
|
90
|
-
|
91
|
-
it "cannot delete feature with older version" do
|
92
|
-
store.delete(key0, segment0[:version] - 1)
|
93
|
-
expect(store.get(key0)).to eq segment0
|
94
|
-
end
|
95
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe LaunchDarkly::SimpleLRUCacheSet do
|
4
|
-
subject { LaunchDarkly::SimpleLRUCacheSet }
|
5
|
-
|
6
|
-
it "retains values up to capacity" do
|
7
|
-
lru = subject.new(3)
|
8
|
-
expect(lru.add("a")).to be false
|
9
|
-
expect(lru.add("b")).to be false
|
10
|
-
expect(lru.add("c")).to be false
|
11
|
-
expect(lru.add("a")).to be true
|
12
|
-
expect(lru.add("b")).to be true
|
13
|
-
expect(lru.add("c")).to be true
|
14
|
-
end
|
15
|
-
|
16
|
-
it "discards oldest value on overflow" do
|
17
|
-
lru = subject.new(2)
|
18
|
-
expect(lru.add("a")).to be false
|
19
|
-
expect(lru.add("b")).to be false
|
20
|
-
expect(lru.add("a")).to be true
|
21
|
-
expect(lru.add("c")).to be false # b is discarded as oldest
|
22
|
-
expect(lru.add("b")).to be false
|
23
|
-
end
|
24
|
-
end
|
data/spec/spec_helper.rb
DELETED
data/spec/store_spec.rb
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe LaunchDarkly::ThreadSafeMemoryStore do
|
4
|
-
subject { LaunchDarkly::ThreadSafeMemoryStore }
|
5
|
-
let(:store) { subject.new }
|
6
|
-
it "can read and write" do
|
7
|
-
store.write("key", "value")
|
8
|
-
expect(store.read("key")).to eq "value"
|
9
|
-
end
|
10
|
-
end
|
data/spec/stream_spec.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
require "ld-eventsource"
|
2
|
-
require "spec_helper"
|
3
|
-
|
4
|
-
describe LaunchDarkly::StreamProcessor do
|
5
|
-
subject { LaunchDarkly::StreamProcessor }
|
6
|
-
let(:config) { LaunchDarkly::Config.new }
|
7
|
-
let(:processor) { subject.new("sdk_key", config) }
|
8
|
-
|
9
|
-
describe '#process_message' do
|
10
|
-
let(:put_message) { SSE::StreamEvent.new(:put, '{"data":{"flags":{"asdf": {"key": "asdf"}},"segments":{"segkey": {"key": "segkey"}}}}') }
|
11
|
-
let(:patch_flag_message) { SSE::StreamEvent.new(:patch, '{"path": "/flags/key", "data": {"key": "asdf", "version": 1}}') }
|
12
|
-
let(:patch_seg_message) { SSE::StreamEvent.new(:patch, '{"path": "/segments/key", "data": {"key": "asdf", "version": 1}}') }
|
13
|
-
let(:delete_flag_message) { SSE::StreamEvent.new(:delete, '{"path": "/flags/key", "version": 2}') }
|
14
|
-
let(:delete_seg_message) { SSE::StreamEvent.new(:delete, '{"path": "/segments/key", "version": 2}') }
|
15
|
-
|
16
|
-
it "will accept PUT methods" do
|
17
|
-
processor.send(:process_message, put_message)
|
18
|
-
expect(config.feature_store.get(LaunchDarkly::FEATURES, "asdf")).to eq(key: "asdf")
|
19
|
-
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "segkey")).to eq(key: "segkey")
|
20
|
-
end
|
21
|
-
it "will accept PATCH methods for flags" do
|
22
|
-
processor.send(:process_message, patch_flag_message)
|
23
|
-
expect(config.feature_store.get(LaunchDarkly::FEATURES, "asdf")).to eq(key: "asdf", version: 1)
|
24
|
-
end
|
25
|
-
it "will accept PATCH methods for segments" do
|
26
|
-
processor.send(:process_message, patch_seg_message)
|
27
|
-
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "asdf")).to eq(key: "asdf", version: 1)
|
28
|
-
end
|
29
|
-
it "will accept DELETE methods for flags" do
|
30
|
-
processor.send(:process_message, patch_flag_message)
|
31
|
-
processor.send(:process_message, delete_flag_message)
|
32
|
-
expect(config.feature_store.get(LaunchDarkly::FEATURES, "key")).to eq(nil)
|
33
|
-
end
|
34
|
-
it "will accept DELETE methods for segments" do
|
35
|
-
processor.send(:process_message, patch_seg_message)
|
36
|
-
processor.send(:process_message, delete_seg_message)
|
37
|
-
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "key")).to eq(nil)
|
38
|
-
end
|
39
|
-
it "will log a warning if the method is not recognized" do
|
40
|
-
expect(processor.instance_variable_get(:@config).logger).to receive :warn
|
41
|
-
processor.send(:process_message, SSE::StreamEvent.new(type: :get, data: "", id: nil))
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|