launchdarkly-server-sdk 6.2.3 → 6.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/lib/ldclient-rb/config.rb +81 -4
  4. data/lib/ldclient-rb/evaluation_detail.rb +67 -8
  5. data/lib/ldclient-rb/file_data_source.rb +9 -300
  6. data/lib/ldclient-rb/impl/big_segments.rb +117 -0
  7. data/lib/ldclient-rb/impl/evaluator.rb +80 -28
  8. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
  9. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  10. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
  11. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  12. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  13. data/lib/ldclient-rb/impl/util.rb +4 -1
  14. data/lib/ldclient-rb/integrations/consul.rb +7 -0
  15. data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
  16. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  17. data/lib/ldclient-rb/integrations/redis.rb +41 -1
  18. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
  19. data/lib/ldclient-rb/integrations/test_data.rb +209 -0
  20. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
  21. data/lib/ldclient-rb/integrations.rb +2 -51
  22. data/lib/ldclient-rb/interfaces.rb +152 -2
  23. data/lib/ldclient-rb/ldclient.rb +21 -7
  24. data/lib/ldclient-rb/polling.rb +22 -41
  25. data/lib/ldclient-rb/util.rb +1 -1
  26. data/lib/ldclient-rb/version.rb +1 -1
  27. metadata +31 -132
  28. data/.circleci/config.yml +0 -40
  29. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
  30. data/.github/ISSUE_TEMPLATE/config.yml +0 -5
  31. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  32. data/.github/pull_request_template.md +0 -21
  33. data/.gitignore +0 -16
  34. data/.hound.yml +0 -2
  35. data/.ldrelease/build-docs.sh +0 -18
  36. data/.ldrelease/circleci/linux/execute.sh +0 -18
  37. data/.ldrelease/circleci/mac/execute.sh +0 -18
  38. data/.ldrelease/circleci/template/build.sh +0 -29
  39. data/.ldrelease/circleci/template/publish.sh +0 -23
  40. data/.ldrelease/circleci/template/set-gem-home.sh +0 -7
  41. data/.ldrelease/circleci/template/test.sh +0 -10
  42. data/.ldrelease/circleci/template/update-version.sh +0 -8
  43. data/.ldrelease/circleci/windows/execute.ps1 +0 -19
  44. data/.ldrelease/config.yml +0 -29
  45. data/.rspec +0 -2
  46. data/.rubocop.yml +0 -600
  47. data/.simplecov +0 -4
  48. data/CHANGELOG.md +0 -367
  49. data/CODEOWNERS +0 -1
  50. data/CONTRIBUTING.md +0 -37
  51. data/Gemfile +0 -3
  52. data/azure-pipelines.yml +0 -51
  53. data/docs/Makefile +0 -26
  54. data/docs/index.md +0 -9
  55. data/launchdarkly-server-sdk.gemspec +0 -45
  56. data/spec/config_spec.rb +0 -63
  57. data/spec/diagnostic_events_spec.rb +0 -165
  58. data/spec/evaluation_detail_spec.rb +0 -135
  59. data/spec/event_sender_spec.rb +0 -197
  60. data/spec/event_summarizer_spec.rb +0 -63
  61. data/spec/events_spec.rb +0 -607
  62. data/spec/expiring_cache_spec.rb +0 -76
  63. data/spec/feature_store_spec_base.rb +0 -213
  64. data/spec/file_data_source_spec.rb +0 -283
  65. data/spec/fixtures/feature.json +0 -37
  66. data/spec/fixtures/feature1.json +0 -36
  67. data/spec/fixtures/user.json +0 -9
  68. data/spec/flags_state_spec.rb +0 -81
  69. data/spec/http_util.rb +0 -132
  70. data/spec/impl/evaluator_bucketing_spec.rb +0 -216
  71. data/spec/impl/evaluator_clause_spec.rb +0 -55
  72. data/spec/impl/evaluator_operators_spec.rb +0 -141
  73. data/spec/impl/evaluator_rule_spec.rb +0 -128
  74. data/spec/impl/evaluator_segment_spec.rb +0 -125
  75. data/spec/impl/evaluator_spec.rb +0 -349
  76. data/spec/impl/evaluator_spec_base.rb +0 -75
  77. data/spec/impl/event_factory_spec.rb +0 -108
  78. data/spec/impl/model/serialization_spec.rb +0 -41
  79. data/spec/in_memory_feature_store_spec.rb +0 -12
  80. data/spec/integrations/consul_feature_store_spec.rb +0 -40
  81. data/spec/integrations/dynamodb_feature_store_spec.rb +0 -103
  82. data/spec/integrations/store_wrapper_spec.rb +0 -276
  83. data/spec/launchdarkly-server-sdk_spec.rb +0 -13
  84. data/spec/launchdarkly-server-sdk_spec_autoloadtest.rb +0 -9
  85. data/spec/ldclient_end_to_end_spec.rb +0 -157
  86. data/spec/ldclient_spec.rb +0 -635
  87. data/spec/newrelic_spec.rb +0 -5
  88. data/spec/polling_spec.rb +0 -120
  89. data/spec/redis_feature_store_spec.rb +0 -121
  90. data/spec/requestor_spec.rb +0 -209
  91. data/spec/segment_store_spec_base.rb +0 -95
  92. data/spec/simple_lru_cache_spec.rb +0 -24
  93. data/spec/spec_helper.rb +0 -9
  94. data/spec/store_spec.rb +0 -10
  95. data/spec/stream_spec.rb +0 -45
  96. data/spec/user_filter_spec.rb +0 -91
  97. data/spec/util_spec.rb +0 -17
  98. data/spec/version_spec.rb +0 -7
data/spec/http_util.rb DELETED
@@ -1,132 +0,0 @@
1
- require "webrick"
2
- require "webrick/httpproxy"
3
- require "webrick/https"
4
-
5
- class StubHTTPServer
6
- attr_reader :requests, :port
7
-
8
- @@next_port = 50000
9
-
10
- def initialize
11
- @port = StubHTTPServer.next_port
12
- begin
13
- base_opts = {
14
- BindAddress: '127.0.0.1',
15
- Port: @port,
16
- AccessLog: [],
17
- Logger: NullLogger.new,
18
- RequestCallback: method(:record_request)
19
- }
20
- @server = create_server(@port, base_opts)
21
- rescue Errno::EADDRINUSE
22
- @port = StubHTTPServer.next_port
23
- retry
24
- end
25
- @requests = []
26
- @requests_queue = Queue.new
27
- end
28
-
29
- def self.next_port
30
- p = @@next_port
31
- @@next_port = (p + 1 < 60000) ? p + 1 : 50000
32
- p
33
- end
34
-
35
- def create_server(port, base_opts)
36
- WEBrick::HTTPServer.new(base_opts)
37
- end
38
-
39
- def start
40
- Thread.new { @server.start }
41
- end
42
-
43
- def stop
44
- @server.shutdown
45
- end
46
-
47
- def base_uri
48
- URI("http://127.0.0.1:#{@port}")
49
- end
50
-
51
- def setup_response(uri_path, &action)
52
- @server.mount_proc(uri_path, action)
53
- end
54
-
55
- def setup_status_response(uri_path, status, headers={})
56
- setup_response(uri_path) do |req, res|
57
- res.status = status
58
- headers.each { |n, v| res[n] = v }
59
- end
60
- end
61
-
62
- def setup_ok_response(uri_path, body, content_type=nil, headers={})
63
- setup_response(uri_path) do |req, res|
64
- res.status = 200
65
- res.content_type = content_type if !content_type.nil?
66
- res.body = body
67
- headers.each { |n, v| res[n] = v }
68
- end
69
- end
70
-
71
- def record_request(req, res)
72
- @requests.push(req)
73
- @requests_queue << [req, req.body]
74
- end
75
-
76
- def await_request
77
- r = @requests_queue.pop
78
- r[0]
79
- end
80
-
81
- def await_request_with_body
82
- r = @requests_queue.pop
83
- return r[0], r[1]
84
- end
85
- end
86
-
87
- class StubProxyServer < StubHTTPServer
88
- attr_reader :request_count
89
- attr_accessor :connect_status
90
-
91
- def initialize
92
- super
93
- @request_count = 0
94
- end
95
-
96
- def create_server(port, base_opts)
97
- WEBrick::HTTPProxyServer.new(base_opts.merge({
98
- ProxyContentHandler: proc do |req,res|
99
- if !@connect_status.nil?
100
- res.status = @connect_status
101
- end
102
- @request_count += 1
103
- end
104
- }))
105
- end
106
- end
107
-
108
- class NullLogger
109
- def method_missing(*)
110
- self
111
- end
112
- end
113
-
114
- def with_server(server = nil)
115
- server = StubHTTPServer.new if server.nil?
116
- begin
117
- server.start
118
- yield server
119
- ensure
120
- server.stop
121
- end
122
- end
123
-
124
- class SocketFactoryFromHash
125
- def initialize(ports = {})
126
- @ports = ports
127
- end
128
-
129
- def open(uri, timeout)
130
- TCPSocket.new 'localhost', @ports[uri]
131
- end
132
- end
@@ -1,216 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe LaunchDarkly::Impl::EvaluatorBucketing do
4
- subject { LaunchDarkly::Impl::EvaluatorBucketing }
5
-
6
- describe "bucket_user" do
7
- describe "seed exists" do
8
- let(:seed) { 61 }
9
- it "returns the expected bucket values for seed" do
10
- user = { key: "userKeyA" }
11
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", seed)
12
- expect(bucket).to be_within(0.0000001).of(0.09801207);
13
-
14
- user = { key: "userKeyB" }
15
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", seed)
16
- expect(bucket).to be_within(0.0000001).of(0.14483777);
17
-
18
- user = { key: "userKeyC" }
19
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", seed)
20
- expect(bucket).to be_within(0.0000001).of(0.9242641);
21
- end
22
-
23
- it "returns the same bucket regardless of hashKey and salt" do
24
- user = { key: "userKeyA" }
25
- bucket1 = subject.bucket_user(user, "hashKey", "key", "saltyA", seed)
26
- bucket2 = subject.bucket_user(user, "hashKey1", "key", "saltyB", seed)
27
- bucket3 = subject.bucket_user(user, "hashKey2", "key", "saltyC", seed)
28
- expect(bucket1).to eq(bucket2)
29
- expect(bucket2).to eq(bucket3)
30
- end
31
-
32
- it "returns a different bucket if the seed is not the same" do
33
- user = { key: "userKeyA" }
34
- bucket1 = subject.bucket_user(user, "hashKey", "key", "saltyA", seed)
35
- bucket2 = subject.bucket_user(user, "hashKey1", "key", "saltyB", seed+1)
36
- expect(bucket1).to_not eq(bucket2)
37
- end
38
-
39
- it "returns a different bucket if the user is not the same" do
40
- user1 = { key: "userKeyA" }
41
- user2 = { key: "userKeyB" }
42
- bucket1 = subject.bucket_user(user1, "hashKey", "key", "saltyA", seed)
43
- bucket2 = subject.bucket_user(user2, "hashKey1", "key", "saltyB", seed)
44
- expect(bucket1).to_not eq(bucket2)
45
- end
46
- end
47
-
48
- it "gets expected bucket values for specific keys" do
49
- user = { key: "userKeyA" }
50
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", nil)
51
- expect(bucket).to be_within(0.0000001).of(0.42157587);
52
-
53
- user = { key: "userKeyB" }
54
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", nil)
55
- expect(bucket).to be_within(0.0000001).of(0.6708485);
56
-
57
- user = { key: "userKeyC" }
58
- bucket = subject.bucket_user(user, "hashKey", "key", "saltyA", nil)
59
- expect(bucket).to be_within(0.0000001).of(0.10343106);
60
- end
61
-
62
- it "can bucket by int value (equivalent to string)" do
63
- user = {
64
- key: "userkey",
65
- custom: {
66
- stringAttr: "33333",
67
- intAttr: 33333
68
- }
69
- }
70
- stringResult = subject.bucket_user(user, "hashKey", "stringAttr", "saltyA", nil)
71
- intResult = subject.bucket_user(user, "hashKey", "intAttr", "saltyA", nil)
72
-
73
- expect(intResult).to be_within(0.0000001).of(0.54771423)
74
- expect(intResult).to eq(stringResult)
75
- end
76
-
77
- it "cannot bucket by float value" do
78
- user = {
79
- key: "userkey",
80
- custom: {
81
- floatAttr: 33.5
82
- }
83
- }
84
- result = subject.bucket_user(user, "hashKey", "floatAttr", "saltyA", nil)
85
- expect(result).to eq(0.0)
86
- end
87
-
88
-
89
- it "cannot bucket by bool value" do
90
- user = {
91
- key: "userkey",
92
- custom: {
93
- boolAttr: true
94
- }
95
- }
96
- result = subject.bucket_user(user, "hashKey", "boolAttr", "saltyA", nil)
97
- expect(result).to eq(0.0)
98
- end
99
- end
100
-
101
- describe "variation_index_for_user" do
102
- context "rollout is not an experiment" do
103
- it "matches bucket" do
104
- user = { key: "userkey" }
105
- flag_key = "flagkey"
106
- salt = "salt"
107
-
108
- # First verify that with our test inputs, the bucket value will be greater than zero and less than 100000,
109
- # so we can construct a rollout whose second bucket just barely contains that value
110
- bucket_value = (subject.bucket_user(user, flag_key, "key", salt, nil) * 100000).truncate()
111
- expect(bucket_value).to be > 0
112
- expect(bucket_value).to be < 100000
113
-
114
- bad_variation_a = 0
115
- matched_variation = 1
116
- bad_variation_b = 2
117
- rule = {
118
- rollout: {
119
- variations: [
120
- { variation: bad_variation_a, weight: bucket_value }, # end of bucket range is not inclusive, so it will *not* match the target value
121
- { variation: matched_variation, weight: 1 }, # size of this bucket is 1, so it only matches that specific value
122
- { variation: bad_variation_b, weight: 100000 - (bucket_value + 1) }
123
- ]
124
- }
125
- }
126
- flag = { key: flag_key, salt: salt }
127
-
128
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user)
129
- expect(result_variation).to be matched_variation
130
- expect(inExperiment).to be(false)
131
- end
132
-
133
- it "uses last bucket if bucket value is equal to total weight" do
134
- user = { key: "userkey" }
135
- flag_key = "flagkey"
136
- salt = "salt"
137
-
138
- bucket_value = (subject.bucket_user(user, flag_key, "key", salt, nil) * 100000).truncate()
139
-
140
- # We'll construct a list of variations that stops right at the target bucket value
141
- rule = {
142
- rollout: {
143
- variations: [
144
- { variation: 0, weight: bucket_value }
145
- ]
146
- }
147
- }
148
- flag = { key: flag_key, salt: salt }
149
-
150
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user)
151
- expect(result_variation).to be 0
152
- expect(inExperiment).to be(false)
153
- end
154
- end
155
- end
156
-
157
- context "rollout is an experiment" do
158
- it "returns whether user is in the experiment or not" do
159
- user1 = { key: "userKeyA" }
160
- user2 = { key: "userKeyB" }
161
- user3 = { key: "userKeyC" }
162
- flag_key = "flagkey"
163
- salt = "salt"
164
- seed = 61
165
-
166
-
167
- rule = {
168
- rollout: {
169
- seed: seed,
170
- kind: 'experiment',
171
- variations: [
172
- { variation: 0, weight: 10000, untracked: false },
173
- { variation: 2, weight: 20000, untracked: false },
174
- { variation: 0, weight: 70000 , untracked: true }
175
- ]
176
- }
177
- }
178
- flag = { key: flag_key, salt: salt }
179
-
180
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user1)
181
- expect(result_variation).to be(0)
182
- expect(inExperiment).to be(true)
183
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user2)
184
- expect(result_variation).to be(2)
185
- expect(inExperiment).to be(true)
186
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user3)
187
- expect(result_variation).to be(0)
188
- expect(inExperiment).to be(false)
189
- end
190
-
191
- it "uses last bucket if bucket value is equal to total weight" do
192
- user = { key: "userkey" }
193
- flag_key = "flagkey"
194
- salt = "salt"
195
- seed = 61
196
-
197
- bucket_value = (subject.bucket_user(user, flag_key, "key", salt, seed) * 100000).truncate()
198
-
199
- # We'll construct a list of variations that stops right at the target bucket value
200
- rule = {
201
- rollout: {
202
- seed: seed,
203
- kind: 'experiment',
204
- variations: [
205
- { variation: 0, weight: bucket_value, untracked: false }
206
- ]
207
- }
208
- }
209
- flag = { key: flag_key, salt: salt }
210
-
211
- result_variation, inExperiment = subject.variation_index_for_user(flag, rule, user)
212
- expect(result_variation).to be 0
213
- expect(inExperiment).to be(true)
214
- end
215
- end
216
- end
@@ -1,55 +0,0 @@
1
- require "spec_helper"
2
- require "impl/evaluator_spec_base"
3
-
4
- module LaunchDarkly
5
- module Impl
6
- describe "Evaluator (clauses)", :evaluator_spec_base => true do
7
- subject { Evaluator }
8
-
9
- it "can match built-in attribute" do
10
- user = { key: 'x', name: 'Bob' }
11
- clause = { attribute: 'name', op: 'in', values: ['Bob'] }
12
- flag = boolean_flag_with_clauses([clause])
13
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be true
14
- end
15
-
16
- it "can match custom attribute" do
17
- user = { key: 'x', name: 'Bob', custom: { legs: 4 } }
18
- clause = { attribute: 'legs', op: 'in', values: [4] }
19
- flag = boolean_flag_with_clauses([clause])
20
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be true
21
- end
22
-
23
- it "returns false for missing attribute" do
24
- user = { key: 'x', name: 'Bob' }
25
- clause = { attribute: 'legs', op: 'in', values: [4] }
26
- flag = boolean_flag_with_clauses([clause])
27
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be false
28
- end
29
-
30
- it "returns false for unknown operator" do
31
- user = { key: 'x', name: 'Bob' }
32
- clause = { attribute: 'name', op: 'unknown', values: [4] }
33
- flag = boolean_flag_with_clauses([clause])
34
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be false
35
- end
36
-
37
- it "does not stop evaluating rules after clause with unknown operator" do
38
- user = { key: 'x', name: 'Bob' }
39
- clause0 = { attribute: 'name', op: 'unknown', values: [4] }
40
- rule0 = { clauses: [ clause0 ], variation: 1 }
41
- clause1 = { attribute: 'name', op: 'in', values: ['Bob'] }
42
- rule1 = { clauses: [ clause1 ], variation: 1 }
43
- flag = boolean_flag_with_rules([rule0, rule1])
44
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be true
45
- end
46
-
47
- it "can be negated" do
48
- user = { key: 'x', name: 'Bob' }
49
- clause = { attribute: 'name', op: 'in', values: ['Bob'], negate: true }
50
- flag = boolean_flag_with_clauses([clause])
51
- expect(basic_evaluator.evaluate(flag, user, factory).detail.value).to be false
52
- end
53
- end
54
- end
55
- end
@@ -1,141 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe LaunchDarkly::Impl::EvaluatorOperators do
4
- subject { LaunchDarkly::Impl::EvaluatorOperators }
5
-
6
- describe "operators" do
7
- dateStr1 = "2017-12-06T00:00:00.000-07:00"
8
- dateStr2 = "2017-12-06T00:01:01.000-07:00"
9
- dateMs1 = 10000000
10
- dateMs2 = 10000001
11
- invalidDate = "hey what's this?"
12
-
13
- operatorTests = [
14
- # numeric comparisons
15
- [ :in, 99, 99, true ],
16
- [ :in, 99.0001, 99.0001, true ],
17
- [ :in, 99, 99.0001, false ],
18
- [ :in, 99.0001, 99, false ],
19
- [ :lessThan, 99, 99.0001, true ],
20
- [ :lessThan, 99.0001, 99, false ],
21
- [ :lessThan, 99, 99, false ],
22
- [ :lessThanOrEqual, 99, 99.0001, true ],
23
- [ :lessThanOrEqual, 99.0001, 99, false ],
24
- [ :lessThanOrEqual, 99, 99, true ],
25
- [ :greaterThan, 99.0001, 99, true ],
26
- [ :greaterThan, 99, 99.0001, false ],
27
- [ :greaterThan, 99, 99, false ],
28
- [ :greaterThanOrEqual, 99.0001, 99, true ],
29
- [ :greaterThanOrEqual, 99, 99.0001, false ],
30
- [ :greaterThanOrEqual, 99, 99, true ],
31
-
32
- # string comparisons
33
- [ :in, "x", "x", true ],
34
- [ :in, "x", "xyz", false ],
35
- [ :startsWith, "xyz", "x", true ],
36
- [ :startsWith, "x", "xyz", false ],
37
- [ :endsWith, "xyz", "z", true ],
38
- [ :endsWith, "z", "xyz", false ],
39
- [ :contains, "xyz", "y", true ],
40
- [ :contains, "y", "xyz", false ],
41
-
42
- # mixed strings and numbers
43
- [ :in, "99", 99, false ],
44
- [ :in, 99, "99", false ],
45
- [ :contains, "99", 99, false ],
46
- [ :startsWith, "99", 99, false ],
47
- [ :endsWith, "99", 99, false ],
48
- [ :lessThanOrEqual, "99", 99, false ],
49
- [ :lessThanOrEqual, 99, "99", false ],
50
- [ :greaterThanOrEqual, "99", 99, false ],
51
- [ :greaterThanOrEqual, 99, "99", false ],
52
-
53
- # regex
54
- [ :matches, "hello world", "hello.*rld", true ],
55
- [ :matches, "hello world", "hello.*orl", true ],
56
- [ :matches, "hello world", "l+", true ],
57
- [ :matches, "hello world", "(world|planet)", true ],
58
- [ :matches, "hello world", "aloha", false ],
59
- [ :matches, "hello world", "***not a regex", false ],
60
-
61
- # dates
62
- [ :before, dateStr1, dateStr2, true ],
63
- [ :before, dateMs1, dateMs2, true ],
64
- [ :before, dateStr2, dateStr1, false ],
65
- [ :before, dateMs2, dateMs1, false ],
66
- [ :before, dateStr1, dateStr1, false ],
67
- [ :before, dateMs1, dateMs1, false ],
68
- [ :before, dateStr1, invalidDate, false ],
69
- [ :after, dateStr1, dateStr2, false ],
70
- [ :after, dateMs1, dateMs2, false ],
71
- [ :after, dateStr2, dateStr1, true ],
72
- [ :after, dateMs2, dateMs1, true ],
73
- [ :after, dateStr1, dateStr1, false ],
74
- [ :after, dateMs1, dateMs1, false ],
75
- [ :after, dateStr1, invalidDate, false ],
76
-
77
- # semver
78
- [ :semVerEqual, "2.0.1", "2.0.1", true ],
79
- [ :semVerEqual, "2.0", "2.0.0", true ],
80
- [ :semVerEqual, "2-rc1", "2.0.0-rc1", true ],
81
- [ :semVerEqual, "2+build2", "2.0.0+build2", true ],
82
- [ :semVerLessThan, "2.0.0", "2.0.1", true ],
83
- [ :semVerLessThan, "2.0", "2.0.1", true ],
84
- [ :semVerLessThan, "2.0.1", "2.0.0", false ],
85
- [ :semVerLessThan, "2.0.1", "2.0", false ],
86
- [ :semVerLessThan, "2.0.0-rc", "2.0.0-rc.beta", true ],
87
- [ :semVerGreaterThan, "2.0.1", "2.0.0", true ],
88
- [ :semVerGreaterThan, "2.0.1", "2.0", true ],
89
- [ :semVerGreaterThan, "2.0.0", "2.0.1", false ],
90
- [ :semVerGreaterThan, "2.0", "2.0.1", false ],
91
- [ :semVerGreaterThan, "2.0.0-rc.1", "2.0.0-rc.0", true ],
92
- [ :semVerLessThan, "2.0.1", "xbad%ver", false ],
93
- [ :semVerGreaterThan, "2.0.1", "xbad%ver", false ]
94
- ]
95
-
96
- operatorTests.each do |params|
97
- op = params[0]
98
- value1 = params[1]
99
- value2 = params[2]
100
- shouldBe = params[3]
101
- it "should return #{shouldBe} for #{value1} #{op} #{value2}" do
102
- expect(subject::apply(op, value1, value2)).to be shouldBe
103
- end
104
- end
105
- end
106
-
107
- describe "user_value" do
108
- [:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name, :anonymous, :some_custom_attr].each do |attr|
109
- it "returns nil if property #{attr} is not defined" do
110
- expect(subject::user_value({}, attr)).to be nil
111
- end
112
- end
113
-
114
- [:key, :ip, :country, :email, :firstName, :lastName, :avatar, :name].each do |attr|
115
- it "gets string value of string property #{attr}" do
116
- expect(subject::user_value({ attr => 'x' }, attr)).to eq 'x'
117
- end
118
-
119
- it "coerces non-string value of property #{attr} to string" do
120
- expect(subject::user_value({ attr => 3 }, attr)).to eq '3'
121
- end
122
- end
123
-
124
- it "gets boolean value of property anonymous" do
125
- expect(subject::user_value({ anonymous: true }, :anonymous)).to be true
126
- expect(subject::user_value({ anonymous: false }, :anonymous)).to be false
127
- end
128
-
129
- it "does not coerces non-boolean value of property anonymous" do
130
- expect(subject::user_value({ anonymous: 3 }, :anonymous)).to eq 3
131
- end
132
-
133
- it "gets string value of custom property" do
134
- expect(subject::user_value({ custom: { some_custom_attr: 'x' } }, :some_custom_attr)).to eq 'x'
135
- end
136
-
137
- it "gets non-string value of custom property" do
138
- expect(subject::user_value({ custom: { some_custom_attr: 3 } }, :some_custom_attr)).to eq 3
139
- end
140
- end
141
- end
@@ -1,128 +0,0 @@
1
- require "spec_helper"
2
- require "impl/evaluator_spec_base"
3
-
4
- module LaunchDarkly
5
- module Impl
6
- describe "Evaluator (rules)", :evaluator_spec_base => true do
7
- subject { Evaluator }
8
-
9
- it "matches user from rules" do
10
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }], variation: 1 }
11
- flag = boolean_flag_with_rules([rule])
12
- user = { key: 'userkey' }
13
- detail = EvaluationDetail.new(true, 1, EvaluationReason::rule_match(0, 'ruleid'))
14
- result = basic_evaluator.evaluate(flag, user, factory)
15
- expect(result.detail).to eq(detail)
16
- expect(result.events).to eq(nil)
17
- end
18
-
19
- it "reuses rule match reason instances if possible" do
20
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }], variation: 1 }
21
- flag = boolean_flag_with_rules([rule])
22
- Model.postprocess_item_after_deserializing!(FEATURES, flag) # now there's a cached rule match reason
23
- user = { key: 'userkey' }
24
- detail = EvaluationDetail.new(true, 1, EvaluationReason::rule_match(0, 'ruleid'))
25
- result1 = basic_evaluator.evaluate(flag, user, factory)
26
- result2 = basic_evaluator.evaluate(flag, user, factory)
27
- expect(result1.detail.reason.rule_id).to eq 'ruleid'
28
- expect(result1.detail.reason).to be result2.detail.reason
29
- end
30
-
31
- it "returns an error if rule variation is too high" do
32
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }], variation: 999 }
33
- flag = boolean_flag_with_rules([rule])
34
- user = { key: 'userkey' }
35
- detail = EvaluationDetail.new(nil, nil,
36
- EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
37
- result = basic_evaluator.evaluate(flag, user, factory)
38
- expect(result.detail).to eq(detail)
39
- expect(result.events).to eq(nil)
40
- end
41
-
42
- it "returns an error if rule variation is negative" do
43
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }], variation: -1 }
44
- flag = boolean_flag_with_rules([rule])
45
- user = { key: 'userkey' }
46
- detail = EvaluationDetail.new(nil, nil,
47
- EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
48
- result = basic_evaluator.evaluate(flag, user, factory)
49
- expect(result.detail).to eq(detail)
50
- expect(result.events).to eq(nil)
51
- end
52
-
53
- it "returns an error if rule has neither variation nor rollout" do
54
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }] }
55
- flag = boolean_flag_with_rules([rule])
56
- user = { key: 'userkey' }
57
- detail = EvaluationDetail.new(nil, nil,
58
- EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
59
- result = basic_evaluator.evaluate(flag, user, factory)
60
- expect(result.detail).to eq(detail)
61
- expect(result.events).to eq(nil)
62
- end
63
-
64
- it "returns an error if rule has a rollout with no variations" do
65
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
66
- rollout: { variations: [] } }
67
- flag = boolean_flag_with_rules([rule])
68
- user = { key: 'userkey' }
69
- detail = EvaluationDetail.new(nil, nil,
70
- EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
71
- result = basic_evaluator.evaluate(flag, user, factory)
72
- expect(result.detail).to eq(detail)
73
- expect(result.events).to eq(nil)
74
- end
75
-
76
- it "coerces user key to a string for evaluation" do
77
- clause = { attribute: 'key', op: 'in', values: ['999'] }
78
- flag = boolean_flag_with_clauses([clause])
79
- user = { key: 999 }
80
- result = basic_evaluator.evaluate(flag, user, factory)
81
- expect(result.detail.value).to eq(true)
82
- end
83
-
84
- it "coerces secondary key to a string for evaluation" do
85
- # We can't really verify that the rollout calculation works correctly, but we can at least
86
- # make sure it doesn't error out if there's a non-string secondary value (ch35189)
87
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
88
- rollout: { salt: '', variations: [ { weight: 100000, variation: 1 } ] } }
89
- flag = boolean_flag_with_rules([rule])
90
- user = { key: "userkey", secondary: 999 }
91
- result = basic_evaluator.evaluate(flag, user, factory)
92
- expect(result.detail.reason).to eq(EvaluationReason::rule_match(0, 'ruleid'))
93
- end
94
-
95
- describe "experiment rollout behavior" do
96
- it "sets the in_experiment value if rollout kind is experiment " do
97
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
98
- rollout: { kind: 'experiment', variations: [ { weight: 100000, variation: 1, untracked: false } ] } }
99
- flag = boolean_flag_with_rules([rule])
100
- user = { key: "userkey", secondary: 999 }
101
- result = basic_evaluator.evaluate(flag, user, factory)
102
- expect(result.detail.reason.to_json).to include('"inExperiment":true')
103
- expect(result.detail.reason.in_experiment).to eq(true)
104
- end
105
-
106
- it "does not set the in_experiment value if rollout kind is not experiment " do
107
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
108
- rollout: { kind: 'rollout', variations: [ { weight: 100000, variation: 1, untracked: false } ] } }
109
- flag = boolean_flag_with_rules([rule])
110
- user = { key: "userkey", secondary: 999 }
111
- result = basic_evaluator.evaluate(flag, user, factory)
112
- expect(result.detail.reason.to_json).to_not include('"inExperiment":true')
113
- expect(result.detail.reason.in_experiment).to eq(nil)
114
- end
115
-
116
- it "does not set the in_experiment value if rollout kind is experiment and untracked is true" do
117
- rule = { id: 'ruleid', clauses: [{ attribute: 'key', op: 'in', values: ['userkey'] }],
118
- rollout: { kind: 'experiment', variations: [ { weight: 100000, variation: 1, untracked: true } ] } }
119
- flag = boolean_flag_with_rules([rule])
120
- user = { key: "userkey", secondary: 999 }
121
- result = basic_evaluator.evaluate(flag, user, factory)
122
- expect(result.detail.reason.to_json).to_not include('"inExperiment":true')
123
- expect(result.detail.reason.in_experiment).to eq(nil)
124
- end
125
- end
126
- end
127
- end
128
- end