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
         |