launchdarkly-server-sdk 6.1.1 → 6.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -5
  3. data/lib/ldclient-rb/config.rb +118 -4
  4. data/lib/ldclient-rb/evaluation_detail.rb +104 -14
  5. data/lib/ldclient-rb/events.rb +201 -107
  6. data/lib/ldclient-rb/file_data_source.rb +9 -300
  7. data/lib/ldclient-rb/flags_state.rb +23 -12
  8. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  9. data/lib/ldclient-rb/impl/diagnostic_events.rb +1 -1
  10. data/lib/ldclient-rb/impl/evaluator.rb +116 -62
  11. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +22 -9
  12. data/lib/ldclient-rb/impl/evaluator_helpers.rb +53 -0
  13. data/lib/ldclient-rb/impl/evaluator_operators.rb +1 -1
  14. data/lib/ldclient-rb/impl/event_summarizer.rb +63 -0
  15. data/lib/ldclient-rb/impl/event_types.rb +90 -0
  16. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
  17. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  18. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
  19. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  20. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +177 -0
  21. data/lib/ldclient-rb/impl/model/serialization.rb +7 -37
  22. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  23. data/lib/ldclient-rb/impl/util.rb +62 -1
  24. data/lib/ldclient-rb/integrations/consul.rb +8 -1
  25. data/lib/ldclient-rb/integrations/dynamodb.rb +48 -3
  26. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  27. data/lib/ldclient-rb/integrations/redis.rb +42 -2
  28. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
  29. data/lib/ldclient-rb/integrations/test_data.rb +209 -0
  30. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
  31. data/lib/ldclient-rb/integrations.rb +2 -51
  32. data/lib/ldclient-rb/interfaces.rb +152 -2
  33. data/lib/ldclient-rb/ldclient.rb +131 -33
  34. data/lib/ldclient-rb/polling.rb +22 -41
  35. data/lib/ldclient-rb/requestor.rb +3 -3
  36. data/lib/ldclient-rb/stream.rb +4 -3
  37. data/lib/ldclient-rb/util.rb +10 -1
  38. data/lib/ldclient-rb/version.rb +1 -1
  39. data/lib/ldclient-rb.rb +0 -1
  40. metadata +35 -132
  41. data/.circleci/config.yml +0 -40
  42. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
  43. data/.github/ISSUE_TEMPLATE/config.yml +0 -5
  44. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  45. data/.github/pull_request_template.md +0 -21
  46. data/.gitignore +0 -16
  47. data/.hound.yml +0 -2
  48. data/.ldrelease/build-docs.sh +0 -18
  49. data/.ldrelease/circleci/linux/execute.sh +0 -18
  50. data/.ldrelease/circleci/mac/execute.sh +0 -18
  51. data/.ldrelease/circleci/template/build.sh +0 -29
  52. data/.ldrelease/circleci/template/publish.sh +0 -23
  53. data/.ldrelease/circleci/template/set-gem-home.sh +0 -7
  54. data/.ldrelease/circleci/template/test.sh +0 -10
  55. data/.ldrelease/circleci/template/update-version.sh +0 -8
  56. data/.ldrelease/circleci/windows/execute.ps1 +0 -19
  57. data/.ldrelease/config.yml +0 -29
  58. data/.rspec +0 -2
  59. data/.rubocop.yml +0 -600
  60. data/.simplecov +0 -4
  61. data/CHANGELOG.md +0 -351
  62. data/CODEOWNERS +0 -1
  63. data/CONTRIBUTING.md +0 -37
  64. data/Gemfile +0 -3
  65. data/azure-pipelines.yml +0 -51
  66. data/docs/Makefile +0 -26
  67. data/docs/index.md +0 -9
  68. data/launchdarkly-server-sdk.gemspec +0 -45
  69. data/lib/ldclient-rb/event_summarizer.rb +0 -55
  70. data/lib/ldclient-rb/impl/event_factory.rb +0 -120
  71. data/spec/config_spec.rb +0 -63
  72. data/spec/diagnostic_events_spec.rb +0 -163
  73. data/spec/evaluation_detail_spec.rb +0 -135
  74. data/spec/event_sender_spec.rb +0 -197
  75. data/spec/event_summarizer_spec.rb +0 -63
  76. data/spec/events_spec.rb +0 -607
  77. data/spec/expiring_cache_spec.rb +0 -76
  78. data/spec/feature_store_spec_base.rb +0 -213
  79. data/spec/file_data_source_spec.rb +0 -283
  80. data/spec/fixtures/feature.json +0 -37
  81. data/spec/fixtures/feature1.json +0 -36
  82. data/spec/fixtures/user.json +0 -9
  83. data/spec/flags_state_spec.rb +0 -81
  84. data/spec/http_util.rb +0 -132
  85. data/spec/impl/evaluator_bucketing_spec.rb +0 -111
  86. data/spec/impl/evaluator_clause_spec.rb +0 -55
  87. data/spec/impl/evaluator_operators_spec.rb +0 -141
  88. data/spec/impl/evaluator_rule_spec.rb +0 -96
  89. data/spec/impl/evaluator_segment_spec.rb +0 -125
  90. data/spec/impl/evaluator_spec.rb +0 -305
  91. data/spec/impl/evaluator_spec_base.rb +0 -75
  92. data/spec/impl/model/serialization_spec.rb +0 -41
  93. data/spec/in_memory_feature_store_spec.rb +0 -12
  94. data/spec/integrations/consul_feature_store_spec.rb +0 -40
  95. data/spec/integrations/dynamodb_feature_store_spec.rb +0 -103
  96. data/spec/integrations/store_wrapper_spec.rb +0 -276
  97. data/spec/launchdarkly-server-sdk_spec.rb +0 -13
  98. data/spec/launchdarkly-server-sdk_spec_autoloadtest.rb +0 -9
  99. data/spec/ldclient_end_to_end_spec.rb +0 -157
  100. data/spec/ldclient_spec.rb +0 -643
  101. data/spec/newrelic_spec.rb +0 -5
  102. data/spec/polling_spec.rb +0 -120
  103. data/spec/redis_feature_store_spec.rb +0 -121
  104. data/spec/requestor_spec.rb +0 -196
  105. data/spec/segment_store_spec_base.rb +0 -95
  106. data/spec/simple_lru_cache_spec.rb +0 -24
  107. data/spec/spec_helper.rb +0 -9
  108. data/spec/store_spec.rb +0 -10
  109. data/spec/stream_spec.rb +0 -45
  110. data/spec/user_filter_spec.rb +0 -91
  111. data/spec/util_spec.rb +0 -17
  112. data/spec/version_spec.rb +0 -7
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
@@ -1,196 +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 "sends etag from previous response" do
44
- etag = "xyz"
45
- with_server do |server|
46
- with_requestor(server.base_uri.to_s) do |requestor|
47
- server.setup_response("/") do |req, res|
48
- res.status = 200
49
- res.body = "{}"
50
- res["ETag"] = etag
51
- end
52
- requestor.request_all_data()
53
- expect(server.requests.count).to eq 1
54
-
55
- requestor.request_all_data()
56
- expect(server.requests.count).to eq 2
57
- expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
58
- end
59
- end
60
- end
61
-
62
- it "sends wrapper header if configured" do
63
- with_server do |server|
64
- with_requestor(server.base_uri.to_s, { wrapper_name: 'MyWrapper', wrapper_version: '1.0' }) do |requestor|
65
- server.setup_ok_response("/", "{}")
66
- requestor.request_all_data()
67
- expect(server.requests.count).to eq 1
68
- expect(server.requests[0].header).to include({
69
- "x-launchdarkly-wrapper" => [ "MyWrapper/1.0" ]
70
- })
71
- end
72
- end
73
- end
74
-
75
- it "can reuse cached data" do
76
- etag = "xyz"
77
- expected_data = { flags: { x: { key: "x" } } }
78
- with_server do |server|
79
- with_requestor(server.base_uri.to_s) do |requestor|
80
- server.setup_response("/") do |req, res|
81
- res.status = 200
82
- res.body = expected_data.to_json
83
- res["ETag"] = etag
84
- end
85
- requestor.request_all_data()
86
- expect(server.requests.count).to eq 1
87
-
88
- server.setup_response("/") do |req, res|
89
- res.status = 304
90
- end
91
- data = requestor.request_all_data()
92
- expect(server.requests.count).to eq 2
93
- expect(server.requests[1].header).to include({ "if-none-match" => [ etag ] })
94
- expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data)
95
- end
96
- end
97
- end
98
-
99
- it "replaces cached data with new data" do
100
- etag1 = "abc"
101
- etag2 = "xyz"
102
- expected_data1 = { flags: { x: { key: "x" } } }
103
- expected_data2 = { flags: { y: { key: "y" } } }
104
- with_server do |server|
105
- with_requestor(server.base_uri.to_s) do |requestor|
106
- server.setup_response("/") do |req, res|
107
- res.status = 200
108
- res.body = expected_data1.to_json
109
- res["ETag"] = etag1
110
- end
111
- data = requestor.request_all_data()
112
- expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data1)
113
- expect(server.requests.count).to eq 1
114
-
115
- server.setup_response("/") do |req, res|
116
- res.status = 304
117
- end
118
- data = requestor.request_all_data()
119
- expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data1)
120
- expect(server.requests.count).to eq 2
121
- expect(server.requests[1].header).to include({ "if-none-match" => [ etag1 ] })
122
-
123
- server.setup_response("/") do |req, res|
124
- res.status = 200
125
- res.body = expected_data2.to_json
126
- res["ETag"] = etag2
127
- end
128
- data = requestor.request_all_data()
129
- expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data2)
130
- expect(server.requests.count).to eq 3
131
- expect(server.requests[2].header).to include({ "if-none-match" => [ etag1 ] })
132
-
133
- server.setup_response("/") do |req, res|
134
- res.status = 304
135
- end
136
- data = requestor.request_all_data()
137
- expect(data).to eq LaunchDarkly::Impl::Model.make_all_store_data(expected_data2)
138
- expect(server.requests.count).to eq 4
139
- expect(server.requests[3].header).to include({ "if-none-match" => [ etag2 ] })
140
- end
141
- end
142
- end
143
-
144
- it "uses UTF-8 encoding by default" do
145
- content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["blue", "grėeń"]}}}'
146
- with_server do |server|
147
- server.setup_ok_response("/sdk/latest-all", content, "application/json")
148
- with_requestor(server.base_uri.to_s) do |requestor|
149
- data = requestor.request_all_data
150
- expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(JSON.parse(content, symbolize_names: true)))
151
- end
152
- end
153
- end
154
-
155
- it "detects other encodings from Content-Type" do
156
- content = '{"flags": {"flagkey": {"key": "flagkey", "variations": ["proszę", "dziękuję"]}}}'
157
- with_server do |server|
158
- server.setup_ok_response("/sdk/latest-all", content.encode(Encoding::ISO_8859_2),
159
- "text/plain; charset=ISO-8859-2")
160
- with_requestor(server.base_uri.to_s) do |requestor|
161
- data = requestor.request_all_data
162
- expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(JSON.parse(content, symbolize_names: true)))
163
- end
164
- end
165
- end
166
-
167
- it "throws exception for error status" do
168
- with_server do |server|
169
- with_requestor(server.base_uri.to_s) do |requestor|
170
- server.setup_response("/") do |req, res|
171
- res.status = 400
172
- end
173
- expect { requestor.request_all_data() }.to raise_error(LaunchDarkly::UnexpectedResponseError)
174
- end
175
- end
176
- end
177
-
178
- it "can use a proxy server" do
179
- expected_data = { flags: { flagkey: { key: "flagkey" } } }
180
- with_server do |server|
181
- server.setup_ok_response("/sdk/latest-all", expected_data.to_json, "application/json", { "etag" => "x" })
182
- with_server(StubProxyServer.new) do |proxy|
183
- begin
184
- ENV["http_proxy"] = proxy.base_uri.to_s
185
- with_requestor(server.base_uri.to_s) do |requestor|
186
- data = requestor.request_all_data
187
- expect(data).to eq(LaunchDarkly::Impl::Model.make_all_store_data(expected_data))
188
- end
189
- ensure
190
- ENV["http_proxy"] = nil
191
- end
192
- end
193
- end
194
- end
195
- end
196
- 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
@@ -1,9 +0,0 @@
1
- require "ldclient-rb"
2
-
3
- $null_log = ::Logger.new($stdout)
4
- $null_log.level = ::Logger::FATAL
5
-
6
- RSpec.configure do |config|
7
- config.before(:each) do
8
- end
9
- end
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
-