launchdarkly-server-sdk 6.2.2 → 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) 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/diagnostic_events.rb +1 -1
  8. data/lib/ldclient-rb/impl/evaluator.rb +80 -28
  9. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +82 -18
  10. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
  11. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +84 -31
  12. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
  13. data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
  14. data/lib/ldclient-rb/impl/util.rb +4 -1
  15. data/lib/ldclient-rb/integrations/consul.rb +7 -0
  16. data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
  17. data/lib/ldclient-rb/integrations/file_data.rb +108 -0
  18. data/lib/ldclient-rb/integrations/redis.rb +41 -1
  19. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +438 -0
  20. data/lib/ldclient-rb/integrations/test_data.rb +209 -0
  21. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +5 -0
  22. data/lib/ldclient-rb/integrations.rb +2 -51
  23. data/lib/ldclient-rb/interfaces.rb +152 -2
  24. data/lib/ldclient-rb/ldclient.rb +21 -7
  25. data/lib/ldclient-rb/polling.rb +22 -41
  26. data/lib/ldclient-rb/util.rb +1 -1
  27. data/lib/ldclient-rb/version.rb +1 -1
  28. metadata +31 -132
  29. data/.circleci/config.yml +0 -40
  30. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -37
  31. data/.github/ISSUE_TEMPLATE/config.yml +0 -5
  32. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  33. data/.github/pull_request_template.md +0 -21
  34. data/.gitignore +0 -16
  35. data/.hound.yml +0 -2
  36. data/.ldrelease/build-docs.sh +0 -18
  37. data/.ldrelease/circleci/linux/execute.sh +0 -18
  38. data/.ldrelease/circleci/mac/execute.sh +0 -18
  39. data/.ldrelease/circleci/template/build.sh +0 -29
  40. data/.ldrelease/circleci/template/publish.sh +0 -23
  41. data/.ldrelease/circleci/template/set-gem-home.sh +0 -7
  42. data/.ldrelease/circleci/template/test.sh +0 -10
  43. data/.ldrelease/circleci/template/update-version.sh +0 -8
  44. data/.ldrelease/circleci/windows/execute.ps1 +0 -19
  45. data/.ldrelease/config.yml +0 -29
  46. data/.rspec +0 -2
  47. data/.rubocop.yml +0 -600
  48. data/.simplecov +0 -4
  49. data/CHANGELOG.md +0 -363
  50. data/CODEOWNERS +0 -1
  51. data/CONTRIBUTING.md +0 -37
  52. data/Gemfile +0 -3
  53. data/azure-pipelines.yml +0 -51
  54. data/docs/Makefile +0 -26
  55. data/docs/index.md +0 -9
  56. data/launchdarkly-server-sdk.gemspec +0 -45
  57. data/spec/config_spec.rb +0 -63
  58. data/spec/diagnostic_events_spec.rb +0 -163
  59. data/spec/evaluation_detail_spec.rb +0 -135
  60. data/spec/event_sender_spec.rb +0 -197
  61. data/spec/event_summarizer_spec.rb +0 -63
  62. data/spec/events_spec.rb +0 -607
  63. data/spec/expiring_cache_spec.rb +0 -76
  64. data/spec/feature_store_spec_base.rb +0 -213
  65. data/spec/file_data_source_spec.rb +0 -283
  66. data/spec/fixtures/feature.json +0 -37
  67. data/spec/fixtures/feature1.json +0 -36
  68. data/spec/fixtures/user.json +0 -9
  69. data/spec/flags_state_spec.rb +0 -81
  70. data/spec/http_util.rb +0 -132
  71. data/spec/impl/evaluator_bucketing_spec.rb +0 -216
  72. data/spec/impl/evaluator_clause_spec.rb +0 -55
  73. data/spec/impl/evaluator_operators_spec.rb +0 -141
  74. data/spec/impl/evaluator_rule_spec.rb +0 -128
  75. data/spec/impl/evaluator_segment_spec.rb +0 -125
  76. data/spec/impl/evaluator_spec.rb +0 -349
  77. data/spec/impl/evaluator_spec_base.rb +0 -75
  78. data/spec/impl/event_factory_spec.rb +0 -108
  79. data/spec/impl/model/serialization_spec.rb +0 -41
  80. data/spec/in_memory_feature_store_spec.rb +0 -12
  81. data/spec/integrations/consul_feature_store_spec.rb +0 -40
  82. data/spec/integrations/dynamodb_feature_store_spec.rb +0 -103
  83. data/spec/integrations/store_wrapper_spec.rb +0 -276
  84. data/spec/launchdarkly-server-sdk_spec.rb +0 -13
  85. data/spec/launchdarkly-server-sdk_spec_autoloadtest.rb +0 -9
  86. data/spec/ldclient_end_to_end_spec.rb +0 -157
  87. data/spec/ldclient_spec.rb +0 -635
  88. data/spec/newrelic_spec.rb +0 -5
  89. data/spec/polling_spec.rb +0 -120
  90. data/spec/redis_feature_store_spec.rb +0 -121
  91. data/spec/requestor_spec.rb +0 -209
  92. data/spec/segment_store_spec_base.rb +0 -95
  93. data/spec/simple_lru_cache_spec.rb +0 -24
  94. data/spec/spec_helper.rb +0 -9
  95. data/spec/store_spec.rb +0 -10
  96. data/spec/stream_spec.rb +0 -45
  97. data/spec/user_filter_spec.rb +0 -91
  98. data/spec/util_spec.rb +0 -17
  99. 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