prefab-cloud-ruby 0.23.8 → 0.24.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/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
|