prefab-cloud-ruby 0.23.8 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -8
- data/VERSION +1 -1
- data/lib/prefab/client.rb +55 -21
- data/lib/prefab/config_client.rb +13 -25
- data/lib/prefab/config_resolver.rb +28 -28
- data/lib/prefab/config_value_unwrapper.rb +8 -5
- data/lib/prefab/context.rb +119 -0
- data/lib/prefab/criteria_evaluator.rb +23 -17
- data/lib/prefab/feature_flag_client.rb +7 -7
- data/lib/prefab/local_config_parser.rb +2 -17
- data/lib/prefab/logger_client.rb +3 -6
- data/lib/prefab/resolved_config_presenter.rb +84 -0
- data/lib/prefab/weighted_value_resolver.rb +4 -4
- data/lib/prefab-cloud-ruby.rb +6 -0
- data/prefab-cloud-ruby.gemspec +6 -3
- data/test/.prefab.unit_tests.config.yaml +3 -2
- data/test/integration_test.rb +4 -8
- data/test/test_client.rb +28 -27
- data/test/test_config_resolver.rb +106 -54
- data/test/test_config_value_unwrapper.rb +15 -15
- data/test/test_context.rb +158 -0
- data/test/test_criteria_evaluator.rb +93 -78
- data/test/test_feature_flag_client.rb +14 -20
- data/test/test_helper.rb +1 -1
- data/test/test_integration.rb +30 -14
- data/test/test_local_config_parser.rb +6 -8
- data/test/test_log_path_collector.rb +4 -7
- data/test/test_logger.rb +12 -12
- metadata +5 -2
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Prefab
|
4
|
+
class ResolvedConfigPresenter
|
5
|
+
class ConfigRow
|
6
|
+
include Comparable
|
7
|
+
|
8
|
+
attr_reader :key, :value, :match, :source
|
9
|
+
|
10
|
+
def initialize(key, value, match, source)
|
11
|
+
@key = key
|
12
|
+
@value = value
|
13
|
+
@match = match
|
14
|
+
@source = source
|
15
|
+
end
|
16
|
+
|
17
|
+
def <=>(other)
|
18
|
+
inspect <=> other.inspect
|
19
|
+
end
|
20
|
+
|
21
|
+
def inspect
|
22
|
+
[@key, @value, @match, @source].inspect
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(resolver, lock, local_store)
|
27
|
+
@resolver = resolver
|
28
|
+
@lock = lock
|
29
|
+
@local_store = local_store
|
30
|
+
end
|
31
|
+
|
32
|
+
def each(&block)
|
33
|
+
to_h.each(&block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_h
|
37
|
+
hash = {}
|
38
|
+
|
39
|
+
Prefab::Context.with_context({}) do
|
40
|
+
@lock.with_read_lock do
|
41
|
+
@local_store.keys.sort.each do |k|
|
42
|
+
v = @local_store[k]
|
43
|
+
|
44
|
+
if v.nil?
|
45
|
+
hash[k] = ConfigRow.new(k, nil, nil, nil)
|
46
|
+
else
|
47
|
+
config = @resolver.evaluate(v[:config])
|
48
|
+
value = Prefab::ConfigValueUnwrapper.unwrap(config, k, {})
|
49
|
+
hash[k] = ConfigRow.new(k, value, v[:match], v[:source])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
hash
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
str = "\n"
|
60
|
+
|
61
|
+
Prefab::Context.with_context({}) do
|
62
|
+
@lock.with_read_lock do
|
63
|
+
@local_store.keys.sort.each do |k|
|
64
|
+
v = @local_store[k]
|
65
|
+
elements = [k.slice(0..49).ljust(50)]
|
66
|
+
if v.nil?
|
67
|
+
elements << 'tombstone'
|
68
|
+
else
|
69
|
+
config = @resolver.evaluate(v[:config], {})
|
70
|
+
value = Prefab::ConfigValueUnwrapper.unwrap(config, k, {})
|
71
|
+
elements << value.to_s.slice(0..34).ljust(35)
|
72
|
+
elements << value.class.to_s.slice(0..6).ljust(7)
|
73
|
+
elements << "Match: #{v[:match]}".slice(0..29).ljust(30)
|
74
|
+
elements << "Source: #{v[:source]}"
|
75
|
+
end
|
76
|
+
str += elements.join(' | ') << "\n"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
str
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -4,14 +4,14 @@ module Prefab
|
|
4
4
|
class WeightedValueResolver
|
5
5
|
MAX_32_FLOAT = 4_294_967_294.0
|
6
6
|
|
7
|
-
def initialize(weights, config_key,
|
7
|
+
def initialize(weights, config_key, context_hash_value)
|
8
8
|
@weights = weights
|
9
9
|
@config_key = config_key
|
10
|
-
@
|
10
|
+
@context_hash_value = context_hash_value
|
11
11
|
end
|
12
12
|
|
13
13
|
def resolve
|
14
|
-
percent = @
|
14
|
+
percent = @context_hash_value ? user_percent : rand
|
15
15
|
|
16
16
|
index = variant_index(percent)
|
17
17
|
|
@@ -19,7 +19,7 @@ module Prefab
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def user_percent
|
22
|
-
to_hash = "#{@config_key}#{@
|
22
|
+
to_hash = "#{@config_key}#{@context_hash_value}"
|
23
23
|
int_value = Murmur3.murmur3_32(to_hash)
|
24
24
|
int_value / MAX_32_FLOAT
|
25
25
|
end
|
data/lib/prefab-cloud-ruby.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Prefab
|
4
|
+
NO_DEFAULT_PROVIDED = :no_default_provided
|
5
|
+
end
|
6
|
+
|
3
7
|
require 'concurrent/atomics'
|
4
8
|
require 'concurrent'
|
5
9
|
require 'faraday'
|
@@ -21,8 +25,10 @@ require 'prefab/criteria_evaluator'
|
|
21
25
|
require 'prefab/config_loader'
|
22
26
|
require 'prefab/local_config_parser'
|
23
27
|
require 'prefab/yaml_config_parser'
|
28
|
+
require 'prefab/resolved_config_presenter'
|
24
29
|
require 'prefab/config_resolver'
|
25
30
|
require 'prefab/http_connection'
|
31
|
+
require 'prefab/context'
|
26
32
|
require 'prefab/client'
|
27
33
|
require 'prefab/config_client'
|
28
34
|
require 'prefab/feature_flag_client'
|
data/prefab-cloud-ruby.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: prefab-cloud-ruby 0.
|
5
|
+
# stub: prefab-cloud-ruby 0.24.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "prefab-cloud-ruby".freeze
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "0.24.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib".freeze]
|
13
13
|
s.authors = ["Jeff Dwyer".freeze]
|
14
|
-
s.date = "2023-04-
|
14
|
+
s.date = "2023-04-26"
|
15
15
|
s.description = "Feature Flags, Live Config, and Dynamic Log Levels as a service".freeze
|
16
16
|
s.email = "jdwyer@prefab.cloud".freeze
|
17
17
|
s.extra_rdoc_files = [
|
@@ -38,6 +38,7 @@ Gem::Specification.new do |s|
|
|
38
38
|
"lib/prefab/config_loader.rb",
|
39
39
|
"lib/prefab/config_resolver.rb",
|
40
40
|
"lib/prefab/config_value_unwrapper.rb",
|
41
|
+
"lib/prefab/context.rb",
|
41
42
|
"lib/prefab/criteria_evaluator.rb",
|
42
43
|
"lib/prefab/error.rb",
|
43
44
|
"lib/prefab/errors/initialization_timeout_error.rb",
|
@@ -54,6 +55,7 @@ Gem::Specification.new do |s|
|
|
54
55
|
"lib/prefab/noop_cache.rb",
|
55
56
|
"lib/prefab/noop_stats.rb",
|
56
57
|
"lib/prefab/options.rb",
|
58
|
+
"lib/prefab/resolved_config_presenter.rb",
|
57
59
|
"lib/prefab/sse_logger.rb",
|
58
60
|
"lib/prefab/weighted_value_resolver.rb",
|
59
61
|
"lib/prefab/yaml_config_parser.rb",
|
@@ -68,6 +70,7 @@ Gem::Specification.new do |s|
|
|
68
70
|
"test/test_config_loader.rb",
|
69
71
|
"test/test_config_resolver.rb",
|
70
72
|
"test/test_config_value_unwrapper.rb",
|
73
|
+
"test/test_context.rb",
|
71
74
|
"test/test_criteria_evaluator.rb",
|
72
75
|
"test/test_exponential_backoff.rb",
|
73
76
|
"test/test_feature_flag_client.rb",
|
@@ -9,8 +9,9 @@ sample: test sample value
|
|
9
9
|
enabled_flag: true
|
10
10
|
disabled_flag: false
|
11
11
|
flag_with_a_value: { "feature_flag": "true", value: "all-features" }
|
12
|
-
|
13
|
-
just_my_domain: { "feature_flag": "true", value: "new-version", criterion: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
|
12
|
+
user_key_match: { "feature_flag": "true", value: true, criterion: { operator: PROP_IS_ONE_OF, values: [ "abc123", "xyz987" ], property: "user.key" } }
|
13
|
+
just_my_domain: { "feature_flag": "true", value: "new-version", criterion: { operator: PROP_IS_ONE_OF, property: "user.domain", values: [ "prefab.cloud", "example.com" ] } }
|
14
|
+
deprecated_no_dot_notation: { "feature_flag": "true", value: true, criterion: { operator: PROP_IS_ONE_OF, property: "domain", values: [ "prefab.cloud", "example.com" ] } }
|
14
15
|
|
15
16
|
nested:
|
16
17
|
values:
|
data/test/integration_test.rb
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class IntegrationTest
|
4
|
-
attr_reader :func
|
5
|
-
attr_reader :input
|
6
|
-
attr_reader :expected
|
7
|
-
attr_reader :test_client
|
4
|
+
attr_reader :func, :input, :expected, :test_client
|
8
5
|
|
9
6
|
def initialize(test_data)
|
10
7
|
@client_overrides = parse_client_overrides(test_data['client_overrides'])
|
11
8
|
@func = parse_function(test_data['function'])
|
12
9
|
@input = parse_input(test_data['input'])
|
13
10
|
@expected = parse_expected(test_data['expected'])
|
14
|
-
test_client =
|
15
|
-
@test_client = base_client.send(test_client)
|
11
|
+
@test_client = base_client
|
16
12
|
end
|
17
13
|
|
18
14
|
def test_type
|
@@ -40,7 +36,7 @@ class IntegrationTest
|
|
40
36
|
def parse_function(function)
|
41
37
|
case function
|
42
38
|
when 'get_or_raise' then :get
|
43
|
-
when 'enabled' then :
|
39
|
+
when 'enabled' then :enabled?
|
44
40
|
else :"#{function}"
|
45
41
|
end
|
46
42
|
end
|
@@ -62,7 +58,7 @@ class IntegrationTest
|
|
62
58
|
end
|
63
59
|
|
64
60
|
def parse_ff_input(input)
|
65
|
-
[input['flag'], input['
|
61
|
+
[input['flag'], input['context']]
|
66
62
|
end
|
67
63
|
|
68
64
|
def parse_expected(expected)
|
data/test/test_client.rb
CHANGED
@@ -44,32 +44,36 @@ class TestClient < Minitest::Test
|
|
44
44
|
assert_equal false, @client.enabled?('flag_with_a_value')
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
def test_ff_enabled_with_user_key_match
|
48
|
+
assert_equal_context_and_jit(false, :enabled?, 'user_key_match', { user: { key: 'jimmy' } })
|
49
|
+
assert_equal_context_and_jit(true, :enabled?, 'user_key_match', { user: { key: 'abc123' } })
|
50
|
+
assert_equal_context_and_jit(true, :enabled?, 'user_key_match', { user: { key: 'xyz987' } })
|
51
51
|
end
|
52
52
|
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
assert_equal true, @client.get('in_lookup_key', 'abc123')
|
58
|
-
assert_equal true, @client.get('in_lookup_key', 'xyz987')
|
53
|
+
def test_ff_enabled_with_context
|
54
|
+
assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'gmail.com' })
|
55
|
+
assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'prefab.cloud' })
|
56
|
+
assert_equal_context_and_jit(false, :enabled?, 'just_my_domain', user: { domain: 'example.com' })
|
59
57
|
end
|
60
58
|
|
61
|
-
def
|
62
|
-
|
63
|
-
assert_equal
|
64
|
-
|
59
|
+
def test_ff_get_with_context
|
60
|
+
assert_nil @client.get('just_my_domain', 'abc123', user: { domain: 'gmail.com' })
|
61
|
+
assert_equal 'DEFAULT', @client.get('just_my_domain', 'abc123', { user: { domain: 'gmail.com' } }, 'DEFAULT')
|
62
|
+
|
63
|
+
assert_equal_context_and_jit('new-version', :get, 'just_my_domain', { user: { domain: 'prefab.cloud' } })
|
64
|
+
assert_equal_context_and_jit('new-version', :get, 'just_my_domain', { user: { domain: 'example.com' } })
|
65
65
|
end
|
66
66
|
|
67
|
-
def
|
68
|
-
|
69
|
-
assert_equal
|
67
|
+
def test_deprecated_no_dot_notation_ff_enabled_with_jit_context
|
68
|
+
# with no lookup key
|
69
|
+
assert_equal false, @client.enabled?('deprecated_no_dot_notation', { domain: 'gmail.com' })
|
70
|
+
assert_equal true, @client.enabled?('deprecated_no_dot_notation', { domain: 'prefab.cloud' })
|
71
|
+
assert_equal true, @client.enabled?('deprecated_no_dot_notation', { domain: 'example.com' })
|
70
72
|
|
71
|
-
|
72
|
-
assert_equal
|
73
|
+
# with a lookup key
|
74
|
+
assert_equal false, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'gmail.com' })
|
75
|
+
assert_equal true, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'prefab.cloud' })
|
76
|
+
assert_equal true, @client.enabled?('deprecated_no_dot_notation', 'some-lookup-key', { domain: 'example.com' })
|
73
77
|
end
|
74
78
|
|
75
79
|
def test_getting_feature_flag_value
|
@@ -103,14 +107,11 @@ class TestClient < Minitest::Test
|
|
103
107
|
|
104
108
|
private
|
105
109
|
|
106
|
-
def
|
107
|
-
|
108
|
-
prefab_config_override_dir: 'none',
|
109
|
-
prefab_config_classpath_dir: 'test',
|
110
|
-
prefab_envs: ['unit_tests'],
|
111
|
-
prefab_datasources: Prefab::Options::DATASOURCES::LOCAL_ONLY
|
112
|
-
}.merge(overrides))
|
110
|
+
def assert_equal_context_and_jit(expected, method, key, context)
|
111
|
+
assert_equal expected, @client.send(method, key, context)
|
113
112
|
|
114
|
-
Prefab::
|
113
|
+
Prefab::Context.with_context(context) do
|
114
|
+
assert_equal expected, @client.send(method, key)
|
115
|
+
end
|
115
116
|
end
|
116
117
|
end
|
@@ -9,10 +9,19 @@ class TestConfigResolver < Minitest::Test
|
|
9
9
|
SEGMENT_KEY = 'segment_key'
|
10
10
|
CONFIG_KEY = 'config_key'
|
11
11
|
DEFAULT_VALUE = 'default_value'
|
12
|
+
DESIRED_VALUE = 'desired_value'
|
12
13
|
IN_SEGMENT_VALUE = 'in_segment_value'
|
13
14
|
WRONG_ENV_VALUE = 'wrong_env_value'
|
14
15
|
NOT_IN_SEGMENT_VALUE = 'not_in_segment_value'
|
15
16
|
|
17
|
+
DEFAULT_ROW = Prefab::ConfigRow.new(
|
18
|
+
values: [
|
19
|
+
Prefab::ConditionalValue.new(
|
20
|
+
value: Prefab::ConfigValue.new(string: DEFAULT_VALUE)
|
21
|
+
)
|
22
|
+
]
|
23
|
+
)
|
24
|
+
|
16
25
|
def test_resolution
|
17
26
|
@loader = MockConfigLoader.new
|
18
27
|
|
@@ -20,13 +29,7 @@ class TestConfigResolver < Minitest::Test
|
|
20
29
|
'key' => { config: Prefab::Config.new(
|
21
30
|
key: 'key',
|
22
31
|
rows: [
|
23
|
-
|
24
|
-
values: [
|
25
|
-
Prefab::ConditionalValue.new(
|
26
|
-
value: Prefab::ConfigValue.new(string: 'value_no_env_default')
|
27
|
-
)
|
28
|
-
]
|
29
|
-
),
|
32
|
+
DEFAULT_ROW,
|
30
33
|
Prefab::ConfigRow.new(
|
31
34
|
project_env_id: TEST_ENV_ID,
|
32
35
|
values: [
|
@@ -114,31 +117,49 @@ class TestConfigResolver < Minitest::Test
|
|
114
117
|
|
115
118
|
@loader.stub :calc_config, loaded_values do
|
116
119
|
@resolverA = resolver_for_namespace('', @loader, project_env_id: PRODUCTION_ENV_ID)
|
117
|
-
|
120
|
+
assert_equal_context_and_jit DEFAULT_VALUE, @resolverA, 'key', {}, :string
|
118
121
|
|
119
122
|
## below here in the test env
|
120
123
|
@resolverA = resolver_for_namespace('', @loader)
|
121
|
-
|
124
|
+
assert_equal_context_and_jit 'value_none', @resolverA, 'key', {}, :string
|
122
125
|
|
123
126
|
@resolverA = resolver_for_namespace('projectA', @loader)
|
124
|
-
|
127
|
+
assert_equal_context_and_jit 'valueA', @resolverA, 'key', {}, :string
|
125
128
|
|
126
129
|
@resolverB = resolver_for_namespace('projectB', @loader)
|
127
|
-
|
130
|
+
assert_equal_context_and_jit 'valueB', @resolverB, 'key', {}, :string
|
128
131
|
|
129
132
|
@resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
|
130
|
-
|
133
|
+
assert_equal_context_and_jit 'projectB.subprojectX', @resolverBX, 'key', {}, :string
|
131
134
|
|
132
135
|
@resolverBX = resolver_for_namespace('projectB.subprojectX', @loader)
|
133
|
-
|
136
|
+
assert_equal_context_and_jit 'valueB2', @resolverBX, 'key2', {}, :string
|
134
137
|
|
135
|
-
@resolverUndefinedSubProject = resolver_for_namespace('projectB.subprojectX.subsubQ',
|
136
|
-
|
138
|
+
@resolverUndefinedSubProject = resolver_for_namespace('projectB.subprojectX.subsubQ',
|
139
|
+
@loader)
|
140
|
+
assert_equal_context_and_jit 'projectB.subprojectX', @resolverUndefinedSubProject, 'key',
|
141
|
+
{}, :string
|
137
142
|
|
138
143
|
@resolverBX = resolver_for_namespace('projectC', @loader)
|
139
|
-
|
144
|
+
assert_equal_context_and_jit 'value_none', @resolverBX, 'key', {}, :string
|
140
145
|
|
141
146
|
assert_nil @resolverBX.get('key_that_doesnt_exist', nil)
|
147
|
+
|
148
|
+
assert_equal @resolverBX.to_s.strip.split("\n").map(&:strip), [
|
149
|
+
'key | value_none | String | Match: | Source:',
|
150
|
+
'key2 | valueB2 | String | Match: | Source:'
|
151
|
+
]
|
152
|
+
|
153
|
+
assert_equal @resolverBX.presenter.to_h, {
|
154
|
+
'key' => Prefab::ResolvedConfigPresenter::ConfigRow.new('key', 'value_none', nil, nil),
|
155
|
+
'key2' => Prefab::ResolvedConfigPresenter::ConfigRow.new('key2', 'valueB2', nil, nil)
|
156
|
+
}
|
157
|
+
|
158
|
+
resolved_lines = []
|
159
|
+
@resolverBX.presenter.each do |key, row|
|
160
|
+
resolved_lines << [key, row.value]
|
161
|
+
end
|
162
|
+
assert_equal resolved_lines, [%w[key value_none], %w[key2 valueB2]]
|
142
163
|
end
|
143
164
|
end
|
144
165
|
|
@@ -155,7 +176,7 @@ class TestConfigResolver < Minitest::Test
|
|
155
176
|
Prefab::Criterion.new(
|
156
177
|
operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
157
178
|
value_to_match: string_list(['hotmail.com', 'gmail.com']),
|
158
|
-
property_name: 'email'
|
179
|
+
property_name: 'user.email'
|
159
180
|
)
|
160
181
|
]
|
161
182
|
),
|
@@ -222,8 +243,10 @@ class TestConfigResolver < Minitest::Test
|
|
222
243
|
resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
|
223
244
|
resolver.project_env_id = PRODUCTION_ENV_ID
|
224
245
|
|
225
|
-
|
226
|
-
|
246
|
+
assert_equal_context_and_jit DEFAULT_VALUE, resolver, CONFIG_KEY,
|
247
|
+
{ user: { email: 'test@something-else.com' } }, :string
|
248
|
+
assert_equal_context_and_jit IN_SEGMENT_VALUE, resolver, CONFIG_KEY,
|
249
|
+
{ user: { email: 'test@hotmail.com' } }, :string
|
227
250
|
end
|
228
251
|
end
|
229
252
|
|
@@ -240,7 +263,7 @@ class TestConfigResolver < Minitest::Test
|
|
240
263
|
Prefab::Criterion.new(
|
241
264
|
operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
242
265
|
value_to_match: string_list(['hotmail.com', 'gmail.com']),
|
243
|
-
property_name: 'email'
|
266
|
+
property_name: 'user.email'
|
244
267
|
)
|
245
268
|
]
|
246
269
|
),
|
@@ -289,77 +312,98 @@ class TestConfigResolver < Minitest::Test
|
|
289
312
|
options = Prefab::Options.new
|
290
313
|
resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
|
291
314
|
|
292
|
-
|
293
|
-
|
315
|
+
assert_equal_context_and_jit IN_SEGMENT_VALUE, resolver, CONFIG_KEY, { user: { email: 'test@hotmail.com' } },
|
316
|
+
:string
|
317
|
+
assert_equal_context_and_jit NOT_IN_SEGMENT_VALUE, resolver, CONFIG_KEY, { user: { email: 'test@something-else.com' } },
|
318
|
+
:string
|
294
319
|
end
|
295
320
|
end
|
296
321
|
|
297
|
-
def
|
298
|
-
|
299
|
-
|
300
|
-
key: SEGMENT_KEY,
|
322
|
+
def test_jit_context_merges_with_existing_context
|
323
|
+
config = Prefab::Config.new(
|
324
|
+
key: CONFIG_KEY,
|
301
325
|
rows: [
|
326
|
+
DEFAULT_ROW,
|
302
327
|
Prefab::ConfigRow.new(
|
328
|
+
project_env_id: TEST_ENV_ID,
|
303
329
|
values: [
|
304
330
|
Prefab::ConditionalValue.new(
|
305
|
-
value: Prefab::ConfigValue.new(bool: true),
|
306
331
|
criteria: [
|
307
332
|
Prefab::Criterion.new(
|
308
|
-
operator: Prefab::Criterion::CriterionOperator::
|
309
|
-
value_to_match: string_list([
|
310
|
-
property_name: '
|
333
|
+
operator: Prefab::Criterion::CriterionOperator::PROP_IS_ONE_OF,
|
334
|
+
value_to_match: string_list(%w[pro advanced]),
|
335
|
+
property_name: 'team.plan'
|
336
|
+
),
|
337
|
+
|
338
|
+
Prefab::Criterion.new(
|
339
|
+
operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
340
|
+
value_to_match: string_list(%w[@example.com]),
|
341
|
+
property_name: 'user.email'
|
311
342
|
)
|
312
|
-
]
|
313
|
-
|
314
|
-
|
343
|
+
],
|
344
|
+
value: Prefab::ConfigValue.new(string: DESIRED_VALUE)
|
345
|
+
)
|
315
346
|
]
|
316
347
|
)
|
317
348
|
]
|
318
349
|
)
|
319
350
|
|
351
|
+
loader = MockConfigLoader.new
|
352
|
+
|
353
|
+
loader.stub :calc_config, { CONFIG_KEY => { config: config } } do
|
354
|
+
options = Prefab::Options.new
|
355
|
+
resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
|
356
|
+
resolver.project_env_id = TEST_ENV_ID
|
357
|
+
|
358
|
+
Prefab::Context.with_context({ user: { email: 'test@example.com' } }) do
|
359
|
+
assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY).string
|
360
|
+
assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'freebie' } }).string
|
361
|
+
assert_equal DESIRED_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'pro' } }).string
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def test_jit_can_clobber_existing_context
|
320
367
|
config = Prefab::Config.new(
|
321
368
|
key: CONFIG_KEY,
|
322
369
|
rows: [
|
370
|
+
DEFAULT_ROW,
|
323
371
|
Prefab::ConfigRow.new(
|
372
|
+
project_env_id: TEST_ENV_ID,
|
324
373
|
values: [
|
325
374
|
Prefab::ConditionalValue.new(
|
326
375
|
criteria: [
|
327
376
|
Prefab::Criterion.new(
|
328
|
-
operator: Prefab::Criterion::CriterionOperator::
|
329
|
-
value_to_match:
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
),
|
334
|
-
Prefab::ConditionalValue.new(
|
335
|
-
criteria: [
|
377
|
+
operator: Prefab::Criterion::CriterionOperator::PROP_IS_ONE_OF,
|
378
|
+
value_to_match: string_list(%w[pro advanced]),
|
379
|
+
property_name: 'team.plan'
|
380
|
+
),
|
381
|
+
|
336
382
|
Prefab::Criterion.new(
|
337
|
-
operator: Prefab::Criterion::CriterionOperator::
|
338
|
-
value_to_match:
|
383
|
+
operator: Prefab::Criterion::CriterionOperator::PROP_ENDS_WITH_ONE_OF,
|
384
|
+
value_to_match: string_list(%w[@example.com]),
|
385
|
+
property_name: 'user.email'
|
339
386
|
)
|
340
387
|
],
|
341
|
-
value: Prefab::ConfigValue.new(string:
|
388
|
+
value: Prefab::ConfigValue.new(string: DESIRED_VALUE)
|
342
389
|
)
|
343
390
|
]
|
344
391
|
)
|
345
392
|
]
|
346
393
|
)
|
347
394
|
|
348
|
-
loaded_values = {
|
349
|
-
SEGMENT_KEY => { config: segment_config },
|
350
|
-
CONFIG_KEY => { config: config }
|
351
|
-
}
|
352
|
-
|
353
395
|
loader = MockConfigLoader.new
|
354
396
|
|
355
|
-
loader.stub :calc_config,
|
397
|
+
loader.stub :calc_config, { CONFIG_KEY => { config: config } } do
|
356
398
|
options = Prefab::Options.new
|
357
399
|
resolver = Prefab::ConfigResolver.new(MockBaseClient.new(options), loader)
|
400
|
+
resolver.project_env_id = TEST_ENV_ID
|
358
401
|
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
402
|
+
Prefab::Context.with_context({ user: { email: 'test@hotmail.com' }, team: { plan: 'pro' } }) do
|
403
|
+
assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY).string
|
404
|
+
assert_equal DESIRED_VALUE, resolver.get(CONFIG_KEY, { user: { email: 'test@example.com' } }).string
|
405
|
+
assert_equal DEFAULT_VALUE, resolver.get(CONFIG_KEY, { team: { plan: 'freebie' } }).string
|
406
|
+
end
|
363
407
|
end
|
364
408
|
end
|
365
409
|
|
@@ -374,4 +418,12 @@ class TestConfigResolver < Minitest::Test
|
|
374
418
|
resolver.update
|
375
419
|
resolver
|
376
420
|
end
|
421
|
+
|
422
|
+
def assert_equal_context_and_jit(expected_value, resolver, key, properties, type)
|
423
|
+
assert_equal expected_value, resolver.get(key, properties).send(type)
|
424
|
+
|
425
|
+
Prefab::Context.with_context(properties) do
|
426
|
+
assert_equal expected_value, resolver.get(key).send(type)
|
427
|
+
end
|
428
|
+
end
|
377
429
|
end
|
@@ -41,34 +41,34 @@ class TestConfigValueUnwrapper < Minitest::Test
|
|
41
41
|
def test_unwrapping_weighted_values
|
42
42
|
# single value
|
43
43
|
config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1]]))
|
44
|
+
|
44
45
|
assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, {})
|
45
46
|
|
46
47
|
# multiple values, evenly distributed
|
47
48
|
config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 1], ['ghi', 1]]))
|
48
|
-
assert_equal '
|
49
|
-
assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
50
|
-
assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
51
|
-
assert_equal '
|
49
|
+
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:000'))
|
50
|
+
assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:456'))
|
51
|
+
assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:789'))
|
52
|
+
assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:888'))
|
52
53
|
|
53
54
|
# multiple values, unevenly distributed
|
54
55
|
config_value = Prefab::ConfigValue.new(weighted_values: weighted_values([['abc', 1], ['def', 99], ['ghi', 1]]))
|
55
|
-
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
56
|
-
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
57
|
-
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
58
|
-
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY,
|
59
|
-
|
60
|
-
assert_equal '
|
61
|
-
assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, lookup_properties('user:119'))
|
56
|
+
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:123'))
|
57
|
+
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:456'))
|
58
|
+
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:789'))
|
59
|
+
assert_equal 'def', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:012'))
|
60
|
+
assert_equal 'ghi', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:428'))
|
61
|
+
assert_equal 'abc', Prefab::ConfigValueUnwrapper.unwrap(config_value, CONFIG_KEY, context_with_key('user:548'))
|
62
62
|
end
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
-
def weighted_values(values_and_weights)
|
66
|
+
def weighted_values(values_and_weights, hash_by_property_name: 'user.key')
|
67
67
|
values = values_and_weights.map do |value, weight|
|
68
68
|
weighted_value(value, weight)
|
69
69
|
end
|
70
70
|
|
71
|
-
Prefab::WeightedValues.new(weighted_values: values)
|
71
|
+
Prefab::WeightedValues.new(weighted_values: values, hash_by_property_name: hash_by_property_name)
|
72
72
|
end
|
73
73
|
|
74
74
|
def weighted_value(string, weight)
|
@@ -77,7 +77,7 @@ class TestConfigValueUnwrapper < Minitest::Test
|
|
77
77
|
)
|
78
78
|
end
|
79
79
|
|
80
|
-
def
|
81
|
-
|
80
|
+
def context_with_key(key)
|
81
|
+
Prefab::Context.new(user: { key: key })
|
82
82
|
end
|
83
83
|
end
|