launchdarkly-server-sdk 5.7.0 → 5.8.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: 78fa0fd5890ae19f95b19c915694bca33187ef23
4
- data.tar.gz: 5dae22ebbc4a8c1d2e6deb3e206ff4d27937da02
3
+ metadata.gz: 70c964dad6b0915f006bee0d90160fe04bdd4853
4
+ data.tar.gz: f80c3dfe18ad4a70c0d7156ab06b748ffc897ae9
5
5
  SHA512:
6
- metadata.gz: 888c327731d50e3d4869b2c6fe72748a9c9bb7f2c80123194eb2d32f1b365a613dbf7bec78a38fcf4f35b16e2ec634725fde25681bad0225dbe9fee41ce674f8
7
- data.tar.gz: b8fcaa392b4940838daa060d66eea7dc2413d458875de3bb36905e70b194b9e34f6707d1d014c6e297ac81d396123411060c18761fe1a05c6287419cd902ca27
6
+ metadata.gz: 8a31c4b4a77caec7e64b54b18aabdecb12f2b5a44432d4ef74c94c2a1b57dff8a4cbf38f8dc894be8fec6f999bdc8bb62ff6095d1670ac892969329c435c9ff7
7
+ data.tar.gz: 2e21c5ad0cb881d99c905eaefc8952024d8fc12a1f28b1b2f4100cb39a95cdfa65d77b4a2067f3843ca9c79a446e6b071dd9c2470f2d0d0afba64d86365eb6cd
@@ -2,6 +2,35 @@
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.7.4] - 2020-05-04
6
+ ### Fixed:
7
+ - Setting a user's `custom` property explicitly to `nil`, rather than omitting it entirely or setting it to an empty hash, would cause the SDK to log an error and drop the current batch of analytics events. Now, it will be treated the same as an empty hash. ([#147](https://github.com/launchdarkly/ruby-server-sdk/issues/147))
8
+
9
+ ## [5.7.3] - 2020-04-27
10
+ ### Changed:
11
+ - Previously, installing the SDK in an environment that did not have `openssl` would cause a failure at build time. The SDK still requires `openssl` at runtime, but this check has been removed because it caused the `rake` problem mentioned below, and because `openssl` is normally bundled in modern Ruby versions.
12
+
13
+ ### Fixed:
14
+ - The `LDClient` constructor will fail immediately with a descriptive `ArgumentError` if you provide a `nil` SDK key in a configuration that requires an SDK key (that is, a configuration that _will_ require communicating with LaunchDarkly services). Previously, it would still fail, but without a clear error message. You are still allowed to omit the SDK key in an offline configuration. ([#154](https://github.com/launchdarkly/ruby-server-sdk/issues/154))
15
+ - Removed a hidden dependency on `rake` which could cause your build to fail if you had a dependency on this SDK and you did not have `rake` installed. ([#155](https://github.com/launchdarkly/ruby-server-sdk/issues/155))
16
+ - Previously a clause in a feature flag rule that used a string operator (such as "starts with") or a numeric operator (such as "greater than") could cause evaluation of the flag to completely fail and return a default value if the value on the right-hand side of the expression did not have the right data type-- for instance, "greater than" with a string value. The LaunchDarkly dashboard does not allow creation of such a rule, but it might be possible to do so via the REST API; the correct behavior of the SDK is to simply treat the expression as a non-match.
17
+
18
+ ## [5.7.2] - 2020-03-27
19
+ ### Fixed:
20
+ - Fixed a bug in the 5.7.0 and 5.7.1 releases that caused analytics events not to be sent unless diagnostic events were explicitly disabled. This also caused an error to be logged: `undefined method started?`.
21
+
22
+ ## [5.7.1] - 2020-03-18
23
+ ### Fixed:
24
+ - The backoff delay logic for reconnecting after a stream failure was broken so that if a failure occurred after a stream had been active for at least 60 seconds, retries would use _no_ delay, potentially causing a flood of requests and a spike in CPU usage. This bug was introduced in version 5.5.0 of the SDK.
25
+
26
+ ## [5.7.0] - 2020-03-10
27
+ ### Added:
28
+ - The SDK now periodically sends diagnostic data to LaunchDarkly, describing the version and configuration of the SDK, the architecture and version of the runtime platform, and performance statistics. No credentials, hostnames, or other identifiable values are included. This behavior can be disabled with `Config.diagnostic_opt_out` or configured with `Config.diagnostic_recording_interval`.
29
+ - New `Config` properties `wrapper_name` and `wrapper_version` allow a library that uses the Ruby SDK to identify itself for usage data if desired.
30
+
31
+ ### Removed:
32
+ - Removed an unused dependency on `rake`.
33
+
5
34
  ## [5.6.2] - 2020-01-15
6
35
  ### Fixed:
7
36
  - The SDK now specifies a uniquely identifiable request header when sending events to LaunchDarkly to ensure that events are only processed once, even if the SDK sends them two times due to a failed initial attempt.
@@ -1,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- launchdarkly-server-sdk (5.7.0)
4
+ launchdarkly-server-sdk (5.8.0)
5
5
  concurrent-ruby (~> 1.0)
6
6
  json (>= 1.8, < 3)
7
- ld-eventsource (= 1.0.2)
7
+ ld-eventsource (= 1.0.3)
8
8
  semantic (~> 1.6)
9
9
 
10
10
  GEM
@@ -40,7 +40,7 @@ GEM
40
40
  jmespath (1.4.0)
41
41
  json (1.8.6)
42
42
  json (1.8.6-java)
43
- ld-eventsource (1.0.2)
43
+ ld-eventsource (1.0.3)
44
44
  concurrent-ruby (~> 1.0)
45
45
  http_tools (~> 0.4.5)
46
46
  socketry (~> 0.5.1)
@@ -19,7 +19,6 @@ Gem::Specification.new do |spec|
19
19
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
  spec.require_paths = ["lib"]
22
- spec.extensions = 'ext/mkrf_conf.rb'
23
22
 
24
23
  spec.add_development_dependency "aws-sdk-dynamodb", "~> 1.18"
25
24
  spec.add_development_dependency "bundler", "~> 1.7"
@@ -35,5 +34,5 @@ Gem::Specification.new do |spec|
35
34
  spec.add_runtime_dependency "json", [">= 1.8", "< 3"]
36
35
  spec.add_runtime_dependency "semantic", "~> 1.6"
37
36
  spec.add_runtime_dependency "concurrent-ruby", "~> 1.0"
38
- spec.add_runtime_dependency "ld-eventsource", "1.0.2"
37
+ spec.add_runtime_dependency "ld-eventsource", "1.0.3"
39
38
  end
@@ -140,35 +140,44 @@ module LaunchDarkly
140
140
  end,
141
141
  endsWith:
142
142
  lambda do |a, b|
143
- (a.is_a? String) && (a.end_with? b)
143
+ (a.is_a? String) && (b.is_a? String) && (a.end_with? b)
144
144
  end,
145
145
  startsWith:
146
146
  lambda do |a, b|
147
- (a.is_a? String) && (a.start_with? b)
147
+ (a.is_a? String) && (b.is_a? String) && (a.start_with? b)
148
148
  end,
149
149
  matches:
150
150
  lambda do |a, b|
151
- (b.is_a? String) && !(Regexp.new b).match(a).nil?
151
+ if (b.is_a? String) && (b.is_a? String)
152
+ begin
153
+ re = Regexp.new b
154
+ !re.match(a).nil?
155
+ rescue
156
+ false
157
+ end
158
+ else
159
+ false
160
+ end
152
161
  end,
153
162
  contains:
154
163
  lambda do |a, b|
155
- (a.is_a? String) && (a.include? b)
164
+ (a.is_a? String) && (b.is_a? String) && (a.include? b)
156
165
  end,
157
166
  lessThan:
158
167
  lambda do |a, b|
159
- (a.is_a? Numeric) && (a < b)
168
+ (a.is_a? Numeric) && (b.is_a? Numeric) && (a < b)
160
169
  end,
161
170
  lessThanOrEqual:
162
171
  lambda do |a, b|
163
- (a.is_a? Numeric) && (a <= b)
172
+ (a.is_a? Numeric) && (b.is_a? Numeric) && (a <= b)
164
173
  end,
165
174
  greaterThan:
166
175
  lambda do |a, b|
167
- (a.is_a? Numeric) && (a > b)
176
+ (a.is_a? Numeric) && (b.is_a? Numeric) && (a > b)
168
177
  end,
169
178
  greaterThanOrEqual:
170
179
  lambda do |a, b|
171
- (a.is_a? Numeric) && (a >= b)
180
+ (a.is_a? Numeric) && (b.is_a? Numeric) && (a >= b)
172
181
  end,
173
182
  before:
174
183
  comparator(DATE_OPERAND) { |n| n < 0 },
@@ -91,6 +91,7 @@ module LaunchDarkly
91
91
  # @private
92
92
  class EventProcessor
93
93
  def initialize(sdk_key, config, client = nil, diagnostic_accumulator = nil, test_properties = nil)
94
+ raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
94
95
  @logger = config.logger
95
96
  @inbox = SizedQueue.new(config.capacity < 100 ? 100 : config.capacity)
96
97
  @flush_task = Concurrent::TimerTask.new(execution_interval: config.flush_interval) do
@@ -319,7 +320,7 @@ module LaunchDarkly
319
320
  success = flush_workers.post do
320
321
  begin
321
322
  events_out = @formatter.make_output_events(payload.events, payload.summary)
322
- result = @event_sender.send_event_data(events_out.to_json, false)
323
+ result = @event_sender.send_event_data(events_out.to_json, "#{events_out.length} events", false)
323
324
  @disabled.value = true if result.must_shutdown
324
325
  if !result.time_from_server.nil?
325
326
  @last_known_past_time.value = (result.time_from_server.to_f * 1000).to_i
@@ -348,7 +349,7 @@ module LaunchDarkly
348
349
  uri = URI(@config.events_uri + "/diagnostic")
349
350
  diagnostic_event_workers.post do
350
351
  begin
351
- @event_sender.send_event_data(event.to_json, true)
352
+ @event_sender.send_event_data(event.to_json, "diagnostic event", true)
352
353
  rescue => e
353
354
  Util.log_exception(@config.logger, "Unexpected error in event processor", e)
354
355
  end
@@ -18,10 +18,9 @@ module LaunchDarkly
18
18
  @retry_interval = retry_interval
19
19
  end
20
20
 
21
- def send_event_data(event_data, is_diagnostic)
21
+ def send_event_data(event_data, description, is_diagnostic)
22
22
  uri = is_diagnostic ? @diagnostic_uri : @events_uri
23
23
  payload_id = is_diagnostic ? nil : SecureRandom.uuid
24
- description = is_diagnostic ? 'diagnostic event' : "#{event_data.length} events"
25
24
  res = nil
26
25
  (0..1).each do |attempt|
27
26
  if attempt > 0
@@ -30,7 +29,7 @@ module LaunchDarkly
30
29
  end
31
30
  begin
32
31
  @client.start if !@client.started?
33
- @logger.debug { "[LDClient] sending #{description}: #{body}" }
32
+ @logger.debug { "[LDClient] sending #{description}: #{event_data}" }
34
33
  req = Net::HTTP::Post.new(uri)
35
34
  req.content_type = "application/json"
36
35
  req.body = event_data
@@ -33,6 +33,8 @@ module LaunchDarkly
33
33
  @pool = opts[:pool] || ConnectionPool.new(size: max_connections) do
34
34
  ::Redis.new(@redis_opts)
35
35
  end
36
+ # shutdown pool on close unless the client passed a custom pool and specified not to shutdown
37
+ @pool_shutdown_on_close = (!opts[:pool] || opts.fetch(:pool_shutdown_on_close, true))
36
38
  @prefix = opts[:prefix] || LaunchDarkly::Integrations::Redis::default_prefix
37
39
  @logger = opts[:logger] || Config.default_logger
38
40
  @test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
@@ -118,6 +120,7 @@ module LaunchDarkly
118
120
 
119
121
  def stop
120
122
  if @stopped.make_true
123
+ return unless @pool_shutdown_on_close
121
124
  @pool.shutdown { |redis| redis.close }
122
125
  end
123
126
  end
@@ -45,6 +45,9 @@ module LaunchDarkly
45
45
  # @option opts [Integer] :expiration (15) expiration time for the in-memory cache, in seconds; 0 for no local caching
46
46
  # @option opts [Integer] :capacity (1000) maximum number of items in the cache
47
47
  # @option opts [Object] :pool custom connection pool, if desired
48
+ # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool;
49
+ # this is true by default, and should be set to false only if you are managing the pool yourself and want its
50
+ # lifecycle to be independent of the SDK client
48
51
  # @return [LaunchDarkly::Interfaces::FeatureStore] a feature store object
49
52
  #
50
53
  def self.new_feature_store(opts)
@@ -33,6 +33,16 @@ module LaunchDarkly
33
33
  # @return [LDClient] The LaunchDarkly client instance
34
34
  #
35
35
  def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
36
+ # Note that sdk_key is normally a required parameter, and a nil value would cause the SDK to
37
+ # fail in most configurations. However, there are some configurations where it would be OK
38
+ # (offline = true, *or* we are using LDD mode or the file data source and events are disabled
39
+ # so we're not connecting to any LD services) so rather than try to check for all of those
40
+ # up front, we will let the constructors for the data source implementations implement this
41
+ # fail-fast as appropriate, and just check here for the part regarding events.
42
+ if !config.offline? && config.send_events
43
+ raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil?
44
+ end
45
+
36
46
  @sdk_key = sdk_key
37
47
 
38
48
  @event_factory_default = EventFactory.new(false)
@@ -56,7 +66,7 @@ module LaunchDarkly
56
66
  if @config.offline? || !@config.send_events
57
67
  @event_processor = NullEventProcessor.new
58
68
  else
59
- @event_processor = EventProcessor.new(sdk_key, config, diagnostic_accumulator)
69
+ @event_processor = EventProcessor.new(sdk_key, config, nil, diagnostic_accumulator)
60
70
  end
61
71
 
62
72
  if @config.use_ldd?
@@ -352,6 +362,7 @@ module LaunchDarkly
352
362
  if config.offline?
353
363
  return NullUpdateProcessor.new
354
364
  end
365
+ raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
355
366
  requestor = Requestor.new(sdk_key, config)
356
367
  if config.stream?
357
368
  StreamProcessor.new(sdk_key, config, requestor, diagnostic_accumulator)
@@ -35,6 +35,7 @@ module LaunchDarkly
35
35
  # @option opts [Integer] :expiration expiration time for the in-memory cache, in seconds; 0 for no local caching
36
36
  # @option opts [Integer] :capacity maximum number of feature flags (or related objects) to cache locally
37
37
  # @option opts [Object] :pool custom connection pool, if desired
38
+ # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool.
38
39
  #
39
40
  def initialize(opts = {})
40
41
  core = LaunchDarkly::Impl::Integrations::Redis::RedisFeatureStoreCore.new(opts)
@@ -15,8 +15,9 @@ module LaunchDarkly
15
15
  user_private_attrs = Set.new((user_props[:privateAttributeNames] || []).map(&:to_sym))
16
16
 
17
17
  filtered_user_props, removed = filter_values(user_props, user_private_attrs, ALLOWED_TOP_LEVEL_KEYS, IGNORED_TOP_LEVEL_KEYS)
18
- if user_props.has_key?(:custom)
19
- filtered_user_props[:custom], removed_custom = filter_values(user_props[:custom], user_private_attrs)
18
+ custom = user_props[:custom]
19
+ if !custom.nil?
20
+ filtered_user_props[:custom], removed_custom = filter_values(custom, user_private_attrs)
20
21
  removed.merge(removed_custom)
21
22
  end
22
23
 
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "5.7.0"
2
+ VERSION = "5.8.0"
3
3
  end
@@ -495,13 +495,13 @@ describe LaunchDarkly::Evaluation do
495
495
  # mixed strings and numbers
496
496
  [ :in, "99", 99, false ],
497
497
  [ :in, 99, "99", false ],
498
- #[ :contains, "99", 99, false ], # currently throws exception - would return false in Java SDK
499
- #[ :startsWith, "99", 99, false ], # currently throws exception - would return false in Java SDK
500
- #[ :endsWith, "99", 99, false ] # currently throws exception - would return false in Java SDK
498
+ [ :contains, "99", 99, false ],
499
+ [ :startsWith, "99", 99, false ],
500
+ [ :endsWith, "99", 99, false ],
501
501
  [ :lessThanOrEqual, "99", 99, false ],
502
- #[ :lessThanOrEqual, 99, "99", false ], # currently throws exception - would return false in Java SDK
502
+ [ :lessThanOrEqual, 99, "99", false ],
503
503
  [ :greaterThanOrEqual, "99", 99, false ],
504
- #[ :greaterThanOrEqual, 99, "99", false ], # currently throws exception - would return false in Java SDK
504
+ [ :greaterThanOrEqual, 99, "99", false ],
505
505
 
506
506
  # regex
507
507
  [ :matches, "hello world", "hello.*rld", true ],
@@ -509,7 +509,7 @@ describe LaunchDarkly::Evaluation do
509
509
  [ :matches, "hello world", "l+", true ],
510
510
  [ :matches, "hello world", "(world|planet)", true ],
511
511
  [ :matches, "hello world", "aloha", false ],
512
- #[ :matches, "hello world", "***not a regex", false ] # currently throws exception - same as Java SDK
512
+ [ :matches, "hello world", "***not a regex", false ],
513
513
 
514
514
  # dates
515
515
  [ :before, dateStr1, dateStr2, true ],
@@ -27,7 +27,7 @@ module LaunchDarkly
27
27
  with_sender_and_server do |es, server|
28
28
  server.setup_ok_response("/bulk", "")
29
29
 
30
- result = es.send_event_data(fake_data, false)
30
+ result = es.send_event_data(fake_data, "", false)
31
31
 
32
32
  expect(result.success).to be true
33
33
  expect(result.must_shutdown).to be false
@@ -49,8 +49,8 @@ module LaunchDarkly
49
49
  with_sender_and_server do |es, server|
50
50
  server.setup_ok_response("/bulk", "")
51
51
 
52
- result1 = es.send_event_data(fake_data, false)
53
- result2 = es.send_event_data(fake_data, false)
52
+ result1 = es.send_event_data(fake_data, "", false)
53
+ result2 = es.send_event_data(fake_data, "", false)
54
54
  expect(result1.success).to be true
55
55
  expect(result2.success).to be true
56
56
 
@@ -66,7 +66,7 @@ module LaunchDarkly
66
66
  with_sender_and_server do |es, server|
67
67
  server.setup_ok_response("/diagnostic", "")
68
68
 
69
- result = es.send_event_data(fake_data, true)
69
+ result = es.send_event_data(fake_data, "", true)
70
70
 
71
71
  expect(result.success).to be true
72
72
  expect(result.must_shutdown).to be false
@@ -94,7 +94,7 @@ module LaunchDarkly
94
94
 
95
95
  es = make_sender(server)
96
96
 
97
- result = es.send_event_data(fake_data, false)
97
+ result = es.send_event_data(fake_data, "", false)
98
98
 
99
99
  expect(result.success).to be true
100
100
 
@@ -116,7 +116,7 @@ module LaunchDarkly
116
116
  res.status = req_count == 2 ? 200 : status
117
117
  end
118
118
 
119
- result = es.send_event_data(fake_data, false)
119
+ result = es.send_event_data(fake_data, "", false)
120
120
 
121
121
  expect(result.success).to be true
122
122
  expect(result.must_shutdown).to be false
@@ -141,7 +141,7 @@ module LaunchDarkly
141
141
  res.status = req_count == 3 ? 200 : status
142
142
  end
143
143
 
144
- result = es.send_event_data(fake_data, false)
144
+ result = es.send_event_data(fake_data, "", false)
145
145
 
146
146
  expect(result.success).to be false
147
147
  expect(result.must_shutdown).to be false
@@ -164,7 +164,7 @@ module LaunchDarkly
164
164
  res.status = status
165
165
  end
166
166
 
167
- result = es.send_event_data(fake_data, false)
167
+ result = es.send_event_data(fake_data, "", false)
168
168
 
169
169
  expect(result.success).to be false
170
170
  expect(result.must_shutdown).to be true
@@ -408,6 +408,17 @@ describe LaunchDarkly::EventProcessor do
408
408
  end
409
409
  end
410
410
 
411
+ it "treats nil value for custom the same as an empty hash" do
412
+ with_processor_and_sender(default_config) do |ep, sender|
413
+ user_with_nil_custom = { key: "userkey", custom: nil }
414
+ e = { kind: "identify", key: "userkey", user: user_with_nil_custom }
415
+ ep.add_event(e)
416
+
417
+ output = flush_and_get_events(ep, sender)
418
+ expect(output).to contain_exactly(e)
419
+ end
420
+ end
421
+
411
422
  it "does a final flush when shutting down" do
412
423
  with_processor_and_sender(default_config) do |ep, sender|
413
424
  e = { kind: "identify", key: user[:key], user: user }
@@ -578,7 +589,7 @@ describe LaunchDarkly::EventProcessor do
578
589
  @diagnostic_payloads = Queue.new
579
590
  end
580
591
 
581
- def send_event_data(data, is_diagnostic)
592
+ def send_event_data(data, description, is_diagnostic)
582
593
  (is_diagnostic ? @diagnostic_payloads : @analytics_payloads).push(JSON.parse(data, symbolize_names: true))
583
594
  @result
584
595
  end
@@ -0,0 +1,123 @@
1
+ require "http_util"
2
+ require "spec_helper"
3
+
4
+
5
+ SDK_KEY = "sdk-key"
6
+
7
+ USER = { key: 'userkey' }
8
+
9
+ ALWAYS_TRUE_FLAG = { key: 'flagkey', version: 1, on: false, offVariation: 1, variations: [ false, true ] }
10
+ DATA_WITH_ALWAYS_TRUE_FLAG = {
11
+ flags: { ALWAYS_TRUE_FLAG[:key ].to_sym => ALWAYS_TRUE_FLAG },
12
+ segments: {}
13
+ }
14
+ PUT_EVENT_WITH_ALWAYS_TRUE_FLAG = "event: put\ndata:{\"data\":#{DATA_WITH_ALWAYS_TRUE_FLAG.to_json}}\n\n'"
15
+
16
+ def with_client(config)
17
+ client = LaunchDarkly::LDClient.new(SDK_KEY, config)
18
+ begin
19
+ yield client
20
+ ensure
21
+ client.close
22
+ end
23
+ end
24
+
25
+ module LaunchDarkly
26
+ # Note that we can't do end-to-end tests in streaming mode until we have a test server that can do streaming
27
+ # responses, which is difficult in WEBrick.
28
+
29
+ describe "LDClient end-to-end" do
30
+ it "starts in polling mode" do
31
+ with_server do |poll_server|
32
+ poll_server.setup_ok_response("/sdk/latest-all", DATA_WITH_ALWAYS_TRUE_FLAG.to_json, "application/json")
33
+
34
+ config = Config.new(
35
+ stream: false,
36
+ base_uri: poll_server.base_uri.to_s,
37
+ send_events: false,
38
+ logger: NullLogger.new
39
+ )
40
+ with_client(config) do |client|
41
+ expect(client.initialized?).to be true
42
+ expect(client.variation(ALWAYS_TRUE_FLAG[:key], USER, false)).to be true
43
+ end
44
+ end
45
+ end
46
+
47
+ it "fails in polling mode with 401 error" do
48
+ with_server do |poll_server|
49
+ poll_server.setup_status_response("/sdk/latest-all", 401)
50
+
51
+ config = Config.new(
52
+ stream: false,
53
+ base_uri: poll_server.base_uri.to_s,
54
+ send_events: false,
55
+ logger: NullLogger.new
56
+ )
57
+ with_client(config) do |client|
58
+ expect(client.initialized?).to be false
59
+ expect(client.variation(ALWAYS_TRUE_FLAG[:key], USER, false)).to be false
60
+ end
61
+ end
62
+ end
63
+
64
+ it "sends event without diagnostics" do
65
+ with_server do |poll_server|
66
+ with_server do |events_server|
67
+ events_server.setup_ok_response("/bulk", "")
68
+ poll_server.setup_ok_response("/sdk/latest-all", '{"flags":{},"segments":{}}', "application/json")
69
+
70
+ config = Config.new(
71
+ stream: false,
72
+ base_uri: poll_server.base_uri.to_s,
73
+ events_uri: events_server.base_uri.to_s,
74
+ diagnostic_opt_out: true,
75
+ logger: NullLogger.new
76
+ )
77
+ with_client(config) do |client|
78
+ client.identify(USER)
79
+ client.flush
80
+
81
+ req, body = events_server.await_request_with_body
82
+ expect(req.header['authorization']).to eq [ SDK_KEY ]
83
+ data = JSON.parse(body)
84
+ expect(data.length).to eq 1
85
+ expect(data[0]["kind"]).to eq "identify"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ it "sends diagnostic event" do
92
+ with_server do |poll_server|
93
+ with_server do |events_server|
94
+ events_server.setup_ok_response("/bulk", "")
95
+ events_server.setup_ok_response("/diagnostic", "")
96
+ poll_server.setup_ok_response("/sdk/latest-all", '{"flags":{},"segments":{}}', "application/json")
97
+
98
+ config = Config.new(
99
+ stream: false,
100
+ base_uri: poll_server.base_uri.to_s,
101
+ events_uri: events_server.base_uri.to_s,
102
+ logger: NullLogger.new
103
+ )
104
+ with_client(config) do |client|
105
+ user = { key: 'userkey' }
106
+ client.identify(user)
107
+ client.flush
108
+
109
+ req0, body0 = events_server.await_request_with_body
110
+ req1, body1 = events_server.await_request_with_body
111
+ req = req0.path == "/diagnostic" ? req0 : req1
112
+ body = req0.path == "/diagnostic" ? body0 : body1
113
+ expect(req.header['authorization']).to eq [ SDK_KEY ]
114
+ data = JSON.parse(body)
115
+ expect(data["kind"]).to eq "diagnostic-init"
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ # TODO: TLS tests with self-signed cert
122
+ end
123
+ end
@@ -49,6 +49,46 @@ describe LaunchDarkly::LDClient do
49
49
  client.instance_variable_get(:@event_processor)
50
50
  end
51
51
 
52
+ describe "constructor requirement of non-nil sdk key" do
53
+ it "is not enforced when offline" do
54
+ subject.new(nil, offline_config)
55
+ end
56
+
57
+ it "is not enforced if use_ldd is true and send_events is false" do
58
+ subject.new(nil, LaunchDarkly::Config.new({ use_ldd: true, send_events: false }))
59
+ end
60
+
61
+ it "is not enforced if using file data and send_events is false" do
62
+ source = LaunchDarkly::FileDataSource.factory({})
63
+ subject.new(nil, LaunchDarkly::Config.new({ data_source: source, send_events: false }))
64
+ end
65
+
66
+ it "is enforced in streaming mode even if send_events is false" do
67
+ expect {
68
+ subject.new(nil, LaunchDarkly::Config.new({ send_events: false }))
69
+ }.to raise_error(ArgumentError)
70
+ end
71
+
72
+ it "is enforced in polling mode even if send_events is false" do
73
+ expect {
74
+ subject.new(nil, LaunchDarkly::Config.new({ stream: false, send_events: false }))
75
+ }.to raise_error(ArgumentError)
76
+ end
77
+
78
+ it "is enforced if use_ldd is true and send_events is true" do
79
+ expect {
80
+ subject.new(nil, LaunchDarkly::Config.new({ use_ldd: true }))
81
+ }.to raise_error(ArgumentError)
82
+ end
83
+
84
+ it "is enforced if using file data and send_events is true" do
85
+ source = LaunchDarkly::FileDataSource.factory({})
86
+ expect {
87
+ subject.new(nil, LaunchDarkly::Config.new({ data_source: source }))
88
+ }.to raise_error(ArgumentError)
89
+ end
90
+ end
91
+
52
92
  describe '#variation' do
53
93
  feature_with_value = { key: "key", on: false, offVariation: 0, variations: ["value"], version: 100,
54
94
  trackEvents: true, debugEventsUntilDate: 1000 }
@@ -1,3 +1,4 @@
1
+ require "connection_pool"
1
2
  require "feature_store_spec_base"
2
3
  require "json"
3
4
  require "redis"
@@ -27,11 +28,11 @@ end
27
28
 
28
29
  describe LaunchDarkly::RedisFeatureStore do
29
30
  subject { LaunchDarkly::RedisFeatureStore }
30
-
31
+
31
32
  break if ENV['LD_SKIP_DATABASE_TESTS'] == '1'
32
33
 
33
34
  # These tests will all fail if there isn't a Redis instance running on the default port.
34
-
35
+
35
36
  context "real Redis with local cache" do
36
37
  include_examples "feature_store", method(:create_redis_store), method(:clear_all_data)
37
38
  end
@@ -59,7 +60,7 @@ describe LaunchDarkly::RedisFeatureStore do
59
60
  flag = { key: "foo", version: 1 }
60
61
  test_hook = make_concurrent_modifier_test_hook(other_client, flag, 2, 4)
61
62
  store = create_redis_store({ test_hook: test_hook })
62
-
63
+
63
64
  begin
64
65
  store.init(LaunchDarkly::FEATURES => { flag[:key] => flag })
65
66
 
@@ -77,7 +78,7 @@ describe LaunchDarkly::RedisFeatureStore do
77
78
  flag = { key: "foo", version: 1 }
78
79
  test_hook = make_concurrent_modifier_test_hook(other_client, flag, 3, 3)
79
80
  store = create_redis_store({ test_hook: test_hook })
80
-
81
+
81
82
  begin
82
83
  store.init(LaunchDarkly::FEATURES => { flag[:key] => flag })
83
84
 
@@ -89,4 +90,32 @@ describe LaunchDarkly::RedisFeatureStore do
89
90
  other_client.close
90
91
  end
91
92
  end
93
+
94
+ it "shuts down a custom Redis pool by default" do
95
+ unowned_pool = ConnectionPool.new(size: 1, timeout: 1) { Redis.new({ url: "redis://localhost:6379" }) }
96
+ store = create_redis_store({ pool: unowned_pool })
97
+
98
+ begin
99
+ store.init(LaunchDarkly::FEATURES => { })
100
+ store.stop
101
+
102
+ expect { unowned_pool.with {} }.to raise_error(ConnectionPool::PoolShuttingDownError)
103
+ ensure
104
+ unowned_pool.shutdown { |conn| conn.close }
105
+ end
106
+ end
107
+
108
+ it "doesn't shut down a custom Redis pool if pool_shutdown_on_close = false" do
109
+ unowned_pool = ConnectionPool.new(size: 1, timeout: 1) { Redis.new({ url: "redis://localhost:6379" }) }
110
+ store = create_redis_store({ pool: unowned_pool, pool_shutdown_on_close: false })
111
+
112
+ begin
113
+ store.init(LaunchDarkly::FEATURES => { })
114
+ store.stop
115
+
116
+ expect { unowned_pool.with {} }.not_to raise_error(ConnectionPool::PoolShuttingDownError)
117
+ ensure
118
+ unowned_pool.shutdown { |conn| conn.close }
119
+ end
120
+ end
92
121
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.7.0
4
+ version: 5.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-10 00:00:00.000000000 Z
11
+ date: 2020-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -204,20 +204,19 @@ dependencies:
204
204
  requirements:
205
205
  - - '='
206
206
  - !ruby/object:Gem::Version
207
- version: 1.0.2
207
+ version: 1.0.3
208
208
  type: :runtime
209
209
  prerelease: false
210
210
  version_requirements: !ruby/object:Gem::Requirement
211
211
  requirements:
212
212
  - - '='
213
213
  - !ruby/object:Gem::Version
214
- version: 1.0.2
214
+ version: 1.0.3
215
215
  description: Official LaunchDarkly SDK for Ruby
216
216
  email:
217
217
  - team@launchdarkly.com
218
218
  executables: []
219
- extensions:
220
- - ext/mkrf_conf.rb
219
+ extensions: []
221
220
  extra_rdoc_files: []
222
221
  files:
223
222
  - ".circleci/config.yml"
@@ -239,7 +238,6 @@ files:
239
238
  - LICENSE.txt
240
239
  - README.md
241
240
  - azure-pipelines.yml
242
- - ext/mkrf_conf.rb
243
241
  - launchdarkly-server-sdk.gemspec
244
242
  - lib/launchdarkly-server-sdk.rb
245
243
  - lib/ldclient-rb.rb
@@ -302,6 +300,7 @@ files:
302
300
  - spec/integrations/store_wrapper_spec.rb
303
301
  - spec/launchdarkly-server-sdk_spec.rb
304
302
  - spec/launchdarkly-server-sdk_spec_autoloadtest.rb
303
+ - spec/ldclient_end_to_end_spec.rb
305
304
  - spec/ldclient_spec.rb
306
305
  - spec/newrelic_spec.rb
307
306
  - spec/polling_spec.rb
@@ -360,6 +359,7 @@ test_files:
360
359
  - spec/integrations/store_wrapper_spec.rb
361
360
  - spec/launchdarkly-server-sdk_spec.rb
362
361
  - spec/launchdarkly-server-sdk_spec_autoloadtest.rb
362
+ - spec/ldclient_end_to_end_spec.rb
363
363
  - spec/ldclient_spec.rb
364
364
  - spec/newrelic_spec.rb
365
365
  - spec/polling_spec.rb
@@ -1,11 +0,0 @@
1
- require "rubygems"
2
-
3
-
4
- # From http://stackoverflow.com/questions/5830835/how-to-add-openssl-dependency-to-gemspec
5
- # the whole reason this file exists: to return an error if openssl
6
- # isn't installed.
7
- require "openssl"
8
-
9
- f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w") # create dummy rakefile to indicate success
10
- f.write("task :default\n")
11
- f.close