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 +4 -4
- data/CHANGELOG.md +29 -0
- data/Gemfile.lock +3 -3
- data/launchdarkly-server-sdk.gemspec +1 -2
- data/lib/ldclient-rb/evaluation.rb +17 -8
- data/lib/ldclient-rb/events.rb +3 -2
- data/lib/ldclient-rb/impl/event_sender.rb +2 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +3 -0
- data/lib/ldclient-rb/integrations/redis.rb +3 -0
- data/lib/ldclient-rb/ldclient.rb +12 -1
- data/lib/ldclient-rb/redis_store.rb +1 -0
- data/lib/ldclient-rb/user_filter.rb +3 -2
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/evaluation_spec.rb +6 -6
- data/spec/event_sender_spec.rb +8 -8
- data/spec/events_spec.rb +12 -1
- data/spec/ldclient_end_to_end_spec.rb +123 -0
- data/spec/ldclient_spec.rb +40 -0
- data/spec/redis_feature_store_spec.rb +33 -4
- metadata +7 -7
- data/ext/mkrf_conf.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 70c964dad6b0915f006bee0d90160fe04bdd4853
|
4
|
+
data.tar.gz: f80c3dfe18ad4a70c0d7156ab06b748ffc897ae9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a31c4b4a77caec7e64b54b18aabdecb12f2b5a44432d4ef74c94c2a1b57dff8a4cbf38f8dc894be8fec6f999bdc8bb62ff6095d1670ac892969329c435c9ff7
|
7
|
+
data.tar.gz: 2e21c5ad0cb881d99c905eaefc8952024d8fc12a1f28b1b2f4100cb39a95cdfa65d77b4a2067f3843ca9c79a446e6b071dd9c2470f2d0d0afba64d86365eb6cd
|
data/CHANGELOG.md
CHANGED
@@ -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.
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
launchdarkly-server-sdk (5.
|
4
|
+
launchdarkly-server-sdk (5.8.0)
|
5
5
|
concurrent-ruby (~> 1.0)
|
6
6
|
json (>= 1.8, < 3)
|
7
|
-
ld-eventsource (= 1.0.
|
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.
|
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.
|
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) &&
|
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 },
|
data/lib/ldclient-rb/events.rb
CHANGED
@@ -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}: #{
|
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)
|
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -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
|
-
|
19
|
-
|
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
|
|
data/lib/ldclient-rb/version.rb
CHANGED
data/spec/evaluation_spec.rb
CHANGED
@@ -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
|
-
|
499
|
-
|
500
|
-
|
498
|
+
[ :contains, "99", 99, false ],
|
499
|
+
[ :startsWith, "99", 99, false ],
|
500
|
+
[ :endsWith, "99", 99, false ],
|
501
501
|
[ :lessThanOrEqual, "99", 99, false ],
|
502
|
-
|
502
|
+
[ :lessThanOrEqual, 99, "99", false ],
|
503
503
|
[ :greaterThanOrEqual, "99", 99, false ],
|
504
|
-
|
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
|
-
|
512
|
+
[ :matches, "hello world", "***not a regex", false ],
|
513
513
|
|
514
514
|
# dates
|
515
515
|
[ :before, dateStr1, dateStr2, true ],
|
data/spec/event_sender_spec.rb
CHANGED
@@ -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
|
data/spec/events_spec.rb
CHANGED
@@ -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
|
data/spec/ldclient_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
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.
|
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
|
data/ext/mkrf_conf.rb
DELETED
@@ -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
|