yggdrasil-engine 0.0.7-x64-mingw32 → 0.0.8-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 680c78a8637ea4a1c5ea08875bbab78218560c040e90108db06c7f9f1e7435a3
4
- data.tar.gz: 8638abe970e52200ab1ac3981c3eb92192d4f34f3018855d96c4bec5e85180ef
3
+ metadata.gz: '083efa0b70e64d6b20ab1830502ea60637bfa0cb4fc9e57ae14c1cfff73f7155'
4
+ data.tar.gz: 43e4d0e94d318a809e64d355bb35d844c7fc47d9dc922731795dfde2852fa7b3
5
5
  SHA512:
6
- metadata.gz: f19bdcfc73e75276d2f15e14787ed336a0d9320a68e25478ffe5cc5d680184a63c9dcf02d61e91b0c3e55914d31faccb3f6c46a1cd70519c870539bfc6b8f2b3
7
- data.tar.gz: 0c59099f9a165ba9050a9392073420255d64fc9d816ad575e2672528dccb445df46de348c4eb2b394de055767b63151b2751e3ac551a51dd14d418bb28e6b6da
6
+ metadata.gz: 0f91c3b1086f00d445e958ca0557da4ecf9c523c9a5acae1931275a96fb4633021b91f6f7264eaf8bf559f852b51e70aa147ba908d63875d51743f6b4924ecd2
7
+ data.tar.gz: 5783acb521bdf228c30a2abb31d8dbcf95be95a6530413c5e998a283c48cb42a02606830d81eefcaab41d674273fb884a1a60d6ea99f1b69b46e9eaeff4d2444
data/README.md CHANGED
@@ -1,36 +1,36 @@
1
- # Ruby Bindings to Yggdrasil
2
-
3
- ## Running the tests
4
-
5
- First make sure that you have built the native FFI code and it's located in the right place. This can be done with the build script in `build.sh`:
6
-
7
-
8
- ```bash
9
- ./build.sh
10
- ```
11
-
12
- Then you can run the tests with:
13
-
14
- ```bash
15
- rspec
16
- ```
17
-
18
- There's also a `mem_check.rb` in the scripts folder. This is not a bullet proof test, but it can be helpful for detecting large leaks. This requires human interaction - you need to read the output and understand what it's telling you, so it's not run as part of the test suite.
19
-
20
- ```bash
21
- ruby scripts/mem_check.rb
22
- ```
23
-
24
- ## Build
25
-
26
- You can build the gem with:
27
-
28
- ```bash
29
- gem build yggdrasil-engine.gemspec
30
- ```
31
-
32
- Then you can install the gem for local development with:
33
-
34
- ```
35
- gem install yggdrasil-engine-0.0.1.gem
36
- ```
1
+ # Ruby Bindings to Yggdrasil
2
+
3
+ ## Running the tests
4
+
5
+ First make sure that you have built the native FFI code and it's located in the right place. This can be done with the build script in `build.sh`:
6
+
7
+
8
+ ```bash
9
+ ./build.sh
10
+ ```
11
+
12
+ Then you can run the tests with:
13
+
14
+ ```bash
15
+ rspec
16
+ ```
17
+
18
+ There's also a `mem_check.rb` in the scripts folder. This is not a bullet proof test, but it can be helpful for detecting large leaks. This requires human interaction - you need to read the output and understand what it's telling you, so it's not run as part of the test suite.
19
+
20
+ ```bash
21
+ ruby scripts/mem_check.rb
22
+ ```
23
+
24
+ ## Build
25
+
26
+ You can build the gem with:
27
+
28
+ ```bash
29
+ gem build yggdrasil-engine.gemspec
30
+ ```
31
+
32
+ Then you can install the gem for local development with:
33
+
34
+ ```
35
+ gem install yggdrasil-engine-0.0.1.gem
36
+ ```
@@ -1,60 +1,60 @@
1
- STANDARD_STRATEGIES = [
2
- "default",
3
- "userWithId",
4
- "gradualRolloutUserId",
5
- "gradualRolloutSessionId",
6
- "gradualRolloutRandom",
7
- "flexibleRollout",
8
- "remoteAddress",
9
- ].freeze
10
-
11
- class CustomStrategyHandler
12
- def initialize
13
- @custom_strategies_definitions = {}
14
- @custom_strategy_implementations = {}
15
- end
16
-
17
- def update_strategies(json_str)
18
- custom_strategies = {}
19
- parsed_json = JSON.parse(json_str)
20
-
21
- parsed_json["features"].each do |feature|
22
- toggle_name = feature["name"]
23
- strategies = feature["strategies"]
24
-
25
- custom_strategies_for_toggle = strategies.select do |strategy|
26
- !STANDARD_STRATEGIES.include?(strategy["name"])
27
- end
28
-
29
- unless custom_strategies_for_toggle.empty?
30
- custom_strategies[toggle_name] = custom_strategies_for_toggle
31
- end
32
- end
33
-
34
- @custom_strategies_definitions = custom_strategies
35
- end
36
-
37
- def register_custom_strategies(strategies)
38
- strategies.each do |strategy|
39
- if strategy.respond_to?(:name) && strategy.name.is_a?(String) &&
40
- strategy.respond_to?(:enabled?)
41
- @custom_strategy_implementations[strategy.name] = strategy
42
- else
43
- raise "Invalid strategy object. Must have a name method that returns a String and an enabled? method."
44
- end
45
- end
46
- end
47
-
48
- def evaluate_custom_strategies(toggle_name, context)
49
- results = {}
50
-
51
- @custom_strategies_definitions[toggle_name]&.each_with_index do |strategy, index|
52
- key = "customStrategy#{index + 1}"
53
- strategy_impl = @custom_strategy_implementations[strategy["name"]]
54
- result = strategy_impl&.enabled?(strategy["parameters"], context) || false
55
- results[key] = result
56
- end
57
-
58
- results
59
- end
60
- end
1
+ STANDARD_STRATEGIES = [
2
+ "default",
3
+ "userWithId",
4
+ "gradualRolloutUserId",
5
+ "gradualRolloutSessionId",
6
+ "gradualRolloutRandom",
7
+ "flexibleRollout",
8
+ "remoteAddress",
9
+ ].freeze
10
+
11
+ class CustomStrategyHandler
12
+ def initialize
13
+ @custom_strategies_definitions = {}
14
+ @custom_strategy_implementations = {}
15
+ end
16
+
17
+ def update_strategies(json_str)
18
+ custom_strategies = {}
19
+ parsed_json = JSON.parse(json_str)
20
+
21
+ parsed_json["features"].each do |feature|
22
+ toggle_name = feature["name"]
23
+ strategies = feature["strategies"]
24
+
25
+ custom_strategies_for_toggle = strategies.select do |strategy|
26
+ !STANDARD_STRATEGIES.include?(strategy["name"])
27
+ end
28
+
29
+ unless custom_strategies_for_toggle.empty?
30
+ custom_strategies[toggle_name] = custom_strategies_for_toggle
31
+ end
32
+ end
33
+
34
+ @custom_strategies_definitions = custom_strategies
35
+ end
36
+
37
+ def register_custom_strategies(strategies)
38
+ strategies.each do |strategy|
39
+ if strategy.respond_to?(:name) && strategy.name.is_a?(String) &&
40
+ strategy.respond_to?(:enabled?)
41
+ @custom_strategy_implementations[strategy.name] = strategy
42
+ else
43
+ raise "Invalid strategy object. Must have a name method that returns a String and an enabled? method."
44
+ end
45
+ end
46
+ end
47
+
48
+ def evaluate_custom_strategies(toggle_name, context)
49
+ results = {}
50
+
51
+ @custom_strategies_definitions[toggle_name]&.each_with_index do |strategy, index|
52
+ key = "customStrategy#{index + 1}"
53
+ strategy_impl = @custom_strategy_implementations[strategy["name"]]
54
+ result = strategy_impl&.enabled?(strategy["parameters"], context) || false
55
+ results[key] = result
56
+ end
57
+
58
+ results
59
+ end
60
+ end
@@ -1,131 +1,145 @@
1
- require 'ffi'
2
- require 'json'
3
- require 'custom_strategy'
4
-
5
- TOGGLE_MISSING_RESPONSE = 'NotFound'.freeze
6
- ERROR_RESPONSE = 'Error'.freeze
7
- OK_RESPONSE = 'Ok'.freeze
8
-
9
- def platform_specific_lib
10
- os = RbConfig::CONFIG['host_os']
11
- cpu = RbConfig::CONFIG['host_cpu']
12
-
13
- extension = case os
14
- when /darwin|mac os/
15
- 'dylib'
16
- when /linux/
17
- 'so'
18
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
19
- 'dll'
20
- else
21
- raise "unsupported platform #{os}"
22
- end
23
-
24
- arch_suffix = case cpu
25
- when /x86_64/
26
- 'x86_64'
27
- when /arm/
28
- 'arm64'
29
- when /aarch64/
30
- 'aarch64'
31
- else
32
- raise "unsupported architecture #{cpu}"
33
- end
34
-
35
- "libyggdrasilffi_#{arch_suffix}.#{extension}"
36
- end
37
-
38
- def to_variant(raw_variant)
39
- payload = raw_variant[:payload] && raw_variant[:payload].transform_keys(&:to_s)
40
- {
41
- name: raw_variant[:name],
42
- enabled: raw_variant[:enabled],
43
- feature_enabled: raw_variant[:featureEnabled],
44
- payload: payload,
45
- }
46
- end
47
-
48
- class YggdrasilEngine
49
- extend FFI::Library
50
- ffi_lib File.expand_path(platform_specific_lib, __dir__)
51
-
52
- attach_function :new_engine, [], :pointer
53
- attach_function :free_engine, [:pointer], :void
54
-
55
- attach_function :take_state, %i[pointer string], :pointer
56
- attach_function :check_enabled, %i[pointer string string string], :pointer
57
- attach_function :check_variant, %i[pointer string string string], :pointer
58
- attach_function :get_metrics, [:pointer], :pointer
59
- attach_function :free_response, [:pointer], :void
60
-
61
- attach_function :count_toggle, %i[pointer string bool], :void
62
- attach_function :count_variant, %i[pointer string string], :void
63
-
64
- def initialize
65
- @engine = YggdrasilEngine.new_engine
66
- @custom_strategy_handler = CustomStrategyHandler.new
67
- ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
68
- end
69
-
70
- def self.finalize(engine)
71
- proc { YggdrasilEngine.free_engine(engine) }
72
- end
73
-
74
- def take_state(toggles)
75
- @custom_strategy_handler.update_strategies(toggles)
76
- response_ptr = YggdrasilEngine.take_state(@engine, toggles)
77
- take_toggles_response = JSON.parse(response_ptr.read_string, symbolize_names: true)
78
- YggdrasilEngine.free_response(response_ptr)
79
- end
80
-
81
- def get_variant(name, context)
82
- context_json = (context || {}).to_json
83
- custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(name, context).to_json
84
-
85
- variant_def_json_ptr = YggdrasilEngine.check_variant(@engine, name, context_json, custom_strategy_results)
86
- variant_def_json = variant_def_json_ptr.read_string
87
- YggdrasilEngine.free_response(variant_def_json_ptr)
88
- variant_response = JSON.parse(variant_def_json, symbolize_names: true)
89
-
90
- return nil if variant_response[:status_code] == TOGGLE_MISSING_RESPONSE
91
- variant = variant_response[:value]
92
-
93
- return to_variant(variant) if variant_response[:status_code] == OK_RESPONSE
94
- end
95
-
96
- def enabled?(toggle_name, context)
97
- context_json = (context || {}).to_json
98
- custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(toggle_name, context).to_json
99
-
100
- response_ptr = YggdrasilEngine.check_enabled(@engine, toggle_name, context_json, custom_strategy_results)
101
- response_json = response_ptr.read_string
102
- YggdrasilEngine.free_response(response_ptr)
103
- response = JSON.parse(response_json, symbolize_names: true)
104
-
105
- raise "Error: #{response[:error_message]}" if response[:status_code] == ERROR_RESPONSE
106
- return nil if response[:status_code] == TOGGLE_MISSING_RESPONSE
107
-
108
- return response[:value] == true
109
- end
110
-
111
- def count_toggle(toggle_name, enabled)
112
- response_ptr = YggdrasilEngine.count_toggle(@engine, toggle_name, enabled)
113
- YggdrasilEngine.free_response(response_ptr)
114
- end
115
-
116
- def count_variant(toggle_name, variant_name)
117
- response_ptr = YggdrasilEngine.count_variant(@engine, toggle_name, variant_name)
118
- YggdrasilEngine.free_response(response_ptr)
119
- end
120
-
121
- def get_metrics
122
- metrics_ptr = YggdrasilEngine.get_metrics(@engine)
123
- metrics = JSON.parse(metrics_ptr.read_string, symbolize_names: true)
124
- YggdrasilEngine.free_response(metrics_ptr)
125
- metrics[:value]
126
- end
127
-
128
- def register_custom_strategies(strategies)
129
- @custom_strategy_handler.register_custom_strategies(strategies)
130
- end
131
- end
1
+ require 'ffi'
2
+ require 'json'
3
+ require 'custom_strategy'
4
+
5
+ TOGGLE_MISSING_RESPONSE = 'NotFound'.freeze
6
+ ERROR_RESPONSE = 'Error'.freeze
7
+ OK_RESPONSE = 'Ok'.freeze
8
+
9
+ def platform_specific_lib
10
+ os = RbConfig::CONFIG['host_os']
11
+ cpu = RbConfig::CONFIG['host_cpu']
12
+
13
+ extension, prefix = case os
14
+ when /darwin|mac os/
15
+ ['dylib', 'lib']
16
+ when /linux/
17
+ ['so', 'lib']
18
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
19
+ ['dll', '']
20
+ else
21
+ raise "unsupported platform #{os}"
22
+ end
23
+
24
+ arch_suffix = case cpu
25
+ when /x86_64/
26
+ 'x86_64'
27
+ when /arm|aarch64/
28
+ 'arm64'
29
+ else
30
+ raise "unsupported architecture #{cpu}"
31
+ end
32
+
33
+ lib_type_suffix = if os =~ /linux/
34
+ musl = system("ldd /bin/sh | grep -q musl")
35
+ musl ? "-musl" : ""
36
+ else
37
+ ""
38
+ end
39
+
40
+ "#{prefix}yggdrasilffi_#{arch_suffix}#{lib_type_suffix}.#{extension}"
41
+ end
42
+
43
+ def to_variant(raw_variant)
44
+ payload = raw_variant[:payload] && raw_variant[:payload].transform_keys(&:to_s)
45
+ {
46
+ name: raw_variant[:name],
47
+ enabled: raw_variant[:enabled],
48
+ feature_enabled: raw_variant[:featureEnabled],
49
+ payload: payload,
50
+ }
51
+ end
52
+
53
+ class YggdrasilEngine
54
+ extend FFI::Library
55
+ ffi_lib File.expand_path(platform_specific_lib, __dir__)
56
+
57
+ attach_function :new_engine, [], :pointer
58
+ attach_function :free_engine, [:pointer], :void
59
+
60
+ attach_function :take_state, %i[pointer string], :pointer
61
+ attach_function :check_enabled, %i[pointer string string string], :pointer
62
+ attach_function :check_variant, %i[pointer string string string], :pointer
63
+ attach_function :get_metrics, [:pointer], :pointer
64
+ attach_function :free_response, [:pointer], :void
65
+
66
+ attach_function :count_toggle, %i[pointer string bool], :void
67
+ attach_function :count_variant, %i[pointer string string], :void
68
+
69
+ attach_function :list_known_toggles, [:pointer], :pointer
70
+
71
+ def initialize
72
+ @engine = YggdrasilEngine.new_engine
73
+ @custom_strategy_handler = CustomStrategyHandler.new
74
+ ObjectSpace.define_finalizer(self, self.class.finalize(@engine))
75
+ end
76
+
77
+ def self.finalize(engine)
78
+ proc { YggdrasilEngine.free_engine(engine) }
79
+ end
80
+
81
+ def take_state(toggles)
82
+ @custom_strategy_handler.update_strategies(toggles)
83
+ response_ptr = YggdrasilEngine.take_state(@engine, toggles)
84
+ take_toggles_response = JSON.parse(response_ptr.read_string, symbolize_names: true)
85
+ YggdrasilEngine.free_response(response_ptr)
86
+ end
87
+
88
+ def get_variant(name, context)
89
+ context_json = (context || {}).to_json
90
+ custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(name, context).to_json
91
+
92
+ variant_def_json_ptr = YggdrasilEngine.check_variant(@engine, name, context_json, custom_strategy_results)
93
+ variant_def_json = variant_def_json_ptr.read_string
94
+ YggdrasilEngine.free_response(variant_def_json_ptr)
95
+ variant_response = JSON.parse(variant_def_json, symbolize_names: true)
96
+
97
+ return nil if variant_response[:status_code] == TOGGLE_MISSING_RESPONSE
98
+ variant = variant_response[:value]
99
+
100
+ return to_variant(variant) if variant_response[:status_code] == OK_RESPONSE
101
+ end
102
+
103
+ def enabled?(toggle_name, context)
104
+ context_json = (context || {}).to_json
105
+ custom_strategy_results = @custom_strategy_handler.evaluate_custom_strategies(toggle_name, context).to_json
106
+
107
+ response_ptr = YggdrasilEngine.check_enabled(@engine, toggle_name, context_json, custom_strategy_results)
108
+ response_json = response_ptr.read_string
109
+ YggdrasilEngine.free_response(response_ptr)
110
+ response = JSON.parse(response_json, symbolize_names: true)
111
+
112
+ raise "Error: #{response[:error_message]}" if response[:status_code] == ERROR_RESPONSE
113
+ return nil if response[:status_code] == TOGGLE_MISSING_RESPONSE
114
+
115
+ return response[:value] == true
116
+ end
117
+
118
+ def count_toggle(toggle_name, enabled)
119
+ response_ptr = YggdrasilEngine.count_toggle(@engine, toggle_name, enabled)
120
+ YggdrasilEngine.free_response(response_ptr)
121
+ end
122
+
123
+ def count_variant(toggle_name, variant_name)
124
+ response_ptr = YggdrasilEngine.count_variant(@engine, toggle_name, variant_name)
125
+ YggdrasilEngine.free_response(response_ptr)
126
+ end
127
+
128
+ def get_metrics
129
+ metrics_ptr = YggdrasilEngine.get_metrics(@engine)
130
+ metrics = JSON.parse(metrics_ptr.read_string, symbolize_names: true)
131
+ YggdrasilEngine.free_response(metrics_ptr)
132
+ metrics[:value]
133
+ end
134
+
135
+ def list_known_toggles
136
+ response_ptr = YggdrasilEngine.list_known_toggles(@engine)
137
+ response_json = response_ptr.read_string
138
+ YggdrasilEngine.free_response(response_ptr)
139
+ JSON.parse(response_json, symbolize_names: true)
140
+ end
141
+
142
+ def register_custom_strategies(strategies)
143
+ @custom_strategy_handler.register_custom_strategies(strategies)
144
+ end
145
+ end
Binary file
@@ -1,136 +1,136 @@
1
- require_relative '../lib/custom_strategy'
2
-
3
- RSpec.describe "custom strategies" do
4
- let(:raw_state) do
5
- {
6
- "version": 1,
7
- "features": [
8
- {
9
- "name": "Feature.A",
10
- "enabled": true,
11
- "strategies": [
12
- {
13
- "name": "default",
14
- "parameters": {}
15
- },
16
- {
17
- "name": "custom",
18
- "parameters": {
19
- "gerkhins": "yes"
20
- }
21
- },
22
- {
23
- "name": "some-other-custom",
24
- "parameters": {
25
- "gerkhins": "yes"
26
- }
27
- },
28
- ]
29
- }
30
- ]
31
- }
32
- end
33
-
34
- let(:handler) { CustomStrategyHandler.new }
35
-
36
- before do
37
- handler.update_strategies(raw_state.to_json)
38
- end
39
-
40
- describe 'computing custom strategies' do
41
- it 'respects the logic contained in the enabled function' do
42
- class TestStrategy
43
- attr_reader :name
44
-
45
- def initialize(name)
46
- @name = name
47
- end
48
-
49
- def enabled?(params, context)
50
- params["gerkhins"] == "yes"
51
- end
52
- end
53
-
54
- handler.register_custom_strategies([TestStrategy.new("custom")])
55
- strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
56
- expect(strategy_results.length).to eq(2)
57
- expect(strategy_results["customStrategy1"]).to eq(true)
58
- expect(strategy_results["customStrategy2"]).to eq(false)
59
- end
60
-
61
- it 'creates a strategy result for every custom strategy thats implemented and defined' do
62
- class TestStrategy
63
- attr_reader :name
64
-
65
- def initialize(name)
66
- @name = name
67
- end
68
-
69
- def enabled?(params, context)
70
- params["gerkhins"] == "yes"
71
- end
72
- end
73
-
74
- handler.register_custom_strategies([TestStrategy.new("custom"), TestStrategy.new("some-other-custom")])
75
- strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
76
- expect(strategy_results.length).to eq(2)
77
- expect(strategy_results["customStrategy1"]).to eq(true)
78
- expect(strategy_results["customStrategy2"]).to eq(true)
79
- end
80
-
81
- it 'returns false for missing implementations' do
82
- handler.register_custom_strategies([])
83
- strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
84
- expect(strategy_results.length).to eq(2)
85
- expect(strategy_results["customStrategy1"]).to eq(false)
86
- end
87
-
88
- it "should calculate custom strategies e2e" do
89
- class TestStrategy
90
- attr_reader :name
91
-
92
- def initialize(name)
93
- @name = name
94
- end
95
-
96
- def enabled?(params, context)
97
- context[:userId] == "123"
98
- end
99
- end
100
-
101
- state = {
102
- "version": 1,
103
- "features": [
104
- {
105
- "name": "Feature.A",
106
- "enabled": true,
107
- "strategies": [
108
- {
109
- "name": "custom",
110
- "parameters": {
111
- "gerkhins": "yes"
112
- }
113
- }
114
- ]
115
- }
116
- ]
117
- }
118
-
119
- engine = YggdrasilEngine.new
120
- engine.register_custom_strategies([TestStrategy.new("custom")])
121
-
122
- engine.take_state(state.to_json)
123
-
124
- should_be_enabled = engine.enabled?("Feature.A", {
125
- userId: "123"
126
- })
127
-
128
- should_not_be_enabled = engine.enabled?("Feature.A", {
129
- userId: "456"
130
- })
131
-
132
- expect(should_be_enabled).to eq(true)
133
- expect(should_not_be_enabled).to eq(false)
134
- end
135
- end
136
- end
1
+ require_relative '../lib/custom_strategy'
2
+
3
+ RSpec.describe "custom strategies" do
4
+ let(:raw_state) do
5
+ {
6
+ "version": 1,
7
+ "features": [
8
+ {
9
+ "name": "Feature.A",
10
+ "enabled": true,
11
+ "strategies": [
12
+ {
13
+ "name": "default",
14
+ "parameters": {}
15
+ },
16
+ {
17
+ "name": "custom",
18
+ "parameters": {
19
+ "gerkhins": "yes"
20
+ }
21
+ },
22
+ {
23
+ "name": "some-other-custom",
24
+ "parameters": {
25
+ "gerkhins": "yes"
26
+ }
27
+ },
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ end
33
+
34
+ let(:handler) { CustomStrategyHandler.new }
35
+
36
+ before do
37
+ handler.update_strategies(raw_state.to_json)
38
+ end
39
+
40
+ describe 'computing custom strategies' do
41
+ it 'respects the logic contained in the enabled function' do
42
+ class TestStrategy
43
+ attr_reader :name
44
+
45
+ def initialize(name)
46
+ @name = name
47
+ end
48
+
49
+ def enabled?(params, context)
50
+ params["gerkhins"] == "yes"
51
+ end
52
+ end
53
+
54
+ handler.register_custom_strategies([TestStrategy.new("custom")])
55
+ strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
56
+ expect(strategy_results.length).to eq(2)
57
+ expect(strategy_results["customStrategy1"]).to eq(true)
58
+ expect(strategy_results["customStrategy2"]).to eq(false)
59
+ end
60
+
61
+ it 'creates a strategy result for every custom strategy thats implemented and defined' do
62
+ class TestStrategy
63
+ attr_reader :name
64
+
65
+ def initialize(name)
66
+ @name = name
67
+ end
68
+
69
+ def enabled?(params, context)
70
+ params["gerkhins"] == "yes"
71
+ end
72
+ end
73
+
74
+ handler.register_custom_strategies([TestStrategy.new("custom"), TestStrategy.new("some-other-custom")])
75
+ strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
76
+ expect(strategy_results.length).to eq(2)
77
+ expect(strategy_results["customStrategy1"]).to eq(true)
78
+ expect(strategy_results["customStrategy2"]).to eq(true)
79
+ end
80
+
81
+ it 'returns false for missing implementations' do
82
+ handler.register_custom_strategies([])
83
+ strategy_results = handler.evaluate_custom_strategies("Feature.A", {})
84
+ expect(strategy_results.length).to eq(2)
85
+ expect(strategy_results["customStrategy1"]).to eq(false)
86
+ end
87
+
88
+ it "should calculate custom strategies e2e" do
89
+ class TestStrategy
90
+ attr_reader :name
91
+
92
+ def initialize(name)
93
+ @name = name
94
+ end
95
+
96
+ def enabled?(params, context)
97
+ context[:userId] == "123"
98
+ end
99
+ end
100
+
101
+ state = {
102
+ "version": 1,
103
+ "features": [
104
+ {
105
+ "name": "Feature.A",
106
+ "enabled": true,
107
+ "strategies": [
108
+ {
109
+ "name": "custom",
110
+ "parameters": {
111
+ "gerkhins": "yes"
112
+ }
113
+ }
114
+ ]
115
+ }
116
+ ]
117
+ }
118
+
119
+ engine = YggdrasilEngine.new
120
+ engine.register_custom_strategies([TestStrategy.new("custom")])
121
+
122
+ engine.take_state(state.to_json)
123
+
124
+ should_be_enabled = engine.enabled?("Feature.A", {
125
+ userId: "123"
126
+ })
127
+
128
+ should_not_be_enabled = engine.enabled?("Feature.A", {
129
+ userId: "456"
130
+ })
131
+
132
+ expect(should_be_enabled).to eq(true)
133
+ expect(should_not_be_enabled).to eq(false)
134
+ end
135
+ end
136
+ end
@@ -1,151 +1,161 @@
1
- require 'rspec'
2
- require 'json'
3
- require_relative '../lib/yggdrasil_engine'
4
-
5
- index_file_path = '../client-specification/specifications/index.json'
6
- test_suites = JSON.parse(File.read(index_file_path))
7
-
8
- def test_suite_variant(base_variant)
9
- payload = base_variant[:payload] && base_variant[:payload].transform_keys(&:to_s)
10
- {
11
- name: base_variant[:name],
12
- enabled: base_variant[:enabled],
13
- feature_enabled: base_variant[:feature_enabled],
14
- payload: payload,
15
- }
16
- end
17
-
18
- RSpec.describe YggdrasilEngine do
19
- let(:yggdrasil_engine) { YggdrasilEngine.new }
20
-
21
- describe '#checking a toggle' do
22
- it 'that does not exist should yield a not found' do
23
- is_enabled = yggdrasil_engine.enabled?('missing-toggle', {})
24
- expect(is_enabled).to be_nil
25
- end
26
- end
27
-
28
- describe '#metrics' do
29
- it 'should clear metrics when get_metrics is called' do
30
- feature_name = 'Feature.A'
31
-
32
- suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
33
- suite_data = JSON.parse(File.read(suite_path))
34
-
35
- yggdrasil_engine.take_state(suite_data['state'].to_json)
36
-
37
- yggdrasil_engine.count_toggle(feature_name, true)
38
- yggdrasil_engine.count_toggle(feature_name, false)
39
-
40
- metrics = yggdrasil_engine.get_metrics() # This should clear the metrics buffer
41
-
42
- metric = metrics[:toggles][feature_name.to_sym]
43
- expect(metric[:yes]).to eq(1)
44
- expect(metric[:no]).to eq(1)
45
-
46
- metrics = yggdrasil_engine.get_metrics()
47
- expect(metrics).to be_nil
48
- end
49
-
50
- it 'should increment toggle count when it exists' do
51
- toggle_name = 'Feature.A'
52
-
53
- suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
54
- suite_data = JSON.parse(File.read(suite_path))
55
-
56
- yggdrasil_engine.take_state(suite_data['state'].to_json)
57
-
58
- yggdrasil_engine.count_toggle(toggle_name, true)
59
- yggdrasil_engine.count_toggle(toggle_name, false)
60
-
61
- metrics = yggdrasil_engine.get_metrics()
62
- metric = metrics[:toggles][toggle_name.to_sym]
63
-
64
- expect(metric[:yes]).to eq(1)
65
- expect(metric[:no]).to eq(1)
66
- end
67
-
68
- it 'should increment toggle count when the toggle does not exist' do
69
- toggle_name = 'Feature.X'
70
-
71
- yggdrasil_engine.count_toggle(toggle_name, true)
72
- yggdrasil_engine.count_toggle(toggle_name, false)
73
-
74
- metrics = yggdrasil_engine.get_metrics()
75
- metric = metrics[:toggles][toggle_name.to_sym]
76
-
77
- expect(metric[:yes]).to eq(1)
78
- expect(metric[:no]).to eq(1)
79
- end
80
-
81
- it 'should increment variant' do
82
- toggle_name = 'Feature.Q'
83
-
84
- suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
85
- suite_data = JSON.parse(File.read(suite_path))
86
-
87
- yggdrasil_engine.take_state(suite_data['state'].to_json)
88
-
89
- yggdrasil_engine.count_variant(toggle_name, 'disabled')
90
-
91
- metrics = yggdrasil_engine.get_metrics()
92
- metric = metrics[:toggles][toggle_name.to_sym]
93
-
94
- expect(metric[:variants][:disabled]).to eq(1)
95
- end
96
- end
97
- end
98
-
99
- RSpec.describe 'Client Specification' do
100
- let(:yggdrasil_engine) { YggdrasilEngine.new }
101
-
102
- test_suites.each do |suite|
103
- suite_path = File.join('../client-specification/specifications', suite)
104
- suite_data = JSON.parse(File.read(suite_path), symbolize_names: true)
105
-
106
- describe "Suite '#{suite}'" do
107
- before(:each) do
108
- yggdrasil_engine.take_state(suite_data[:state].to_json)
109
- end
110
-
111
- suite_data.fetch(:tests, []).each do |test|
112
- describe "Test '#{test[:description]}'" do
113
- let(:context) { test[:context] }
114
- let(:toggle_name) { test[:toggleName] }
115
- let(:expected_result) { test[:expectedResult] }
116
-
117
- it 'returns correct result for `is_enabled?` method' do
118
- result = yggdrasil_engine.enabled?(toggle_name, context) || false
119
-
120
- expect(result).to eq(expected_result),
121
- "Failed test '#{test['description']}': expected #{expected_result}, got #{result}"
122
- end
123
- end
124
- end
125
-
126
- suite_data.fetch(:variantTests, []).each do |test|
127
- next unless test[:expectedResult]
128
-
129
- describe "Variant Test '#{test[:description]}'" do
130
- let(:context) { test[:context] }
131
- let(:toggle_name) { test[:toggleName] }
132
- let(:expected_result) { test_suite_variant(test[:expectedResult]) }
133
-
134
- it 'returns correct result for `get_variant` method' do
135
- result = yggdrasil_engine.get_variant(toggle_name, context) || {
136
- :name => 'disabled',
137
- :payload => nil,
138
- :enabled => false,
139
- :feature_enabled => false
140
- }
141
-
142
- expect(result[:name]).to eq(expected_result[:name])
143
- expect(result[:payload]).to eq(expected_result[:payload])
144
- expect(result[:enabled]).to eq(expected_result[:enabled])
145
- expect(result[:feature_enabled]).to eq(expected_result[:feature_enabled])
146
- end
147
- end
148
- end
149
- end
150
- end
151
- end
1
+ require 'rspec'
2
+ require 'json'
3
+ require_relative '../lib/yggdrasil_engine'
4
+
5
+ index_file_path = '../client-specification/specifications/index.json'
6
+ test_suites = JSON.parse(File.read(index_file_path))
7
+
8
+ def test_suite_variant(base_variant)
9
+ payload = base_variant[:payload] && base_variant[:payload].transform_keys(&:to_s)
10
+ {
11
+ name: base_variant[:name],
12
+ enabled: base_variant[:enabled],
13
+ feature_enabled: base_variant[:feature_enabled],
14
+ payload: payload,
15
+ }
16
+ end
17
+
18
+ RSpec.describe YggdrasilEngine do
19
+ let(:yggdrasil_engine) { YggdrasilEngine.new }
20
+
21
+ describe '#checking a toggle' do
22
+ it 'that does not exist should yield a not found' do
23
+ is_enabled = yggdrasil_engine.enabled?('missing-toggle', {})
24
+ expect(is_enabled).to be_nil
25
+ end
26
+ end
27
+
28
+ describe '#metrics' do
29
+ it 'should clear metrics when get_metrics is called' do
30
+ feature_name = 'Feature.A'
31
+
32
+ suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
33
+ suite_data = JSON.parse(File.read(suite_path))
34
+
35
+ yggdrasil_engine.take_state(suite_data['state'].to_json)
36
+
37
+ yggdrasil_engine.count_toggle(feature_name, true)
38
+ yggdrasil_engine.count_toggle(feature_name, false)
39
+
40
+ metrics = yggdrasil_engine.get_metrics() # This should clear the metrics buffer
41
+
42
+ metric = metrics[:toggles][feature_name.to_sym]
43
+ expect(metric[:yes]).to eq(1)
44
+ expect(metric[:no]).to eq(1)
45
+
46
+ metrics = yggdrasil_engine.get_metrics()
47
+ expect(metrics).to be_nil
48
+ end
49
+
50
+ it 'should increment toggle count when it exists' do
51
+ toggle_name = 'Feature.A'
52
+
53
+ suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
54
+ suite_data = JSON.parse(File.read(suite_path))
55
+
56
+ yggdrasil_engine.take_state(suite_data['state'].to_json)
57
+
58
+ yggdrasil_engine.count_toggle(toggle_name, true)
59
+ yggdrasil_engine.count_toggle(toggle_name, false)
60
+
61
+ metrics = yggdrasil_engine.get_metrics()
62
+ metric = metrics[:toggles][toggle_name.to_sym]
63
+
64
+ expect(metric[:yes]).to eq(1)
65
+ expect(metric[:no]).to eq(1)
66
+ end
67
+
68
+ it 'should increment toggle count when the toggle does not exist' do
69
+ toggle_name = 'Feature.X'
70
+
71
+ yggdrasil_engine.count_toggle(toggle_name, true)
72
+ yggdrasil_engine.count_toggle(toggle_name, false)
73
+
74
+ metrics = yggdrasil_engine.get_metrics()
75
+ metric = metrics[:toggles][toggle_name.to_sym]
76
+
77
+ expect(metric[:yes]).to eq(1)
78
+ expect(metric[:no]).to eq(1)
79
+ end
80
+
81
+ it 'should increment variant' do
82
+ toggle_name = 'Feature.Q'
83
+
84
+ suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
85
+ suite_data = JSON.parse(File.read(suite_path))
86
+
87
+ yggdrasil_engine.take_state(suite_data['state'].to_json)
88
+
89
+ yggdrasil_engine.count_variant(toggle_name, 'disabled')
90
+
91
+ metrics = yggdrasil_engine.get_metrics()
92
+ metric = metrics[:toggles][toggle_name.to_sym]
93
+
94
+ expect(metric[:variants][:disabled]).to eq(1)
95
+ end
96
+
97
+ it 'should list all the features that were loaded' do
98
+ suite_path = File.join('../client-specification/specifications', '01-simple-examples.json')
99
+ suite_data = JSON.parse(File.read(suite_path))
100
+
101
+ yggdrasil_engine.take_state(suite_data['state'].to_json)
102
+
103
+ toggles = yggdrasil_engine.list_known_toggles()
104
+ expect(toggles.length).to eq(3)
105
+ end
106
+ end
107
+ end
108
+
109
+ RSpec.describe 'Client Specification' do
110
+ let(:yggdrasil_engine) { YggdrasilEngine.new }
111
+
112
+ test_suites.each do |suite|
113
+ suite_path = File.join('../client-specification/specifications', suite)
114
+ suite_data = JSON.parse(File.read(suite_path), symbolize_names: true)
115
+
116
+ describe "Suite '#{suite}'" do
117
+ before(:each) do
118
+ yggdrasil_engine.take_state(suite_data[:state].to_json)
119
+ end
120
+
121
+ suite_data.fetch(:tests, []).each do |test|
122
+ describe "Test '#{test[:description]}'" do
123
+ let(:context) { test[:context] }
124
+ let(:toggle_name) { test[:toggleName] }
125
+ let(:expected_result) { test[:expectedResult] }
126
+
127
+ it 'returns correct result for `is_enabled?` method' do
128
+ result = yggdrasil_engine.enabled?(toggle_name, context) || false
129
+
130
+ expect(result).to eq(expected_result),
131
+ "Failed test '#{test['description']}': expected #{expected_result}, got #{result}"
132
+ end
133
+ end
134
+ end
135
+
136
+ suite_data.fetch(:variantTests, []).each do |test|
137
+ next unless test[:expectedResult]
138
+
139
+ describe "Variant Test '#{test[:description]}'" do
140
+ let(:context) { test[:context] }
141
+ let(:toggle_name) { test[:toggleName] }
142
+ let(:expected_result) { test_suite_variant(test[:expectedResult]) }
143
+
144
+ it 'returns correct result for `get_variant` method' do
145
+ result = yggdrasil_engine.get_variant(toggle_name, context) || {
146
+ :name => 'disabled',
147
+ :payload => nil,
148
+ :enabled => false,
149
+ :feature_enabled => false
150
+ }
151
+
152
+ expect(result[:name]).to eq(expected_result[:name])
153
+ expect(result[:payload]).to eq(expected_result[:payload])
154
+ expect(result[:enabled]).to eq(expected_result[:enabled])
155
+ expect(result[:feature_enabled]).to eq(expected_result[:feature_enabled])
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yggdrasil-engine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.7
4
+ version: 0.0.8
5
5
  platform: x64-mingw32
6
6
  authors:
7
7
  - Unleash
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2023-06-28 00:00:00.000000000 Z
@@ -32,15 +32,16 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - README.md
34
34
  - lib/custom_strategy.rb
35
- - lib/libyggdrasilffi_x86_64.dll
36
35
  - lib/yggdrasil_engine.rb
36
+ - lib/yggdrasilffi_x86_64.dll
37
37
  - spec/custom_strategy_spec.rb
38
38
  - spec/yggdrasil_engine_spec.rb
39
39
  homepage: http://github.com/username/my_gem
40
40
  licenses:
41
41
  - MIT
42
- metadata: {}
43
- post_install_message:
42
+ metadata:
43
+ yggdrasil_core_version: 0.14.0
44
+ post_install_message:
44
45
  rdoc_options: []
45
46
  require_paths:
46
47
  - lib
@@ -55,8 +56,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
55
56
  - !ruby/object:Gem::Version
56
57
  version: '0'
57
58
  requirements: []
58
- rubygems_version: 3.2.33
59
- signing_key:
59
+ rubygems_version: 3.3.5
60
+ signing_key:
60
61
  specification_version: 4
61
62
  summary: Unleash engine for evaluating feature toggles
62
63
  test_files: []
Binary file