launchdarkly-server-sdk 5.8.1 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +28 -122
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  4. data/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. data/.gitignore +2 -1
  6. data/.ldrelease/build-docs.sh +18 -0
  7. data/.ldrelease/circleci/linux/execute.sh +18 -0
  8. data/.ldrelease/circleci/mac/execute.sh +18 -0
  9. data/.ldrelease/circleci/template/build.sh +29 -0
  10. data/.ldrelease/circleci/template/publish.sh +23 -0
  11. data/.ldrelease/circleci/template/set-gem-home.sh +7 -0
  12. data/.ldrelease/circleci/template/test.sh +10 -0
  13. data/.ldrelease/circleci/template/update-version.sh +8 -0
  14. data/.ldrelease/circleci/windows/execute.ps1 +19 -0
  15. data/.ldrelease/config.yml +14 -2
  16. data/CHANGELOG.md +29 -0
  17. data/CONTRIBUTING.md +1 -1
  18. data/README.md +4 -3
  19. data/azure-pipelines.yml +1 -1
  20. data/docs/Makefile +26 -0
  21. data/docs/index.md +9 -0
  22. data/launchdarkly-server-sdk.gemspec +16 -16
  23. data/lib/ldclient-rb.rb +0 -1
  24. data/lib/ldclient-rb/config.rb +15 -3
  25. data/lib/ldclient-rb/evaluation_detail.rb +324 -0
  26. data/lib/ldclient-rb/events.rb +6 -7
  27. data/lib/ldclient-rb/file_data_source.rb +1 -1
  28. data/lib/ldclient-rb/impl/evaluator.rb +231 -0
  29. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +87 -0
  30. data/lib/ldclient-rb/impl/evaluator_operators.rb +160 -0
  31. data/lib/ldclient-rb/impl/event_factory.rb +28 -0
  32. data/lib/ldclient-rb/impl/event_sender.rb +56 -40
  33. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +5 -5
  34. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +5 -5
  35. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +5 -7
  36. data/lib/ldclient-rb/impl/model/serialization.rb +62 -0
  37. data/lib/ldclient-rb/impl/unbounded_pool.rb +34 -0
  38. data/lib/ldclient-rb/ldclient.rb +36 -15
  39. data/lib/ldclient-rb/polling.rb +1 -4
  40. data/lib/ldclient-rb/requestor.rb +25 -15
  41. data/lib/ldclient-rb/stream.rb +9 -6
  42. data/lib/ldclient-rb/util.rb +12 -8
  43. data/lib/ldclient-rb/version.rb +1 -1
  44. data/spec/evaluation_detail_spec.rb +135 -0
  45. data/spec/event_sender_spec.rb +20 -2
  46. data/spec/events_spec.rb +10 -0
  47. data/spec/http_util.rb +11 -1
  48. data/spec/impl/evaluator_bucketing_spec.rb +216 -0
  49. data/spec/impl/evaluator_clause_spec.rb +55 -0
  50. data/spec/impl/evaluator_operators_spec.rb +141 -0
  51. data/spec/impl/evaluator_rule_spec.rb +128 -0
  52. data/spec/impl/evaluator_segment_spec.rb +125 -0
  53. data/spec/impl/evaluator_spec.rb +349 -0
  54. data/spec/impl/evaluator_spec_base.rb +75 -0
  55. data/spec/impl/event_factory_spec.rb +108 -0
  56. data/spec/impl/model/serialization_spec.rb +41 -0
  57. data/spec/launchdarkly-server-sdk_spec.rb +1 -1
  58. data/spec/ldclient_end_to_end_spec.rb +34 -0
  59. data/spec/ldclient_spec.rb +64 -12
  60. data/spec/polling_spec.rb +2 -2
  61. data/spec/redis_feature_store_spec.rb +2 -2
  62. data/spec/requestor_spec.rb +11 -11
  63. metadata +92 -48
  64. data/.yardopts +0 -9
  65. data/Gemfile.lock +0 -89
  66. data/lib/ldclient-rb/evaluation.rb +0 -462
  67. data/scripts/gendocs.sh +0 -11
  68. data/scripts/release.sh +0 -27
  69. data/spec/evaluation_spec.rb +0 -789
@@ -0,0 +1,55 @@
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
@@ -0,0 +1,141 @@
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
@@ -0,0 +1,128 @@
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
@@ -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