launchdarkly-server-sdk 5.8.2 → 6.0.0
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +28 -122
- data/.ldrelease/circleci/linux/execute.sh +18 -0
- data/.ldrelease/circleci/mac/execute.sh +18 -0
- data/.ldrelease/circleci/template/build.sh +29 -0
- data/.ldrelease/circleci/template/publish.sh +23 -0
- data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
- data/.ldrelease/circleci/template/test.sh +10 -0
- data/.ldrelease/circleci/template/update-version.sh +8 -0
- data/.ldrelease/circleci/windows/execute.ps1 +19 -0
- data/.ldrelease/config.yml +7 -3
- data/CHANGELOG.md +9 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile.lock +69 -42
- data/README.md +2 -2
- data/azure-pipelines.yml +1 -1
- data/launchdarkly-server-sdk.gemspec +16 -16
- data/lib/ldclient-rb.rb +0 -1
- data/lib/ldclient-rb/config.rb +15 -3
- data/lib/ldclient-rb/evaluation_detail.rb +293 -0
- data/lib/ldclient-rb/events.rb +1 -4
- data/lib/ldclient-rb/file_data_source.rb +1 -1
- data/lib/ldclient-rb/impl/evaluator.rb +225 -0
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +74 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
- data/lib/ldclient-rb/impl/event_sender.rb +56 -40
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +5 -9
- data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
- data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
- data/lib/ldclient-rb/ldclient.rb +14 -9
- data/lib/ldclient-rb/polling.rb +1 -4
- data/lib/ldclient-rb/requestor.rb +25 -15
- data/lib/ldclient-rb/stream.rb +9 -6
- data/lib/ldclient-rb/util.rb +12 -8
- data/lib/ldclient-rb/version.rb +1 -1
- data/spec/evaluation_detail_spec.rb +135 -0
- data/spec/event_sender_spec.rb +20 -2
- data/spec/http_util.rb +11 -1
- data/spec/impl/evaluator_bucketing_spec.rb +111 -0
- data/spec/impl/evaluator_clause_spec.rb +55 -0
- data/spec/impl/evaluator_operators_spec.rb +141 -0
- data/spec/impl/evaluator_rule_spec.rb +96 -0
- data/spec/impl/evaluator_segment_spec.rb +125 -0
- data/spec/impl/evaluator_spec.rb +305 -0
- data/spec/impl/evaluator_spec_base.rb +75 -0
- data/spec/impl/model/serialization_spec.rb +41 -0
- data/spec/launchdarkly-server-sdk_spec.rb +1 -1
- data/spec/ldclient_end_to_end_spec.rb +34 -0
- data/spec/ldclient_spec.rb +10 -8
- data/spec/polling_spec.rb +2 -2
- data/spec/redis_feature_store_spec.rb +2 -2
- data/spec/requestor_spec.rb +11 -11
- metadata +89 -46
- data/lib/ldclient-rb/evaluation.rb +0 -462
- data/spec/evaluation_spec.rb +0 -789
@@ -0,0 +1,125 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "impl/evaluator_spec_base"
|
3
|
+
|
4
|
+
module LaunchDarkly
|
5
|
+
module Impl
|
6
|
+
describe "Evaluator (segments)", :evaluator_spec_base => true do
|
7
|
+
subject { Evaluator }
|
8
|
+
|
9
|
+
def test_segment_match(segment)
|
10
|
+
clause = make_segment_match_clause(segment)
|
11
|
+
flag = boolean_flag_with_clauses([clause])
|
12
|
+
e = Evaluator.new(get_nothing, get_things({ segment[:key] => segment }), logger)
|
13
|
+
e.evaluate(flag, user, factory).detail.value
|
14
|
+
end
|
15
|
+
|
16
|
+
it "retrieves segment from segment store for segmentMatch operator" do
|
17
|
+
segment = {
|
18
|
+
key: 'segkey',
|
19
|
+
included: [ 'userkey' ],
|
20
|
+
version: 1,
|
21
|
+
deleted: false
|
22
|
+
}
|
23
|
+
get_segment = get_things({ 'segkey' => segment })
|
24
|
+
e = subject.new(get_nothing, get_segment, logger)
|
25
|
+
user = { key: 'userkey' }
|
26
|
+
clause = { attribute: '', op: 'segmentMatch', values: ['segkey'] }
|
27
|
+
flag = boolean_flag_with_clauses([clause])
|
28
|
+
expect(e.evaluate(flag, user, factory).detail.value).to be true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "falls through with no errors if referenced segment is not found" do
|
32
|
+
e = subject.new(get_nothing, get_things({ 'segkey' => nil }), logger)
|
33
|
+
user = { key: 'userkey' }
|
34
|
+
clause = { attribute: '', op: 'segmentMatch', values: ['segkey'] }
|
35
|
+
flag = boolean_flag_with_clauses([clause])
|
36
|
+
expect(e.evaluate(flag, user, factory).detail.value).to be false
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'explicitly includes user' do
|
40
|
+
segment = make_segment('segkey')
|
41
|
+
segment[:included] = [ user[:key] ]
|
42
|
+
expect(test_segment_match(segment)).to be true
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'explicitly excludes user' do
|
46
|
+
segment = make_segment('segkey')
|
47
|
+
segment[:excluded] = [ user[:key] ]
|
48
|
+
expect(test_segment_match(segment)).to be false
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'both includes and excludes user; include takes priority' do
|
52
|
+
segment = make_segment('segkey')
|
53
|
+
segment[:included] = [ user[:key] ]
|
54
|
+
segment[:excluded] = [ user[:key] ]
|
55
|
+
expect(test_segment_match(segment)).to be true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'matches user by rule when weight is absent' do
|
59
|
+
segClause = make_user_matching_clause(user, :email)
|
60
|
+
segRule = {
|
61
|
+
clauses: [ segClause ]
|
62
|
+
}
|
63
|
+
segment = make_segment('segkey')
|
64
|
+
segment[:rules] = [ segRule ]
|
65
|
+
expect(test_segment_match(segment)).to be true
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'matches user by rule when weight is nil' do
|
69
|
+
segClause = make_user_matching_clause(user, :email)
|
70
|
+
segRule = {
|
71
|
+
clauses: [ segClause ],
|
72
|
+
weight: nil
|
73
|
+
}
|
74
|
+
segment = make_segment('segkey')
|
75
|
+
segment[:rules] = [ segRule ]
|
76
|
+
expect(test_segment_match(segment)).to be true
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'matches user with full rollout' do
|
80
|
+
segClause = make_user_matching_clause(user, :email)
|
81
|
+
segRule = {
|
82
|
+
clauses: [ segClause ],
|
83
|
+
weight: 100000
|
84
|
+
}
|
85
|
+
segment = make_segment('segkey')
|
86
|
+
segment[:rules] = [ segRule ]
|
87
|
+
expect(test_segment_match(segment)).to be true
|
88
|
+
end
|
89
|
+
|
90
|
+
it "doesn't match user with zero rollout" do
|
91
|
+
segClause = make_user_matching_clause(user, :email)
|
92
|
+
segRule = {
|
93
|
+
clauses: [ segClause ],
|
94
|
+
weight: 0
|
95
|
+
}
|
96
|
+
segment = make_segment('segkey')
|
97
|
+
segment[:rules] = [ segRule ]
|
98
|
+
expect(test_segment_match(segment)).to be false
|
99
|
+
end
|
100
|
+
|
101
|
+
it "matches user with multiple clauses" do
|
102
|
+
segClause1 = make_user_matching_clause(user, :email)
|
103
|
+
segClause2 = make_user_matching_clause(user, :name)
|
104
|
+
segRule = {
|
105
|
+
clauses: [ segClause1, segClause2 ]
|
106
|
+
}
|
107
|
+
segment = make_segment('segkey')
|
108
|
+
segment[:rules] = [ segRule ]
|
109
|
+
expect(test_segment_match(segment)).to be true
|
110
|
+
end
|
111
|
+
|
112
|
+
it "doesn't match user with multiple clauses if a clause doesn't match" do
|
113
|
+
segClause1 = make_user_matching_clause(user, :email)
|
114
|
+
segClause2 = make_user_matching_clause(user, :name)
|
115
|
+
segClause2[:values] = [ 'wrong' ]
|
116
|
+
segRule = {
|
117
|
+
clauses: [ segClause1, segClause2 ]
|
118
|
+
}
|
119
|
+
segment = make_segment('segkey')
|
120
|
+
segment[:rules] = [ segRule ]
|
121
|
+
expect(test_segment_match(segment)).to be false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "impl/evaluator_spec_base"
|
3
|
+
|
4
|
+
module LaunchDarkly
|
5
|
+
module Impl
|
6
|
+
describe "Evaluator (general)", :evaluator_spec_base => true do
|
7
|
+
subject { Evaluator }
|
8
|
+
|
9
|
+
describe "evaluate" do
|
10
|
+
it "returns off variation if flag is off" do
|
11
|
+
flag = {
|
12
|
+
key: 'feature',
|
13
|
+
on: false,
|
14
|
+
offVariation: 1,
|
15
|
+
fallthrough: { variation: 0 },
|
16
|
+
variations: ['a', 'b', 'c']
|
17
|
+
}
|
18
|
+
user = { key: 'x' }
|
19
|
+
detail = EvaluationDetail.new('b', 1, EvaluationReason::off)
|
20
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
21
|
+
expect(result.detail).to eq(detail)
|
22
|
+
expect(result.events).to eq(nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns nil if flag is off and off variation is unspecified" do
|
26
|
+
flag = {
|
27
|
+
key: 'feature',
|
28
|
+
on: false,
|
29
|
+
fallthrough: { variation: 0 },
|
30
|
+
variations: ['a', 'b', 'c']
|
31
|
+
}
|
32
|
+
user = { key: 'x' }
|
33
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::off)
|
34
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
35
|
+
expect(result.detail).to eq(detail)
|
36
|
+
expect(result.events).to eq(nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "returns an error if off variation is too high" do
|
40
|
+
flag = {
|
41
|
+
key: 'feature',
|
42
|
+
on: false,
|
43
|
+
offVariation: 999,
|
44
|
+
fallthrough: { variation: 0 },
|
45
|
+
variations: ['a', 'b', 'c']
|
46
|
+
}
|
47
|
+
user = { key: 'x' }
|
48
|
+
detail = EvaluationDetail.new(nil, nil,
|
49
|
+
EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
50
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
51
|
+
expect(result.detail).to eq(detail)
|
52
|
+
expect(result.events).to eq(nil)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns an error if off variation is negative" do
|
56
|
+
flag = {
|
57
|
+
key: 'feature',
|
58
|
+
on: false,
|
59
|
+
offVariation: -1,
|
60
|
+
fallthrough: { variation: 0 },
|
61
|
+
variations: ['a', 'b', 'c']
|
62
|
+
}
|
63
|
+
user = { key: 'x' }
|
64
|
+
detail = EvaluationDetail.new(nil, nil,
|
65
|
+
EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
66
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
67
|
+
expect(result.detail).to eq(detail)
|
68
|
+
expect(result.events).to eq(nil)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "returns off variation if prerequisite is not found" do
|
72
|
+
flag = {
|
73
|
+
key: 'feature0',
|
74
|
+
on: true,
|
75
|
+
prerequisites: [{key: 'badfeature', variation: 1}],
|
76
|
+
fallthrough: { variation: 0 },
|
77
|
+
offVariation: 1,
|
78
|
+
variations: ['a', 'b', 'c']
|
79
|
+
}
|
80
|
+
user = { key: 'x' }
|
81
|
+
detail = EvaluationDetail.new('b', 1, EvaluationReason::prerequisite_failed('badfeature'))
|
82
|
+
e = subject.new(get_things( 'badfeature' => nil ), get_nothing, logger)
|
83
|
+
result = e.evaluate(flag, user, factory)
|
84
|
+
expect(result.detail).to eq(detail)
|
85
|
+
expect(result.events).to eq(nil)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "reuses prerequisite-failed reason instances if possible" do
|
89
|
+
flag = {
|
90
|
+
key: 'feature0',
|
91
|
+
on: true,
|
92
|
+
prerequisites: [{key: 'badfeature', variation: 1}],
|
93
|
+
fallthrough: { variation: 0 },
|
94
|
+
offVariation: 1,
|
95
|
+
variations: ['a', 'b', 'c']
|
96
|
+
}
|
97
|
+
Model.postprocess_item_after_deserializing!(FEATURES, flag) # now there's a cached reason
|
98
|
+
user = { key: 'x' }
|
99
|
+
e = subject.new(get_things( 'badfeature' => nil ), get_nothing, logger)
|
100
|
+
result1 = e.evaluate(flag, user, factory)
|
101
|
+
expect(result1.detail.reason).to eq EvaluationReason::prerequisite_failed('badfeature')
|
102
|
+
result2 = e.evaluate(flag, user, factory)
|
103
|
+
expect(result2.detail.reason).to be result1.detail.reason
|
104
|
+
end
|
105
|
+
|
106
|
+
it "returns off variation and event if prerequisite of a prerequisite is not found" do
|
107
|
+
flag = {
|
108
|
+
key: 'feature0',
|
109
|
+
on: true,
|
110
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
111
|
+
fallthrough: { variation: 0 },
|
112
|
+
offVariation: 1,
|
113
|
+
variations: ['a', 'b', 'c'],
|
114
|
+
version: 1
|
115
|
+
}
|
116
|
+
flag1 = {
|
117
|
+
key: 'feature1',
|
118
|
+
on: true,
|
119
|
+
prerequisites: [{key: 'feature2', variation: 1}], # feature2 doesn't exist
|
120
|
+
fallthrough: { variation: 0 },
|
121
|
+
variations: ['d', 'e'],
|
122
|
+
version: 2
|
123
|
+
}
|
124
|
+
user = { key: 'x' }
|
125
|
+
detail = EvaluationDetail.new('b', 1, EvaluationReason::prerequisite_failed('feature1'))
|
126
|
+
events_should_be = [{
|
127
|
+
kind: 'feature', key: 'feature1', user: user, value: nil, default: nil, variation: nil, version: 2, prereqOf: 'feature0'
|
128
|
+
}]
|
129
|
+
get_flag = get_things('feature1' => flag1, 'feature2' => nil)
|
130
|
+
e = subject.new(get_flag, get_nothing, logger)
|
131
|
+
result = e.evaluate(flag, user, factory)
|
132
|
+
expect(result.detail).to eq(detail)
|
133
|
+
expect(result.events).to eq(events_should_be)
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns off variation and event if prerequisite is off" do
|
137
|
+
flag = {
|
138
|
+
key: 'feature0',
|
139
|
+
on: true,
|
140
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
141
|
+
fallthrough: { variation: 0 },
|
142
|
+
offVariation: 1,
|
143
|
+
variations: ['a', 'b', 'c'],
|
144
|
+
version: 1
|
145
|
+
}
|
146
|
+
flag1 = {
|
147
|
+
key: 'feature1',
|
148
|
+
on: false,
|
149
|
+
# note that even though it returns the desired variation, it is still off and therefore not a match
|
150
|
+
offVariation: 1,
|
151
|
+
fallthrough: { variation: 0 },
|
152
|
+
variations: ['d', 'e'],
|
153
|
+
version: 2
|
154
|
+
}
|
155
|
+
user = { key: 'x' }
|
156
|
+
detail = EvaluationDetail.new('b', 1, EvaluationReason::prerequisite_failed('feature1'))
|
157
|
+
events_should_be = [{
|
158
|
+
kind: 'feature', key: 'feature1', user: user, variation: 1, value: 'e', default: nil, version: 2, prereqOf: 'feature0'
|
159
|
+
}]
|
160
|
+
get_flag = get_things({ 'feature1' => flag1 })
|
161
|
+
e = subject.new(get_flag, get_nothing, logger)
|
162
|
+
result = e.evaluate(flag, user, factory)
|
163
|
+
expect(result.detail).to eq(detail)
|
164
|
+
expect(result.events).to eq(events_should_be)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "returns off variation and event if prerequisite is not met" do
|
168
|
+
flag = {
|
169
|
+
key: 'feature0',
|
170
|
+
on: true,
|
171
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
172
|
+
fallthrough: { variation: 0 },
|
173
|
+
offVariation: 1,
|
174
|
+
variations: ['a', 'b', 'c'],
|
175
|
+
version: 1
|
176
|
+
}
|
177
|
+
flag1 = {
|
178
|
+
key: 'feature1',
|
179
|
+
on: true,
|
180
|
+
fallthrough: { variation: 0 },
|
181
|
+
variations: ['d', 'e'],
|
182
|
+
version: 2
|
183
|
+
}
|
184
|
+
user = { key: 'x' }
|
185
|
+
detail = EvaluationDetail.new('b', 1, EvaluationReason::prerequisite_failed('feature1'))
|
186
|
+
events_should_be = [{
|
187
|
+
kind: 'feature', key: 'feature1', user: user, variation: 0, value: 'd', default: nil, version: 2, prereqOf: 'feature0'
|
188
|
+
}]
|
189
|
+
get_flag = get_things({ 'feature1' => flag1 })
|
190
|
+
e = subject.new(get_flag, get_nothing, logger)
|
191
|
+
result = e.evaluate(flag, user, factory)
|
192
|
+
expect(result.detail).to eq(detail)
|
193
|
+
expect(result.events).to eq(events_should_be)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "returns fallthrough variation and event if prerequisite is met and there are no rules" do
|
197
|
+
flag = {
|
198
|
+
key: 'feature0',
|
199
|
+
on: true,
|
200
|
+
prerequisites: [{key: 'feature1', variation: 1}],
|
201
|
+
fallthrough: { variation: 0 },
|
202
|
+
offVariation: 1,
|
203
|
+
variations: ['a', 'b', 'c'],
|
204
|
+
version: 1
|
205
|
+
}
|
206
|
+
flag1 = {
|
207
|
+
key: 'feature1',
|
208
|
+
on: true,
|
209
|
+
fallthrough: { variation: 1 },
|
210
|
+
variations: ['d', 'e'],
|
211
|
+
version: 2
|
212
|
+
}
|
213
|
+
user = { key: 'x' }
|
214
|
+
detail = EvaluationDetail.new('a', 0, EvaluationReason::fallthrough)
|
215
|
+
events_should_be = [{
|
216
|
+
kind: 'feature', key: 'feature1', user: user, variation: 1, value: 'e', default: nil, version: 2, prereqOf: 'feature0'
|
217
|
+
}]
|
218
|
+
get_flag = get_things({ 'feature1' => flag1 })
|
219
|
+
e = subject.new(get_flag, get_nothing, logger)
|
220
|
+
result = e.evaluate(flag, user, factory)
|
221
|
+
expect(result.detail).to eq(detail)
|
222
|
+
expect(result.events).to eq(events_should_be)
|
223
|
+
end
|
224
|
+
|
225
|
+
it "returns an error if fallthrough variation is too high" do
|
226
|
+
flag = {
|
227
|
+
key: 'feature',
|
228
|
+
on: true,
|
229
|
+
fallthrough: { variation: 999 },
|
230
|
+
offVariation: 1,
|
231
|
+
variations: ['a', 'b', 'c']
|
232
|
+
}
|
233
|
+
user = { key: 'userkey' }
|
234
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
235
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
236
|
+
expect(result.detail).to eq(detail)
|
237
|
+
expect(result.events).to eq(nil)
|
238
|
+
end
|
239
|
+
|
240
|
+
it "returns an error if fallthrough variation is negative" do
|
241
|
+
flag = {
|
242
|
+
key: 'feature',
|
243
|
+
on: true,
|
244
|
+
fallthrough: { variation: -1 },
|
245
|
+
offVariation: 1,
|
246
|
+
variations: ['a', 'b', 'c']
|
247
|
+
}
|
248
|
+
user = { key: 'userkey' }
|
249
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
250
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
251
|
+
expect(result.detail).to eq(detail)
|
252
|
+
expect(result.events).to eq(nil)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "returns an error if fallthrough has no variation or rollout" do
|
256
|
+
flag = {
|
257
|
+
key: 'feature',
|
258
|
+
on: true,
|
259
|
+
fallthrough: { },
|
260
|
+
offVariation: 1,
|
261
|
+
variations: ['a', 'b', 'c']
|
262
|
+
}
|
263
|
+
user = { key: 'userkey' }
|
264
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
265
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
266
|
+
expect(result.detail).to eq(detail)
|
267
|
+
expect(result.events).to eq(nil)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "returns an error if fallthrough has a rollout with no variations" do
|
271
|
+
flag = {
|
272
|
+
key: 'feature',
|
273
|
+
on: true,
|
274
|
+
fallthrough: { rollout: { variations: [] } },
|
275
|
+
offVariation: 1,
|
276
|
+
variations: ['a', 'b', 'c']
|
277
|
+
}
|
278
|
+
user = { key: 'userkey' }
|
279
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_MALFORMED_FLAG))
|
280
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
281
|
+
expect(result.detail).to eq(detail)
|
282
|
+
expect(result.events).to eq(nil)
|
283
|
+
end
|
284
|
+
|
285
|
+
it "matches user from targets" do
|
286
|
+
flag = {
|
287
|
+
key: 'feature',
|
288
|
+
on: true,
|
289
|
+
targets: [
|
290
|
+
{ values: [ 'whoever', 'userkey' ], variation: 2 }
|
291
|
+
],
|
292
|
+
fallthrough: { variation: 0 },
|
293
|
+
offVariation: 1,
|
294
|
+
variations: ['a', 'b', 'c']
|
295
|
+
}
|
296
|
+
user = { key: 'userkey' }
|
297
|
+
detail = EvaluationDetail.new('c', 2, EvaluationReason::target_match)
|
298
|
+
result = basic_evaluator.evaluate(flag, user, factory)
|
299
|
+
expect(result.detail).to eq(detail)
|
300
|
+
expect(result.events).to eq(nil)
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module LaunchDarkly
|
4
|
+
module Impl
|
5
|
+
module EvaluatorSpecBase
|
6
|
+
def factory
|
7
|
+
EventFactory.new(false)
|
8
|
+
end
|
9
|
+
|
10
|
+
def user
|
11
|
+
{
|
12
|
+
key: "userkey",
|
13
|
+
email: "test@example.com",
|
14
|
+
name: "Bob"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def logger
|
19
|
+
::Logger.new($stdout, level: ::Logger::FATAL)
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_nothing
|
23
|
+
lambda { |key| raise "should not have requested #{key}" }
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_things(map)
|
27
|
+
lambda { |key|
|
28
|
+
raise "should not have requested #{key}" if !map.has_key?(key)
|
29
|
+
map[key]
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def basic_evaluator
|
34
|
+
subject.new(get_nothing, get_nothing, logger)
|
35
|
+
end
|
36
|
+
|
37
|
+
def boolean_flag_with_rules(rules)
|
38
|
+
{ key: 'feature', on: true, rules: rules, fallthrough: { variation: 0 }, variations: [ false, true ] }
|
39
|
+
end
|
40
|
+
|
41
|
+
def boolean_flag_with_clauses(clauses)
|
42
|
+
boolean_flag_with_rules([{ id: 'ruleid', clauses: clauses, variation: 1 }])
|
43
|
+
end
|
44
|
+
|
45
|
+
def make_user_matching_clause(user, attr)
|
46
|
+
{
|
47
|
+
attribute: attr.to_s,
|
48
|
+
op: :in,
|
49
|
+
values: [ user[attr.to_sym] ],
|
50
|
+
negate: false
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def make_segment(key)
|
55
|
+
{
|
56
|
+
key: key,
|
57
|
+
included: [],
|
58
|
+
excluded: [],
|
59
|
+
salt: 'abcdef',
|
60
|
+
version: 1
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def make_segment_match_clause(segment)
|
65
|
+
{
|
66
|
+
op: :segmentMatch,
|
67
|
+
values: [ segment[:key] ],
|
68
|
+
negate: false
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
RSpec.configure { |c| c.include EvaluatorSpecBase, :evaluator_spec_base => true }
|
74
|
+
end
|
75
|
+
end
|