ldclient-rb 0.8.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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