ldclient-rb 2.5.0 → 3.0.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/CHANGELOG.md +9 -2
- data/circle.yml +11 -9
- data/ldclient-rb.gemspec +7 -2
- data/lib/ldclient-rb.rb +2 -2
- data/lib/ldclient-rb/config.rb +2 -1
- data/lib/ldclient-rb/evaluation.rb +77 -39
- data/lib/ldclient-rb/in_memory_store.rb +89 -0
- data/lib/ldclient-rb/ldclient.rb +2 -2
- data/lib/ldclient-rb/polling.rb +6 -3
- data/lib/ldclient-rb/{redis_feature_store.rb → redis_store.rb} +54 -42
- data/lib/ldclient-rb/requestor.rb +12 -0
- data/lib/ldclient-rb/stream.rb +44 -8
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/evaluation_spec.rb +204 -7
- data/spec/feature_store_spec_base.rb +20 -20
- data/spec/segment_store_spec_base.rb +95 -0
- data/spec/stream_spec.rb +32 -17
- metadata +6 -4
- data/lib/ldclient-rb/feature_store.rb +0 -63
@@ -0,0 +1,95 @@
|
|
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
|
data/spec/stream_spec.rb
CHANGED
@@ -3,20 +3,23 @@ require 'ostruct'
|
|
3
3
|
|
4
4
|
describe LaunchDarkly::InMemoryFeatureStore do
|
5
5
|
subject { LaunchDarkly::InMemoryFeatureStore }
|
6
|
+
|
7
|
+
include LaunchDarkly
|
8
|
+
|
6
9
|
let(:store) { subject.new }
|
7
10
|
let(:key) { :asdf }
|
8
|
-
let(:feature) { { value: "qwer", version: 0 } }
|
11
|
+
let(:feature) { { key: "asdf", value: "qwer", version: 0 } }
|
9
12
|
|
10
13
|
describe '#all' do
|
11
14
|
it "will get all keys" do
|
12
|
-
store.upsert(
|
13
|
-
data = store.all
|
15
|
+
store.upsert(LaunchDarkly::FEATURES, feature)
|
16
|
+
data = store.all(LaunchDarkly::FEATURES)
|
14
17
|
expect(data).to eq(key => feature)
|
15
18
|
end
|
16
19
|
it "will not get deleted keys" do
|
17
|
-
store.upsert(
|
18
|
-
store.delete(key, 1)
|
19
|
-
data = store.all
|
20
|
+
store.upsert(LaunchDarkly::FEATURES, feature)
|
21
|
+
store.delete(LaunchDarkly::FEATURES, key, 1)
|
22
|
+
data = store.all(LaunchDarkly::FEATURES)
|
20
23
|
expect(data).to eq({})
|
21
24
|
end
|
22
25
|
end
|
@@ -37,21 +40,33 @@ describe LaunchDarkly::StreamProcessor do
|
|
37
40
|
let(:processor) { subject.new("sdk_key", config, requestor) }
|
38
41
|
|
39
42
|
describe '#process_message' do
|
40
|
-
let(:put_message) { OpenStruct.new({data: '{"
|
41
|
-
let(:
|
42
|
-
let(:
|
43
|
+
let(:put_message) { OpenStruct.new({data: '{"data":{"flags":{"asdf": {"key": "asdf"}},"segments":{"segkey": {"key": "segkey"}}}}'}) }
|
44
|
+
let(:patch_flag_message) { OpenStruct.new({data: '{"path": "/flags/key", "data": {"key": "asdf", "version": 1}}'}) }
|
45
|
+
let(:patch_seg_message) { OpenStruct.new({data: '{"path": "/segments/key", "data": {"key": "asdf", "version": 1}}'}) }
|
46
|
+
let(:delete_flag_message) { OpenStruct.new({data: '{"path": "/flags/key", "version": 2}'}) }
|
47
|
+
let(:delete_seg_message) { OpenStruct.new({data: '{"path": "/segments/key", "version": 2}'}) }
|
43
48
|
it "will accept PUT methods" do
|
44
49
|
processor.send(:process_message, put_message, LaunchDarkly::PUT)
|
45
|
-
expect(
|
50
|
+
expect(config.feature_store.get(LaunchDarkly::FEATURES, "asdf")).to eq(key: "asdf")
|
51
|
+
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "segkey")).to eq(key: "segkey")
|
52
|
+
end
|
53
|
+
it "will accept PATCH methods for flags" do
|
54
|
+
processor.send(:process_message, patch_flag_message, LaunchDarkly::PATCH)
|
55
|
+
expect(config.feature_store.get(LaunchDarkly::FEATURES, "asdf")).to eq(key: "asdf", version: 1)
|
56
|
+
end
|
57
|
+
it "will accept PATCH methods for segments" do
|
58
|
+
processor.send(:process_message, patch_seg_message, LaunchDarkly::PATCH)
|
59
|
+
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "asdf")).to eq(key: "asdf", version: 1)
|
46
60
|
end
|
47
|
-
it "will accept
|
48
|
-
processor.send(:process_message,
|
49
|
-
|
61
|
+
it "will accept DELETE methods for flags" do
|
62
|
+
processor.send(:process_message, patch_flag_message, LaunchDarkly::PATCH)
|
63
|
+
processor.send(:process_message, delete_flag_message, LaunchDarkly::DELETE)
|
64
|
+
expect(config.feature_store.get(LaunchDarkly::FEATURES, "key")).to eq(nil)
|
50
65
|
end
|
51
|
-
it "will accept DELETE methods" do
|
52
|
-
processor.send(:process_message,
|
53
|
-
processor.send(:process_message,
|
54
|
-
expect(
|
66
|
+
it "will accept DELETE methods for segments" do
|
67
|
+
processor.send(:process_message, patch_seg_message, LaunchDarkly::PATCH)
|
68
|
+
processor.send(:process_message, delete_seg_message, LaunchDarkly::DELETE)
|
69
|
+
expect(config.feature_store.get(LaunchDarkly::SEGMENTS, "key")).to eq(nil)
|
55
70
|
end
|
56
71
|
it "will log a warning if the method is not recognized" do
|
57
72
|
expect(processor.instance_variable_get(:@config).logger).to receive :warn
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ldclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- LaunchDarkly
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -323,12 +323,12 @@ files:
|
|
323
323
|
- lib/ldclient-rb/evaluation.rb
|
324
324
|
- lib/ldclient-rb/event_serializer.rb
|
325
325
|
- lib/ldclient-rb/events.rb
|
326
|
-
- lib/ldclient-rb/
|
326
|
+
- lib/ldclient-rb/in_memory_store.rb
|
327
327
|
- lib/ldclient-rb/ldclient.rb
|
328
328
|
- lib/ldclient-rb/memoized_value.rb
|
329
329
|
- lib/ldclient-rb/newrelic.rb
|
330
330
|
- lib/ldclient-rb/polling.rb
|
331
|
-
- lib/ldclient-rb/
|
331
|
+
- lib/ldclient-rb/redis_store.rb
|
332
332
|
- lib/ldclient-rb/requestor.rb
|
333
333
|
- lib/ldclient-rb/stream.rb
|
334
334
|
- lib/ldclient-rb/version.rb
|
@@ -347,6 +347,7 @@ files:
|
|
347
347
|
- spec/newrelic_spec.rb
|
348
348
|
- spec/redis_feature_store_spec.rb
|
349
349
|
- spec/requestor_spec.rb
|
350
|
+
- spec/segment_store_spec_base.rb
|
350
351
|
- spec/spec_helper.rb
|
351
352
|
- spec/store_spec.rb
|
352
353
|
- spec/stream_spec.rb
|
@@ -390,6 +391,7 @@ test_files:
|
|
390
391
|
- spec/newrelic_spec.rb
|
391
392
|
- spec/redis_feature_store_spec.rb
|
392
393
|
- spec/requestor_spec.rb
|
394
|
+
- spec/segment_store_spec_base.rb
|
393
395
|
- spec/spec_helper.rb
|
394
396
|
- spec/store_spec.rb
|
395
397
|
- spec/stream_spec.rb
|
@@ -1,63 +0,0 @@
|
|
1
|
-
require "concurrent/atomics"
|
2
|
-
|
3
|
-
module LaunchDarkly
|
4
|
-
class InMemoryFeatureStore
|
5
|
-
def initialize
|
6
|
-
@features = Hash.new
|
7
|
-
@lock = Concurrent::ReadWriteLock.new
|
8
|
-
@initialized = Concurrent::AtomicBoolean.new(false)
|
9
|
-
end
|
10
|
-
|
11
|
-
def get(key)
|
12
|
-
@lock.with_read_lock do
|
13
|
-
f = @features[key.to_sym]
|
14
|
-
(f.nil? || f[:deleted]) ? nil : f
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def all
|
19
|
-
@lock.with_read_lock do
|
20
|
-
@features.select { |_k, f| not f[:deleted] }
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def delete(key, version)
|
25
|
-
@lock.with_write_lock do
|
26
|
-
old = @features[key.to_sym]
|
27
|
-
|
28
|
-
if !old.nil? && old[:version] < version
|
29
|
-
old[:deleted] = true
|
30
|
-
old[:version] = version
|
31
|
-
@features[key.to_sym] = old
|
32
|
-
elsif old.nil?
|
33
|
-
@features[key.to_sym] = { deleted: true, version: version }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def init(fs)
|
39
|
-
@lock.with_write_lock do
|
40
|
-
@features.replace(fs)
|
41
|
-
@initialized.make_true
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def upsert(key, feature)
|
46
|
-
@lock.with_write_lock do
|
47
|
-
old = @features[key.to_sym]
|
48
|
-
|
49
|
-
if old.nil? || old[:version] < feature[:version]
|
50
|
-
@features[key.to_sym] = feature
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def initialized?
|
56
|
-
@initialized.value
|
57
|
-
end
|
58
|
-
|
59
|
-
def stop
|
60
|
-
# nothing to do
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|