ldclient-rb 0.8.0 → 2.0.1

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.
@@ -0,0 +1,56 @@
1
+ require "concurrent/atomics"
2
+ require "thread"
3
+
4
+ module LaunchDarkly
5
+ class PollingProcessor
6
+
7
+ def initialize(config, requestor)
8
+ @config = config
9
+ @requestor = requestor
10
+ @initialized = Concurrent::AtomicBoolean.new(false)
11
+ @started = Concurrent::AtomicBoolean.new(false)
12
+ end
13
+
14
+ def initialized?
15
+ @initialized.value
16
+ end
17
+
18
+ def start
19
+ return unless @started.make_true
20
+ @config.logger.info("[LDClient] Initializing polling connection")
21
+ create_worker
22
+ end
23
+
24
+ def poll
25
+ flags = @requestor.request_all_flags
26
+ if flags
27
+ @config.feature_store.init(flags)
28
+ if @initialized.make_true
29
+ @config.logger.info("[LDClient] Polling connection initialized")
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_worker
35
+ Thread.new do
36
+ @config.logger.debug("[LDClient] Starting polling worker")
37
+ loop do
38
+ begin
39
+ started_at = Time.now
40
+ poll
41
+ delta = @config.poll_interval - (Time.now - started_at)
42
+ if delta > 0
43
+ sleep(delta)
44
+ end
45
+ rescue StandardError => exn
46
+ @config.logger.error("[LDClient] Exception while polling: #{exn.inspect}")
47
+ # TODO: log_exception(__method__.to_s, exn)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+
54
+ private :poll, :create_worker
55
+ end
56
+ end
@@ -0,0 +1,56 @@
1
+ require "json"
2
+ require "net/http/persistent"
3
+ require "faraday/http_cache"
4
+
5
+ module LaunchDarkly
6
+
7
+ class Requestor
8
+ def initialize(sdk_key, config)
9
+ @sdk_key = sdk_key
10
+ @config = config
11
+ @client = Faraday.new do |builder|
12
+ builder.use :http_cache, store: @config.cache_store
13
+
14
+ builder.adapter :net_http_persistent
15
+ end
16
+ end
17
+
18
+ def request_all_flags()
19
+ make_request("/sdk/latest-flags")
20
+ end
21
+
22
+ def request_flag(key)
23
+ make_request("/sdk/latest-flags/" + key)
24
+ end
25
+
26
+ def make_request(path)
27
+ res = @client.get (@config.base_uri + path) do |req|
28
+ req.headers["Authorization"] = @sdk_key
29
+ req.headers["User-Agent"] = "RubyClient/" + LaunchDarkly::VERSION
30
+ req.options.timeout = @config.read_timeout
31
+ req.options.open_timeout = @config.connect_timeout
32
+ end
33
+
34
+ if res.status == 401
35
+ @config.logger.error("[LDClient] Invalid SDK key")
36
+ return nil
37
+ end
38
+
39
+ if res.status == 404
40
+ @config.logger.error("[LDClient] Resource not found")
41
+ return nil
42
+ end
43
+
44
+ if res.status / 100 != 2
45
+ @config.logger.error("[LDClient] Unexpected status code #{res.status}")
46
+ return nil
47
+ end
48
+
49
+ JSON.parse(res.body, symbolize_names: true)
50
+ end
51
+
52
+ private :make_request
53
+
54
+ end
55
+
56
+ end
@@ -1,173 +1,73 @@
1
1
  require "concurrent/atomics"
2
2
  require "json"
3
- require "ld-em-eventsource"
3
+ require "celluloid/eventsource"
4
4
 
5
5
  module LaunchDarkly
6
- PUT = "put"
7
- PATCH = "patch"
8
- DELETE = "delete"
9
-
10
- class InMemoryFeatureStore
11
- def initialize
12
- @features = Hash.new
13
- @lock = Concurrent::ReadWriteLock.new
14
- @initialized = Concurrent::AtomicBoolean.new(false)
15
- end
16
-
17
- def get(key)
18
- @lock.with_read_lock do
19
- f = @features[key.to_sym]
20
- (f.nil? || f[:deleted]) ? nil : f
21
- end
22
- end
23
-
24
- def all
25
- @lock.with_read_lock do
26
- @features.select { |_k, f| not f[:deleted] }
27
- end
28
- end
29
-
30
- def delete(key, version)
31
- @lock.with_write_lock do
32
- old = @features[key.to_sym]
33
-
34
- if !old.nil? && old[:version] < version
35
- old[:deleted] = true
36
- old[:version] = version
37
- @features[key.to_sym] = old
38
- elsif old.nil?
39
- @features[key.to_sym] = { deleted: true, version: version }
40
- end
41
- end
42
- end
43
-
44
- def init(fs)
45
- @lock.with_write_lock do
46
- @features.replace(fs)
47
- @initialized.make_true
48
- end
49
- end
50
-
51
- def upsert(key, feature)
52
- @lock.with_write_lock do
53
- old = @features[key.to_sym]
54
-
55
- if old.nil? || old[:version] < feature[:version]
56
- @features[key.to_sym] = feature
57
- end
58
- end
59
- end
60
-
61
- def initialized?
62
- @initialized.value
63
- end
64
- end
6
+ PUT = :put
7
+ PATCH = :patch
8
+ DELETE = :delete
9
+ INDIRECT_PUT = :'indirect/put'
10
+ INDIRECT_PATCH = :'indirect/patch'
65
11
 
66
12
  class StreamProcessor
67
- def initialize(api_key, config)
68
- @api_key = api_key
13
+ def initialize(sdk_key, config, requestor)
14
+ @sdk_key = sdk_key
69
15
  @config = config
70
- @store = config.feature_store ? config.feature_store : InMemoryFeatureStore.new
71
- @disconnected = Concurrent::AtomicReference.new(nil)
16
+ @store = config.feature_store
17
+ @requestor = requestor
18
+ @initialized = Concurrent::AtomicBoolean.new(false)
72
19
  @started = Concurrent::AtomicBoolean.new(false)
73
20
  end
74
21
 
75
22
  def initialized?
76
- @store.initialized?
77
- end
78
-
79
- def started?
80
- @started.value
81
- end
82
-
83
- def get_all_features
84
- if not initialized?
85
- throw :uninitialized
86
- end
87
- @store.all
88
- end
89
-
90
- def get_feature(key)
91
- if not initialized?
92
- throw :uninitialized
93
- end
94
- @store.get(key)
95
- end
96
-
97
- def start_reactor
98
- if defined?(Thin)
99
- @config.logger.debug("Running in a Thin environment-- not starting EventMachine")
100
- elsif EM.reactor_running?
101
- @config.logger.debug("EventMachine already running")
102
- else
103
- @config.logger.debug("Starting EventMachine")
104
- Thread.new { EM.run {} }
105
- Thread.pass until EM.reactor_running?
106
- end
107
- EM.reactor_running?
23
+ @initialized.value
108
24
  end
109
25
 
110
26
  def start
111
- # Try to start the reactor. If it's not started, we shouldn't start
112
- # the stream processor
113
- return if not start_reactor
114
-
115
- # If someone else booted the stream processor connection, just return
116
27
  return unless @started.make_true
117
28
 
118
- # If we're the first and only thread to set started, boot
119
- # the stream processor connection
120
- EM.defer do
121
- boot_event_manager
122
- end
123
- end
124
-
125
- def boot_event_manager
126
- source = EM::EventSource.new(@config.stream_uri + "/features",
127
- {},
128
- "Accept" => "text/event-stream",
129
- "Authorization" => "api_key " + @api_key,
130
- "User-Agent" => "RubyClient/" + LaunchDarkly::VERSION)
131
- source.on(PUT) { |message| process_message(message, PUT) }
132
- source.on(PATCH) { |message| process_message(message, PATCH) }
133
- source.on(DELETE) { |message| process_message(message, DELETE) }
134
- source.error do |error|
135
- @config.logger.info("[LDClient] Stream connection: #{error}")
136
- set_disconnected
29
+ @config.logger.info("[LDClient] Initializing stream connection")
30
+
31
+ headers =
32
+ {
33
+ 'Authorization' => @sdk_key,
34
+ 'User-Agent' => 'RubyClient/' + LaunchDarkly::VERSION
35
+ }
36
+ opts = {:headers => headers, :with_credentials => true}
37
+ @es = Celluloid::EventSource.new(@config.stream_uri + "/flags", opts) do |conn|
38
+ conn.on(PUT) { |message| process_message(message, PUT) }
39
+ conn.on(PATCH) { |message| process_message(message, PATCH) }
40
+ conn.on(DELETE) { |message| process_message(message, DELETE) }
41
+ conn.on(INDIRECT_PUT) { |message| process_message(message, INDIRECT_PUT) }
42
+ conn.on(INDIRECT_PATCH) { |message| process_message(message, INDIRECT_PATCH) }
43
+ conn.on_error do |message|
44
+ @config.logger.error("[LDClient] Error connecting to stream. Status code: #{message[:status_code]}")
45
+ end
137
46
  end
138
- source.inactivity_timeout = 0
139
- source.start
140
- source
141
47
  end
142
48
 
143
49
  def process_message(message, method)
144
- message = JSON.parse(message, symbolize_names: true)
50
+ message = JSON.parse(message.data, symbolize_names: true)
51
+ @config.logger.debug("[LDClient] Stream received #{method} message")
145
52
  if method == PUT
146
53
  @store.init(message)
54
+ @initialized.make_true
55
+ @config.logger.info("[LDClient] Stream initialized")
147
56
  elsif method == PATCH
148
57
  @store.upsert(message[:path][1..-1], message[:data])
149
58
  elsif method == DELETE
150
59
  @store.delete(message[:path][1..-1], message[:version])
60
+ elsif method == INDIRECT_PUT
61
+ @store.init(@requestor.request_all_flags)
62
+ @initialized.make_true
63
+ @config.logger.info("[LDClient] Stream initialized (via indirect message)")
64
+ elsif method == INDIRECT_PATCH
65
+ @store.upsert(@requestor.request_flag(message[:data]))
151
66
  else
152
67
  @config.logger.error("[LDClient] Unknown message received: #{method}")
153
68
  end
154
- set_connected
155
- end
156
-
157
- def set_disconnected
158
- @disconnected.set(Time.now)
159
- end
160
-
161
- def set_connected
162
- @disconnected.set(nil)
163
- end
164
-
165
- def should_fallback_update
166
- disc = @disconnected.get
167
- !disc.nil? && disc < (Time.now - 120)
168
69
  end
169
70
 
170
- # TODO mark private methods
171
- private :boot_event_manager, :process_message, :set_connected, :set_disconnected, :start_reactor
71
+ private :process_message
172
72
  end
173
73
  end
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "0.8.0"
2
+ VERSION = "2.0.1"
3
3
  end
@@ -32,14 +32,14 @@ describe LaunchDarkly::Config do
32
32
  expect(subject.new.stream_uri).to eq subject.default_stream_uri
33
33
  end
34
34
  end
35
- describe ".default_store" do
35
+ describe ".default_cache_store" do
36
36
  it "uses Rails cache if it is available" do
37
37
  rails = instance_double("Rails", cache: :cache)
38
38
  stub_const("Rails", rails)
39
- expect(subject.default_store).to eq :cache
39
+ expect(subject.default_cache_store).to eq :cache
40
40
  end
41
41
  it "uses memory store if Rails is not available" do
42
- expect(subject.default_store).to be_an_instance_of LaunchDarkly::ThreadSafeMemoryStore
42
+ expect(subject.default_cache_store).to be_an_instance_of LaunchDarkly::ThreadSafeMemoryStore
43
43
  end
44
44
  end
45
45
  describe ".default_logger" do
@@ -1,67 +1,36 @@
1
1
  {
2
- "name":"New recommendations engine",
3
- "key":"engine.enable",
4
- "kind":"flag",
5
- "salt":"ZW5naW5lLmVuYWJsZQ==",
2
+ "key":"test-feature-flag",
3
+ "version":11,
6
4
  "on":true,
7
- "variations":[
5
+ "prerequisites":[
6
+
7
+ ],
8
+ "salt":"718ea30a918a4eba8734b57ab1a93227",
9
+ "sel":"fe1244e5378c4f99976c9634e33667c6",
10
+ "targets":[
8
11
  {
9
- "value":true,
10
- "weight":31,
11
- "targets":[
12
- {
13
- "attribute":"groups",
14
- "op":"in",
15
- "values":[
16
- "Microsoft"
17
- ]
18
- }
12
+ "values":[
13
+ "alice"
19
14
  ],
20
- "userTarget":{
21
- "attribute":"key",
22
- "op":"in",
23
- "values":[
24
- "foo@bar.com",
25
- "Abbey.Orkwis@example.com",
26
- "Abbie.Stolte@example.com",
27
- "44d2781c-5985-4d89-b07a-0dffbd24126f",
28
- "0ffe4f0c-7aa9-4621-a87c-abe1c051abd8",
29
- "f52d99be-6a40-4cdd-a7b4-0548834fcbe5",
30
- "Vernetta.Belden@example.com",
31
- "c9d591bd-ea1f-465f-86d2-239ea41d0f0f",
32
- "870745092"
33
- ]
34
- }
15
+ "variation":0
35
16
  },
36
17
  {
37
- "value":false,
38
- "weight":69,
39
- "targets":[
40
- {
41
- "attribute":"key",
42
- "op":"in",
43
- "values":[
44
- "Alida.Caples@example.com"
45
- ]
46
- },
47
- {
48
- "attribute":"groups",
49
- "op":"in",
50
- "values":[
51
-
52
- ]
53
- }
18
+ "values":[
19
+ "bob"
54
20
  ],
55
- "userTarget":{
56
- "attribute":"key",
57
- "op":"in",
58
- "values":[
59
- "Alida.Caples@example.com"
60
- ]
61
- }
21
+ "variation":1
62
22
  }
63
23
  ],
64
- "ttl":0,
65
- "commitDate":"2015-05-10T06:06:45.381Z",
66
- "creationDate":"2014-09-02T20:39:18.61Z"
67
- }
24
+ "rules":[
25
+
26
+ ],
27
+ "fallthrough":{
28
+ "variation":0
29
+ },
30
+ "offVariation":1,
31
+ "variations":[
32
+ true,
33
+ false
34
+ ],
35
+ "deleted":false
36
+ }
@@ -1,10 +1,11 @@
1
1
  require "spec_helper"
2
2
 
3
+
3
4
  describe LaunchDarkly::LDClient do
4
5
  subject { LaunchDarkly::LDClient }
6
+ let(:config) { LaunchDarkly::Config.new({:offline => true}) }
5
7
  let(:client) do
6
- expect_any_instance_of(LaunchDarkly::LDClient).to receive :create_worker
7
- subject.new("api_key")
8
+ subject.new("secret", config)
8
9
  end
9
10
  let(:feature) do
10
11
  data = File.read(File.join("spec", "fixtures", "feature.json"))
@@ -23,246 +24,39 @@ describe LaunchDarkly::LDClient do
23
24
  JSON.parse(data, symbolize_names: true)
24
25
  end
25
26
 
26
- context 'user flag settings' do
27
- describe '#update_user_flag_setting' do
28
- it 'requires user' do
29
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
30
- client.update_user_flag_setting(nil, feature[:key], true)
31
- end
32
-
33
- it 'puts the new setting' do
34
- result = double('result', success?: true, status: 204)
35
- expect(client.instance_variable_get(:@client)).to receive(:put).and_return(result)
36
- client.update_user_flag_setting(user[:key], feature[:key], true)
37
- end
38
- end
39
- end
40
-
41
- describe '#flush' do
42
- it "will flush and post all events" do
43
- client.instance_variable_get(:@queue).push "asdf"
44
- client.instance_variable_get(:@queue).push "asdf"
45
- expect(client).to receive(:post_flushed_events)
46
- client.flush
47
- expect(client.instance_variable_get(:@queue).length).to eq 0
48
- end
49
- it "will not do anything if there are no events" do
50
- expect(client).to_not receive(:post_flushed_events)
51
- expect(client.instance_variable_get(:@config).logger).to_not receive :error
52
- client.flush
53
- end
54
- end
55
-
56
- describe '#post_flushed_events' do
57
- let(:events) { ["event"] }
58
- it "will flush and post all events" do
59
- result = double("result", status: 200)
60
- expect(client.instance_variable_get(:@client)).to receive(:post).with(LaunchDarkly::Config.default_events_uri + "/bulk").and_return result
61
- expect(client.instance_variable_get(:@config).logger).to_not receive :error
62
- client.send(:post_flushed_events, events)
63
- expect(client.instance_variable_get(:@queue).length).to eq 0
64
- end
65
- it "will allow any 2XX response" do
66
- result = double("result", status: 202)
67
- expect(client.instance_variable_get(:@client)).to receive(:post).and_return result
68
- expect(client.instance_variable_get(:@config).logger).to_not receive :error
69
- client.send(:post_flushed_events, events)
70
- end
71
- it "will work with unexpected post results" do
72
- result = double("result", status: 418)
73
- expect(client.instance_variable_get(:@client)).to receive(:post).and_return result
74
- expect(client.instance_variable_get(:@config).logger).to receive :error
75
- client.send(:post_flushed_events, events)
76
- expect(client.instance_variable_get(:@queue).length).to eq 0
77
- end
78
- end
79
-
80
- describe '#toggle?' do
81
- it "will not fail" do
82
- expect(client.instance_variable_get(:@config)).to receive(:stream?).and_raise RuntimeError
83
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
84
- result = client.toggle?(feature[:key], user, "default")
85
- expect(result).to eq "default"
86
- end
87
- it "will specify the default value in the feature request event" do
88
- expect(client).to receive(:add_event).with(hash_including(default: "default"))
89
- result = client.toggle?(feature[:key], user, "default")
90
- end
91
- it "requires user" do
92
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
93
- result = client.toggle?(feature[:key], nil, "default")
27
+ describe '#variation' do
28
+ it "will return the default value if the client is offline" do
29
+ result = client.variation(feature[:key], user, "default")
94
30
  expect(result).to eq "default"
95
31
  end
96
- it "sanitizes the user in the event" do
97
- expect(client).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
98
- client.toggle?(feature[:key], numeric_key_user, "default")
99
- end
100
- it "returns value from streamed flag if available" do
101
- expect(client.instance_variable_get(:@config)).to receive(:stream?).and_return(true).twice
102
- expect(client.instance_variable_get(:@stream_processor)).to receive(:started?).and_return true
103
- expect(client.instance_variable_get(:@stream_processor)).to receive(:initialized?).and_return true
104
- expect(client).to receive(:add_event)
105
- expect(client).to receive(:get_streamed_flag).and_return feature
106
- result = client.toggle?(feature[:key], user, "default")
107
- expect(result).to eq false
108
- end
109
- it "returns value from normal request if streamed flag is not available" do
110
- expect(client.instance_variable_get(:@config)).to receive(:stream?).and_return(false).twice
111
- expect(client).to receive(:add_event)
112
- expect(client).to receive(:get_flag_int).and_return feature
113
- result = client.toggle?(feature[:key], user, "default")
114
- expect(result).to eq false
115
- end
116
32
  end
117
33
 
118
- describe '#identify' do
119
- it "queues up an identify event" do
120
- expect(client).to receive(:add_event).with(hash_including(kind: "identify", key: user[:key], user: user))
121
- client.identify(user)
122
- end
123
- it "sanitizes the user in the event" do
124
- expect(client).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
125
- client.identify(numeric_key_user)
34
+ describe '#secure_mode_hash' do
35
+ it "will return the expected value for a known message and secret" do
36
+ result = client.secure_mode_hash({:key => :Message})
37
+ expect(result).to eq "aa747c502a898200f9e4fa21bac68136f886a0e27aec70ba06daf2e2a5cb5597"
126
38
  end
127
39
  end
128
40
 
129
41
  describe '#track' do
130
42
  it "queues up an custom event" do
131
- expect(client).to receive(:add_event).with(hash_including(kind: "custom", key: "custom_event_name", user: user, data: 42))
43
+ expect(client.instance_variable_get(:@event_processor)).to receive(:add_event).with(hash_including(kind: "custom", key: "custom_event_name", user: user, data: 42))
132
44
  client.track("custom_event_name", user, 42)
133
45
  end
134
46
  it "sanitizes the user in the event" do
135
- expect(client).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
47
+ expect(client.instance_variable_get(:@event_processor)).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
136
48
  client.track("custom_event_name", numeric_key_user, nil)
137
49
  end
138
50
  end
139
51
 
140
- describe '#get_streamed_flag' do
141
- it "will not check the polled flag normally" do
142
- expect(client).to receive(:get_flag_stream).and_return true
143
- expect(client).to_not receive(:get_flag_int)
144
- expect(client.send(:get_streamed_flag, "key")).to eq true
145
- end
146
- context "debug stream" do
147
- it "will log an error if the streamed and polled flag do not match" do
148
- expect(client.instance_variable_get(:@config)).to receive(:debug_stream?).and_return true
149
- expect(client).to receive(:get_flag_stream).and_return true
150
- expect(client).to receive(:get_flag_int).and_return false
151
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
152
- expect(client.send(:get_streamed_flag, "key")).to eq true
153
- end
154
- end
155
- end
156
-
157
- describe '#all_flags' do
158
- it "will parse and return the features list" do
159
- result = double("Faraday::Response", status: 200, body: '{"asdf":"qwer"}')
160
- expect(client).to receive(:make_request).with("/api/eval/features").and_return(result)
161
- data = client.send(:all_flags)
162
- expect(data).to eq(asdf: "qwer")
163
- end
164
- it "will log errors" do
165
- result = double("Faraday::Response", status: 418)
166
- expect(client).to receive(:make_request).with("/api/eval/features").and_return(result)
167
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
168
- client.send(:all_flags)
169
- end
170
- end
171
-
172
- describe '#get_flag_int' do
173
- it "will return the parsed flag" do
174
- result = double("Faraday::Response", status: 200, body: '{"asdf":"qwer"}')
175
- expect(client).to receive(:make_request).with("/api/eval/features/key").and_return(result)
176
- data = client.send(:get_flag_int, "key")
177
- expect(data).to eq(asdf: "qwer")
178
- end
179
- it "will accept 401 statuses" do
180
- result = double("Faraday::Response", status: 401)
181
- expect(client).to receive(:make_request).with("/api/eval/features/key").and_return(result)
182
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
183
- data = client.send(:get_flag_int, "key")
184
- expect(data).to be_nil
185
- end
186
- it "will accept 404 statuses" do
187
- result = double("Faraday::Response", status: 404)
188
- expect(client).to receive(:make_request).with("/api/eval/features/key").and_return(result)
189
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
190
- data = client.send(:get_flag_int, "key")
191
- expect(data).to be_nil
192
- end
193
- it "will accept non-standard statuses" do
194
- result = double("Faraday::Response", status: 418)
195
- expect(client).to receive(:make_request).with("/api/eval/features/key").and_return(result)
196
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
197
- data = client.send(:get_flag_int, "key")
198
- expect(data).to be_nil
199
- end
200
- end
201
-
202
- describe '#make_request' do
203
- it "will make a proper request" do
204
- expect(client.instance_variable_get :@client).to receive(:get)
205
- client.send(:make_request, "/asdf")
206
- end
207
- end
208
-
209
- describe '#param_for_user' do
210
- it "will return a consistent hash of a user key, feature key, and feature salt" do
211
- param = client.send(:param_for_user, feature, user)
212
- expect(param).to be_between(0.0, 1.0).inclusive
213
- end
214
- end
215
-
216
- describe '#evaluate' do
217
- it "will return nil if there is no feature" do
218
- expect(client.send(:evaluate, nil, user)).to eq nil
219
- end
220
- it "will return nil unless the feature is on" do
221
- feature[:on] = false
222
- expect(client.send(:evaluate, feature, user)).to eq nil
223
- end
224
- it "will return value if it matches the user" do
225
- user = { key: "Alida.Caples@example.com" }
226
- expect(client.send(:evaluate, feature, user)).to eq false
227
- user = { key: "foo@bar.com" }
228
- expect(client.send(:evaluate, feature, user)).to eq true
229
- end
230
- it "will return value if the target matches" do
231
- user = { key: "asdf@asdf.com", custom: { groups: "Microsoft" } }
232
- expect(client.send(:evaluate, feature, user)).to eq true
233
- end
234
- it "will return value if the weight matches" do
235
- expect(client).to receive(:param_for_user).and_return 0.1
236
- expect(client.send(:evaluate, feature, user)).to eq true
237
- expect(client).to receive(:param_for_user).and_return 0.9
238
- expect(client.send(:evaluate, feature, user)).to eq false
239
- end
240
- end
241
-
242
- describe '#log_timings' do
243
- let(:block) { lambda { "result" } }
244
- let(:label) { "label" }
245
- it "will not measure if not configured to do so" do
246
- expect(Benchmark).to_not receive(:measure)
247
- client.send(:log_timings, label, &block)
52
+ describe '#identify' do
53
+ it "queues up an identify event" do
54
+ expect(client.instance_variable_get(:@event_processor)).to receive(:add_event).with(hash_including(kind: "identify", key: user[:key], user: user))
55
+ client.identify(user)
248
56
  end
249
- context "logging enabled" do
250
- before do
251
- expect(client.instance_variable_get(:@config)).to receive(:log_timings?).and_return true
252
- expect(client.instance_variable_get(:@config).logger).to receive(:debug?).and_return true
253
- end
254
- it "will benchmark timings and return result" do
255
- expect(Benchmark).to receive(:measure).and_call_original
256
- expect(client.instance_variable_get(:@config).logger).to receive(:debug)
257
- result = client.send(:log_timings, label, &block)
258
- expect(result).to eq "result"
259
- end
260
- it "will raise exceptions if the block has them" do
261
- block = lambda { raise RuntimeError }
262
- expect(Benchmark).to receive(:measure).and_call_original
263
- expect(client.instance_variable_get(:@config).logger).to receive(:debug)
264
- expect { client.send(:log_timings, label, &block) }.to raise_error RuntimeError
265
- end
57
+ it "sanitizes the user in the event" do
58
+ expect(client.instance_variable_get(:@event_processor)).to receive(:add_event).with(hash_including(user: sanitized_numeric_key_user))
59
+ client.identify(numeric_key_user)
266
60
  end
267
61
  end
268
62
 
@@ -276,4 +70,4 @@ describe LaunchDarkly::LDClient do
276
70
  end
277
71
  end
278
72
  end
279
- end
73
+ end