ldclient-rb 5.0.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a11c0c98ebb0903ef634be9bab4779e88fac992
4
- data.tar.gz: 6248ed0604ba3ef0c4c7a871d7803d4fec026c9a
3
+ metadata.gz: 6a6cfa6490a0f46a455e7d65cbb9cdab695692e3
4
+ data.tar.gz: 6a749bb22220adfde7323876fa7174e50da15e70
5
5
  SHA512:
6
- metadata.gz: 0dd10cb337613fb44efb0492090ecedc08bdae9c279ce05af2578cdcbd199b20c7b2d314e7d6e31ce8b09c092e74ffb9187b011d5a76672c20a6ad760db445b7
7
- data.tar.gz: 4105be290c1951b38d569cea473422ad5178a7ddab511e9df7accf2e1ef7d9ee60d6d3774063278f2cb45ae3aba831dc95aba62503d4acb21ac4af0f182a6620
6
+ metadata.gz: f05eb914183f31142ef03bbad0319dda44cd25cbbd2723619aca74ba70f6e2a2043db40da35480da65c5ac64bd6bc6a08afb3b8379787c58e51a7a8ed6fed9c8
7
+ data.tar.gz: 53d7974ab225003f95e9fe636dff25b30c706873a13b4a1e24a33914f309ab66a8bce7438fd2382b43afc4e1ca8cc159e6b640b7cf0733ebd62c361aa42d11e2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  All notable changes to the LaunchDarkly Ruby SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).
4
4
 
5
+ ## [5.1.0] - 2018-08-27
6
+ ### Added:
7
+ - The new `LDClient` method `all_flags_state()` should be used instead of `all_flags()` if you are passing flag data to the front end for use with the JavaScript SDK. It preserves some flag metadata that the front end requires in order to send analytics events correctly. Versions 2.5.0 and above of the JavaScript SDK are able to use this metadata, but the output of `all_flags_state()` will still work with older versions.
8
+ - The `all_flags_state()` method also allows you to select only client-side-enabled flags to pass to the front end, by using the option `client_side_only: true`.
9
+
10
+ ### Changed:
11
+ - Unexpected exceptions are now logged at `ERROR` level, and exception stacktraces at `DEBUG` level. Previously, both were being logged at `WARN` level.
12
+
13
+ ### Deprecated:
14
+ - `LDClient.all_flags()`
15
+
16
+
17
+ ## [5.1.0] - 2018-08-27
18
+ ### Added:
19
+ - The new `LDClient` method `all_flags_state()` should be used instead of `all_flags()` if you are passing flag data to the front end for use with the JavaScript SDK. It preserves some flag metadata that the front end requires in order to send analytics events correctly. Versions 2.5.0 and above of the JavaScript SDK are able to use this metadata, but the output of `all_flags_state()` will still work with older versions.
20
+ - The `all_flags_state()` method also allows you to select only client-side-enabled flags to pass to the front end, by using the option `client_side_only: true`.
21
+
22
+ ### Changed:
23
+ - Unexpected exceptions are now logged at `ERROR` level, and exception stacktraces at `DEBUG` level. Previously, both were being logged at `WARN` level.
24
+
25
+ ### Deprecated:
26
+ - `LDClient.all_flags()`
27
+
28
+
5
29
  ## [5.0.1] - 2018-07-02
6
30
  ### Fixed:
7
31
  Fixed a regression in version 5.0.0 that could prevent the client from reconnecting if the stream connection was dropped by the server.
data/CODEOWNERS CHANGED
@@ -1 +1 @@
1
- * @ashanbrown
1
+
data/lib/ldclient-rb.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require "ldclient-rb/version"
2
2
  require "ldclient-rb/util"
3
3
  require "ldclient-rb/evaluation"
4
+ require "ldclient-rb/flags_state"
4
5
  require "ldclient-rb/ldclient"
5
6
  require "ldclient-rb/cache_store"
6
7
  require "ldclient-rb/expiring_cache"
@@ -142,7 +142,7 @@ module LaunchDarkly
142
142
  message.completed
143
143
  end
144
144
  rescue => e
145
- @config.logger.warn { "[LDClient] Unexpected error in event processor: #{e.inspect}. \nTrace: #{e.backtrace}" }
145
+ Util.log_exception(@config.logger, "Unexpected error in event processor", e)
146
146
  end
147
147
  end
148
148
  end
@@ -226,7 +226,7 @@ module LaunchDarkly
226
226
  resp = EventPayloadSendTask.new.run(@sdk_key, @config, @client, payload, @formatter)
227
227
  handle_response(resp) if !resp.nil?
228
228
  rescue => e
229
- @config.logger.warn { "[LDClient] Unexpected error in event processor: #{e.inspect}. \nTrace: #{e.backtrace}" }
229
+ Util.log_exception(@config.logger, "Unexpected error in event processor", e)
230
230
  end
231
231
  end
232
232
  buffer.clear if success # Reset our internal state, these events now belong to the flush worker
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+
3
+ module LaunchDarkly
4
+ #
5
+ # A snapshot of the state of all feature flags with regard to a specific user, generated by
6
+ # calling the client's all_flags_state method. Serializing this object to JSON using
7
+ # JSON.generate (or the to_json method) will produce the appropriate data structure for
8
+ # bootstrapping the LaunchDarkly JavaScript client.
9
+ #
10
+ class FeatureFlagsState
11
+ def initialize(valid)
12
+ @flag_values = {}
13
+ @flag_metadata = {}
14
+ @valid = valid
15
+ end
16
+
17
+ # Used internally to build the state map.
18
+ def add_flag(flag, value, variation)
19
+ key = flag[:key]
20
+ @flag_values[key] = value
21
+ meta = { version: flag[:version], trackEvents: flag[:trackEvents] }
22
+ meta[:variation] = variation if !variation.nil?
23
+ meta[:debugEventsUntilDate] = flag[:debugEventsUntilDate] if flag[:debugEventsUntilDate]
24
+ @flag_metadata[key] = meta
25
+ end
26
+
27
+ # Returns true if this object contains a valid snapshot of feature flag state, or false if the
28
+ # state could not be computed (for instance, because the client was offline or there was no user).
29
+ def valid?
30
+ @valid
31
+ end
32
+
33
+ # Returns the value of an individual feature flag at the time the state was recorded.
34
+ # Returns nil if the flag returned the default value, or if there was no such flag.
35
+ def flag_value(key)
36
+ @flag_values[key]
37
+ end
38
+
39
+ # Returns a map of flag keys to flag values. If a flag would have evaluated to the default value,
40
+ # its value will be nil.
41
+ #
42
+ # Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client.
43
+ # Instead, use as_json.
44
+ def values_map
45
+ @flag_values
46
+ end
47
+
48
+ # Returns a hash that can be used as a JSON representation of the entire state map, in the format
49
+ # used by the LaunchDarkly JavaScript SDK. Use this method if you are passing data to the front end
50
+ # in order to "bootstrap" the JavaScript client.
51
+ #
52
+ # Do not rely on the exact shape of this data, as it may change in future to support the needs of
53
+ # the JavaScript client.
54
+ def as_json(*) # parameter is unused, but may be passed if we're using the json gem
55
+ ret = @flag_values.clone
56
+ ret['$flagsState'] = @flag_metadata
57
+ ret['$valid'] = @valid
58
+ ret
59
+ end
60
+
61
+ # Same as as_json, but converts the JSON structure into a string.
62
+ def to_json(*a)
63
+ as_json.to_json(a)
64
+ end
65
+ end
66
+ end
@@ -162,7 +162,7 @@ module LaunchDarkly
162
162
  @event_processor.add_event(make_feature_event(feature, user, res[:variation], value, default))
163
163
  return value
164
164
  rescue => exn
165
- @config.logger.warn { "[LDClient] Error evaluating feature flag: #{exn.inspect}. \nTrace: #{exn.backtrace}" }
165
+ Util.log_exception(@config.logger, "Error evaluating feature flag", exn)
166
166
  @event_processor.add_event(make_feature_event(feature, user, nil, default, default))
167
167
  return default
168
168
  end
@@ -193,26 +193,61 @@ module LaunchDarkly
193
193
  end
194
194
 
195
195
  #
196
- # Returns all feature flag values for the given user
196
+ # Returns all feature flag values for the given user. This method is deprecated - please use
197
+ # {#all_flags_state} instead. Current versions of the client-side SDK will not generate analytics
198
+ # events correctly if you pass the result of all_flags.
199
+ #
200
+ # @param user [Hash] The end user requesting the feature flags
201
+ # @return [Hash] a hash of feature flag keys to values
197
202
  #
198
203
  def all_flags(user)
199
- sanitize_user(user)
200
- return Hash.new if @config.offline?
204
+ all_flags_state(user).values_map
205
+ end
201
206
 
202
- unless user
203
- @config.logger.error { "[LDClient] Must specify user in all_flags" }
204
- return Hash.new
207
+ #
208
+ # Returns a FeatureFlagsState object that encapsulates the state of all feature flags for a given user,
209
+ # including the flag values and also metadata that can be used on the front end. This method does not
210
+ # send analytics events back to LaunchDarkly.
211
+ #
212
+ # @param user [Hash] The end user requesting the feature flags
213
+ # @param options={} [Hash] Optional parameters to control how the state is generated
214
+ # @option options [Boolean] :client_side_only (false) True if only flags marked for use with the
215
+ # client-side SDK should be included in the state. By default, all flags are included.
216
+ # @return [FeatureFlagsState] a FeatureFlagsState object which can be serialized to JSON
217
+ #
218
+ def all_flags_state(user, options={})
219
+ return FeatureFlagsState.new(false) if @config.offline?
220
+
221
+ unless user && !user[:key].nil?
222
+ @config.logger.error { "[LDClient] User and user key must be specified in all_flags_state" }
223
+ return FeatureFlagsState.new(false)
205
224
  end
206
225
 
226
+ sanitize_user(user)
227
+
207
228
  begin
208
229
  features = @store.all(FEATURES)
209
-
210
- # TODO rescue if necessary
211
- Hash[features.map{ |k, f| [k, evaluate(f, user, @store, @config.logger)[:value]] }]
212
230
  rescue => exn
213
- @config.logger.warn { "[LDClient] Error evaluating all flags: #{exn.inspect}. \nTrace: #{exn.backtrace}" }
214
- return Hash.new
231
+ Util.log_exception(@config.logger, "Unable to read flags for all_flags_state", exn)
232
+ return FeatureFlagsState.new(false)
215
233
  end
234
+
235
+ state = FeatureFlagsState.new(true)
236
+ client_only = options[:client_side_only] || false
237
+ features.each do |k, f|
238
+ if client_only && !f[:clientSide]
239
+ next
240
+ end
241
+ begin
242
+ result = evaluate(f, user, @store, @config.logger)
243
+ state.add_flag(f, result[:value], result[:variation])
244
+ rescue => exn
245
+ Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
246
+ state.add_flag(f, nil, nil)
247
+ end
248
+ end
249
+
250
+ state
216
251
  end
217
252
 
218
253
  #
@@ -226,12 +261,6 @@ module LaunchDarkly
226
261
  @store.stop
227
262
  end
228
263
 
229
- def log_exception(caller, exn)
230
- error_traceback = "#{exn.inspect} #{exn}\n\t#{exn.backtrace.join("\n\t")}"
231
- error = "[LDClient] Unexpected exception in #{caller}: #{error_traceback}"
232
- @config.logger.error { error }
233
- end
234
-
235
264
  def sanitize_user(user)
236
265
  if user[:key]
237
266
  user[:key] = user[:key].to_s
@@ -252,7 +281,7 @@ module LaunchDarkly
252
281
  }
253
282
  end
254
283
 
255
- private :evaluate, :log_exception, :sanitize_user, :make_feature_event
284
+ private :evaluate, :sanitize_user, :make_feature_event
256
285
  end
257
286
 
258
287
  #
@@ -1,6 +1,11 @@
1
1
 
2
2
  module LaunchDarkly
3
3
  module Util
4
+ def self.log_exception(logger, message, exc)
5
+ logger.error { "[LDClient] #{message}: #{exc.inspect}" }
6
+ logger.debug { "[LDClient] Exception trace: #{exc.backtrace}" }
7
+ end
8
+
4
9
  def self.http_error_recoverable?(status)
5
10
  if status >= 400 && status < 500
6
11
  status == 400 || status == 408 || status == 429
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "5.0.1"
2
+ VERSION = "5.1.0"
3
3
  end
@@ -0,0 +1,82 @@
1
+ require "spec_helper"
2
+ require "json"
3
+
4
+ describe LaunchDarkly::FeatureFlagsState do
5
+ subject { LaunchDarkly::FeatureFlagsState }
6
+
7
+ it "can get flag value" do
8
+ state = subject.new(true)
9
+ flag = { key: 'key' }
10
+ state.add_flag(flag, 'value', 1)
11
+
12
+ expect(state.flag_value('key')).to eq 'value'
13
+ end
14
+
15
+ it "returns nil for unknown flag" do
16
+ state = subject.new(true)
17
+
18
+ expect(state.flag_value('key')).to be nil
19
+ end
20
+
21
+ it "can be converted to values map" do
22
+ state = subject.new(true)
23
+ flag1 = { key: 'key1' }
24
+ flag2 = { key: 'key2' }
25
+ state.add_flag(flag1, 'value1', 0)
26
+ state.add_flag(flag2, 'value2', 1)
27
+
28
+ expect(state.values_map).to eq({ 'key1' => 'value1', 'key2' => 'value2' })
29
+ end
30
+
31
+ it "can be converted to JSON structure" do
32
+ state = subject.new(true)
33
+ flag1 = { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false }
34
+ flag2 = { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true, debugEventsUntilDate: 1000 }
35
+ state.add_flag(flag1, 'value1', 0)
36
+ state.add_flag(flag2, 'value2', 1)
37
+
38
+ result = state.as_json
39
+ expect(result).to eq({
40
+ 'key1' => 'value1',
41
+ 'key2' => 'value2',
42
+ '$flagsState' => {
43
+ 'key1' => {
44
+ :variation => 0,
45
+ :version => 100,
46
+ :trackEvents => false
47
+ },
48
+ 'key2' => {
49
+ :variation => 1,
50
+ :version => 200,
51
+ :trackEvents => true,
52
+ :debugEventsUntilDate => 1000
53
+ }
54
+ },
55
+ '$valid' => true
56
+ })
57
+ end
58
+
59
+ it "can be converted to JSON string" do
60
+ state = subject.new(true)
61
+ flag1 = { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false }
62
+ flag2 = { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true, debugEventsUntilDate: 1000 }
63
+ state.add_flag(flag1, 'value1', 0)
64
+ state.add_flag(flag2, 'value2', 1)
65
+
66
+ object = state.as_json
67
+ str = state.to_json
68
+ expect(object.to_json).to eq(str)
69
+ end
70
+
71
+ it "uses our custom serializer with JSON.generate" do
72
+ state = subject.new(true)
73
+ flag1 = { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false }
74
+ flag2 = { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true, debugEventsUntilDate: 1000 }
75
+ state.add_flag(flag1, 'value1', 0)
76
+ state.add_flag(flag2, 'value2', 1)
77
+
78
+ stringFromToJson = state.to_json
79
+ stringFromGenerate = JSON.generate(state)
80
+ expect(stringFromGenerate).to eq(stringFromToJson)
81
+ end
82
+ end
@@ -99,6 +99,114 @@ describe LaunchDarkly::LDClient do
99
99
  end
100
100
  end
101
101
 
102
+ describe '#all_flags' do
103
+ let(:flag1) { { key: "key1", offVariation: 0, variations: [ 'value1' ] } }
104
+ let(:flag2) { { key: "key2", offVariation: 0, variations: [ 'value2' ] } }
105
+
106
+ it "returns flag values" do
107
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
108
+
109
+ result = client.all_flags({ key: 'userkey' })
110
+ expect(result).to eq({ 'key1' => 'value1', 'key2' => 'value2' })
111
+ end
112
+
113
+ it "returns empty map for nil user" do
114
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
115
+
116
+ result = client.all_flags(nil)
117
+ expect(result).to eq({})
118
+ end
119
+
120
+ it "returns empty map for nil user key" do
121
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
122
+
123
+ result = client.all_flags({})
124
+ expect(result).to eq({})
125
+ end
126
+
127
+ it "returns empty map if offline" do
128
+ offline_config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
129
+
130
+ result = offline_client.all_flags(nil)
131
+ expect(result).to eq({})
132
+ end
133
+ end
134
+
135
+ describe '#all_flags_state' do
136
+ let(:flag1) { { key: "key1", version: 100, offVariation: 0, variations: [ 'value1' ], trackEvents: false } }
137
+ let(:flag2) { { key: "key2", version: 200, offVariation: 1, variations: [ 'x', 'value2' ], trackEvents: true, debugEventsUntilDate: 1000 } }
138
+
139
+ it "returns flags state" do
140
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
141
+
142
+ state = client.all_flags_state({ key: 'userkey' })
143
+ expect(state.valid?).to be true
144
+
145
+ values = state.values_map
146
+ expect(values).to eq({ 'key1' => 'value1', 'key2' => 'value2' })
147
+
148
+ result = state.as_json
149
+ expect(result).to eq({
150
+ 'key1' => 'value1',
151
+ 'key2' => 'value2',
152
+ '$flagsState' => {
153
+ 'key1' => {
154
+ :variation => 0,
155
+ :version => 100,
156
+ :trackEvents => false
157
+ },
158
+ 'key2' => {
159
+ :variation => 1,
160
+ :version => 200,
161
+ :trackEvents => true,
162
+ :debugEventsUntilDate => 1000
163
+ }
164
+ },
165
+ '$valid' => true
166
+ })
167
+ end
168
+
169
+ it "can be filtered for only client-side flags" do
170
+ flag1 = { key: "server-side-1", offVariation: 0, variations: [ 'a' ], clientSide: false }
171
+ flag2 = { key: "server-side-2", offVariation: 0, variations: [ 'b' ], clientSide: false }
172
+ flag3 = { key: "client-side-1", offVariation: 0, variations: [ 'value1' ], clientSide: true }
173
+ flag4 = { key: "client-side-2", offVariation: 0, variations: [ 'value2' ], clientSide: true }
174
+ config.feature_store.init({ LaunchDarkly::FEATURES => {
175
+ flag1[:key] => flag1, flag2[:key] => flag2, flag3[:key] => flag3, flag4[:key] => flag4
176
+ }})
177
+
178
+ state = client.all_flags_state({ key: 'userkey' }, client_side_only: true)
179
+ expect(state.valid?).to be true
180
+
181
+ values = state.values_map
182
+ expect(values).to eq({ 'client-side-1' => 'value1', 'client-side-2' => 'value2' })
183
+ end
184
+
185
+ it "returns empty state for nil user" do
186
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
187
+
188
+ state = client.all_flags_state(nil)
189
+ expect(state.valid?).to be false
190
+ expect(state.values_map).to eq({})
191
+ end
192
+
193
+ it "returns empty state for nil user key" do
194
+ config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
195
+
196
+ state = client.all_flags_state({})
197
+ expect(state.valid?).to be false
198
+ expect(state.values_map).to eq({})
199
+ end
200
+
201
+ it "returns empty state if offline" do
202
+ offline_config.feature_store.init({ LaunchDarkly::FEATURES => { 'key1' => flag1, 'key2' => flag2 } })
203
+
204
+ state = offline_client.all_flags_state({ key: 'userkey' })
205
+ expect(state.valid?).to be false
206
+ expect(state.values_map).to eq({})
207
+ end
208
+ end
209
+
102
210
  describe '#secure_mode_hash' do
103
211
  it "will return the expected value for a known message and secret" do
104
212
  result = client.secure_mode_hash({key: :Message})
@@ -130,17 +238,6 @@ describe LaunchDarkly::LDClient do
130
238
  end
131
239
  end
132
240
 
133
- describe '#log_exception' do
134
- it "log error data" do
135
- expect(client.instance_variable_get(:@config).logger).to receive(:error)
136
- begin
137
- raise StandardError.new 'asdf'
138
- rescue StandardError => exn
139
- client.send(:log_exception, 'caller', exn)
140
- end
141
- end
142
- end
143
-
144
241
  describe 'with send_events: false' do
145
242
  let(:config) { LaunchDarkly::Config.new({offline: true, send_events: false, update_processor: update_processor}) }
146
243
  let(:client) { subject.new("secret", config) }
data/spec/util_spec.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "spec_helper"
2
+
3
+ describe LaunchDarkly::Util do
4
+ describe 'log_exception' do
5
+ let(:logger) { double() }
6
+
7
+ it "logs error data" do
8
+ expect(logger).to receive(:error)
9
+ expect(logger).to receive(:debug)
10
+ begin
11
+ raise StandardError.new 'asdf'
12
+ rescue StandardError => exn
13
+ LaunchDarkly::Util.log_exception(logger, "message", exn)
14
+ end
15
+ end
16
+ end
17
+ end
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: 5.0.1
4
+ version: 5.1.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-07-03 00:00:00.000000000 Z
11
+ date: 2018-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -310,6 +310,7 @@ files:
310
310
  - lib/ldclient-rb/event_summarizer.rb
311
311
  - lib/ldclient-rb/events.rb
312
312
  - lib/ldclient-rb/expiring_cache.rb
313
+ - lib/ldclient-rb/flags_state.rb
313
314
  - lib/ldclient-rb/in_memory_store.rb
314
315
  - lib/ldclient-rb/ldclient.rb
315
316
  - lib/ldclient-rb/memoized_value.rb
@@ -340,6 +341,7 @@ files:
340
341
  - spec/fixtures/numeric_key_user.json
341
342
  - spec/fixtures/sanitized_numeric_key_user.json
342
343
  - spec/fixtures/user.json
344
+ - spec/flags_state_spec.rb
343
345
  - spec/in_memory_feature_store_spec.rb
344
346
  - spec/ldclient_spec.rb
345
347
  - spec/newrelic_spec.rb
@@ -356,6 +358,7 @@ files:
356
358
  - spec/store_spec.rb
357
359
  - spec/stream_spec.rb
358
360
  - spec/user_filter_spec.rb
361
+ - spec/util_spec.rb
359
362
  - spec/version_spec.rb
360
363
  homepage: https://github.com/launchdarkly/ruby-client
361
364
  licenses:
@@ -393,6 +396,7 @@ test_files:
393
396
  - spec/fixtures/numeric_key_user.json
394
397
  - spec/fixtures/sanitized_numeric_key_user.json
395
398
  - spec/fixtures/user.json
399
+ - spec/flags_state_spec.rb
396
400
  - spec/in_memory_feature_store_spec.rb
397
401
  - spec/ldclient_spec.rb
398
402
  - spec/newrelic_spec.rb
@@ -409,4 +413,5 @@ test_files:
409
413
  - spec/store_spec.rb
410
414
  - spec/stream_spec.rb
411
415
  - spec/user_filter_spec.rb
416
+ - spec/util_spec.rb
412
417
  - spec/version_spec.rb