devcycle-ruby-server-sdk 3.6.1 → 3.7.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/lib/devcycle-ruby-server-sdk/api/client.rb +102 -26
- data/lib/devcycle-ruby-server-sdk/api/dev_cycle_provider.rb +32 -5
- data/lib/devcycle-ruby-server-sdk/eval_hooks_runner.rb +135 -0
- data/lib/devcycle-ruby-server-sdk/eval_reasons.rb +13 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm +0 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams.proto +8 -0
- data/lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb +25 -59
- data/lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh +2 -2
- data/lib/devcycle-ruby-server-sdk/models/eval_hook.rb +28 -0
- data/lib/devcycle-ruby-server-sdk/models/eval_hook_context.rb +22 -0
- data/lib/devcycle-ruby-server-sdk/models/variable.rb +9 -1
- data/lib/devcycle-ruby-server-sdk/version.rb +1 -1
- data/lib/devcycle-ruby-server-sdk.rb +6 -0
- data/spec/api/devcycle_api_spec.rb +11 -9
- data/spec/devcycle_provider_spec.rb +157 -7
- data/spec/eval_hooks_runner_spec.rb +410 -0
- data/spec/eval_hooks_spec.rb +245 -0
- metadata +10 -2
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DevCycle::Client do
|
4
|
+
let(:test_user) { DevCycle::User.new(user_id: 'test-user', email: 'test@example.com') }
|
5
|
+
let(:test_key) { 'test-variable' }
|
6
|
+
let(:test_default) { 'default-value' }
|
7
|
+
let(:options) { DevCycle::Options.new }
|
8
|
+
|
9
|
+
# Use unique SDK keys for each test to avoid WASM initialization conflicts
|
10
|
+
let(:valid_sdk_key) { "server-test-key-#{SecureRandom.hex(4)}" }
|
11
|
+
let(:client) { DevCycle::Client.new(valid_sdk_key, options) }
|
12
|
+
|
13
|
+
after(:each) do
|
14
|
+
client.close if client.respond_to?(:close)
|
15
|
+
end
|
16
|
+
|
17
|
+
describe 'eval hooks functionality' do
|
18
|
+
context 'hook management' do
|
19
|
+
it 'initializes with an empty eval hooks runner' do
|
20
|
+
expect(client.instance_variable_get(:@eval_hooks_runner)).to be_a(DevCycle::EvalHooksRunner)
|
21
|
+
expect(client.instance_variable_get(:@eval_hooks_runner).eval_hooks).to be_empty
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can add eval hooks' do
|
25
|
+
hook = DevCycle::EvalHook.new
|
26
|
+
client.add_eval_hook(hook)
|
27
|
+
expect(client.instance_variable_get(:@eval_hooks_runner).eval_hooks).to include(hook)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can clear eval hooks' do
|
31
|
+
hook = DevCycle::EvalHook.new
|
32
|
+
client.add_eval_hook(hook)
|
33
|
+
expect(client.instance_variable_get(:@eval_hooks_runner).eval_hooks).not_to be_empty
|
34
|
+
|
35
|
+
client.clear_eval_hooks
|
36
|
+
expect(client.instance_variable_get(:@eval_hooks_runner).eval_hooks).to be_empty
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'variable evaluation with hooks' do
|
41
|
+
it 'runs before hooks before variable evaluation' do
|
42
|
+
before_hook_called = false
|
43
|
+
hook = DevCycle::EvalHook.new(
|
44
|
+
before: ->(context) {
|
45
|
+
before_hook_called = true
|
46
|
+
expect(context.key).to eq(test_key)
|
47
|
+
expect(context.user).to eq(test_user)
|
48
|
+
expect(context.default_value).to eq(test_default)
|
49
|
+
context
|
50
|
+
}
|
51
|
+
)
|
52
|
+
client.add_eval_hook(hook)
|
53
|
+
|
54
|
+
result = client.variable(test_user, test_key, test_default)
|
55
|
+
expect(before_hook_called).to be true
|
56
|
+
expect(result.isDefaulted).to be true
|
57
|
+
expect(result.value).to eq(test_default)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'runs after hooks after successful variable evaluation' do
|
61
|
+
after_hook_called = false
|
62
|
+
hook = DevCycle::EvalHook.new(
|
63
|
+
after: ->(context) {
|
64
|
+
after_hook_called = true
|
65
|
+
expect(context.key).to eq(test_key)
|
66
|
+
expect(context.user).to eq(test_user)
|
67
|
+
expect(context.default_value).to eq(test_default)
|
68
|
+
}
|
69
|
+
)
|
70
|
+
client.add_eval_hook(hook)
|
71
|
+
|
72
|
+
result = client.variable(test_user, test_key, test_default)
|
73
|
+
expect(after_hook_called).to be true
|
74
|
+
expect(result.isDefaulted).to be true
|
75
|
+
expect(result.value).to eq(test_default)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'runs error hooks when variable evaluation fails' do
|
79
|
+
error_hook_called = false
|
80
|
+
hook = DevCycle::EvalHook.new(
|
81
|
+
error: ->(context, error) {
|
82
|
+
error_hook_called = true
|
83
|
+
expect(context.key).to eq(test_key)
|
84
|
+
expect(context.user).to eq(test_user)
|
85
|
+
expect(context.default_value).to eq(test_default)
|
86
|
+
}
|
87
|
+
)
|
88
|
+
client.add_eval_hook(hook)
|
89
|
+
|
90
|
+
# Force an error by making determine_variable_type raise an error
|
91
|
+
allow(client).to receive(:determine_variable_type).and_raise(StandardError, 'Variable type error')
|
92
|
+
|
93
|
+
client.variable(test_user, test_key, test_default)
|
94
|
+
expect(error_hook_called).to be true
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'runs finally hooks regardless of success or failure' do
|
98
|
+
finally_hook_called = false
|
99
|
+
hook = DevCycle::EvalHook.new(
|
100
|
+
on_finally: ->(context) {
|
101
|
+
finally_hook_called = true
|
102
|
+
expect(context.key).to eq(test_key)
|
103
|
+
expect(context.user).to eq(test_user)
|
104
|
+
expect(context.default_value).to eq(test_default)
|
105
|
+
}
|
106
|
+
)
|
107
|
+
client.add_eval_hook(hook)
|
108
|
+
|
109
|
+
result = client.variable(test_user, test_key, test_default)
|
110
|
+
expect(finally_hook_called).to be true
|
111
|
+
expect(result.isDefaulted).to be true
|
112
|
+
expect(result.value).to eq(test_default)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'skips after hooks when before hook raises an error' do
|
116
|
+
before_hook_called = false
|
117
|
+
after_hook_called = false
|
118
|
+
error_hook_called = false
|
119
|
+
finally_hook_called = false
|
120
|
+
|
121
|
+
hook = DevCycle::EvalHook.new(
|
122
|
+
before: ->(context) {
|
123
|
+
before_hook_called = true
|
124
|
+
raise StandardError, 'Before hook error'
|
125
|
+
},
|
126
|
+
after: ->(context) {
|
127
|
+
after_hook_called = true
|
128
|
+
},
|
129
|
+
error: ->(context, error) {
|
130
|
+
error_hook_called = true
|
131
|
+
expect(error).to be_a(StandardError)
|
132
|
+
expect(error.message).to include('Before hook error')
|
133
|
+
},
|
134
|
+
on_finally: ->(context) {
|
135
|
+
finally_hook_called = true
|
136
|
+
}
|
137
|
+
)
|
138
|
+
client.add_eval_hook(hook)
|
139
|
+
|
140
|
+
client.variable(test_user, test_key, test_default)
|
141
|
+
expect(before_hook_called).to be true
|
142
|
+
expect(after_hook_called).to be false
|
143
|
+
expect(error_hook_called).to be true
|
144
|
+
expect(finally_hook_called).to be true
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'runs multiple hooks in order' do
|
148
|
+
execution_order = []
|
149
|
+
|
150
|
+
hook1 = DevCycle::EvalHook.new(
|
151
|
+
before: ->(context) {
|
152
|
+
execution_order << 'hook1_before'
|
153
|
+
context
|
154
|
+
},
|
155
|
+
after: ->(context) {
|
156
|
+
execution_order << 'hook1_after'
|
157
|
+
},
|
158
|
+
on_finally: ->(context) {
|
159
|
+
execution_order << 'hook1_finally'
|
160
|
+
}
|
161
|
+
)
|
162
|
+
|
163
|
+
hook2 = DevCycle::EvalHook.new(
|
164
|
+
before: ->(context) {
|
165
|
+
execution_order << 'hook2_before'
|
166
|
+
context
|
167
|
+
},
|
168
|
+
after: ->(context) {
|
169
|
+
execution_order << 'hook2_after'
|
170
|
+
},
|
171
|
+
on_finally: ->(context) {
|
172
|
+
execution_order << 'hook2_finally'
|
173
|
+
}
|
174
|
+
)
|
175
|
+
|
176
|
+
client.add_eval_hook(hook1)
|
177
|
+
client.add_eval_hook(hook2)
|
178
|
+
|
179
|
+
result = client.variable(test_user, test_key, test_default)
|
180
|
+
|
181
|
+
expect(execution_order).to eq([
|
182
|
+
'hook1_before', 'hook2_before',
|
183
|
+
'hook1_after', 'hook2_after',
|
184
|
+
'hook1_finally', 'hook2_finally'
|
185
|
+
])
|
186
|
+
expect(result.isDefaulted).to be true
|
187
|
+
expect(result.value).to eq(test_default)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'allows before hooks to modify context' do
|
191
|
+
modified_context = nil
|
192
|
+
hook = DevCycle::EvalHook.new(
|
193
|
+
before: ->(context) {
|
194
|
+
# Modify the context
|
195
|
+
context.key = 'modified-key'
|
196
|
+
context.user = DevCycle::User.new(user_id: 'modified-user', email: 'modified@example.com')
|
197
|
+
context
|
198
|
+
},
|
199
|
+
after: ->(context) {
|
200
|
+
modified_context = context
|
201
|
+
}
|
202
|
+
)
|
203
|
+
client.add_eval_hook(hook)
|
204
|
+
|
205
|
+
result = client.variable(test_user, test_key, test_default)
|
206
|
+
|
207
|
+
expect(modified_context.key).to eq('modified-key')
|
208
|
+
expect(modified_context.user).to eq(DevCycle::User.new(user_id: 'modified-user', email: 'modified@example.com'))
|
209
|
+
expect(result.isDefaulted).to be true
|
210
|
+
expect(result.value).to eq(test_default)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'works with different variable types' do
|
214
|
+
# Test with boolean default
|
215
|
+
boolean_hook_called = false
|
216
|
+
boolean_hook = DevCycle::EvalHook.new(
|
217
|
+
after: ->(context) {
|
218
|
+
boolean_hook_called = true
|
219
|
+
}
|
220
|
+
)
|
221
|
+
client.add_eval_hook(boolean_hook)
|
222
|
+
|
223
|
+
boolean_result = client.variable(test_user, 'boolean-test', true)
|
224
|
+
expect(boolean_hook_called).to be true
|
225
|
+
expect(boolean_result.isDefaulted).to be true
|
226
|
+
expect(boolean_result.value).to eq(true)
|
227
|
+
|
228
|
+
# Test with number default
|
229
|
+
number_hook_called = false
|
230
|
+
number_hook = DevCycle::EvalHook.new(
|
231
|
+
after: ->(context) {
|
232
|
+
number_hook_called = true
|
233
|
+
}
|
234
|
+
)
|
235
|
+
client.add_eval_hook(number_hook)
|
236
|
+
|
237
|
+
number_result = client.variable(test_user, 'number-test', 42)
|
238
|
+
expect(number_hook_called).to be true
|
239
|
+
expect(number_result.isDefaulted).to be true
|
240
|
+
expect(number_result.value).to eq(42)
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: devcycle-ruby-server-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- DevCycleHQ
|
@@ -165,6 +165,8 @@ files:
|
|
165
165
|
- lib/devcycle-ruby-server-sdk/api_client.rb
|
166
166
|
- lib/devcycle-ruby-server-sdk/api_error.rb
|
167
167
|
- lib/devcycle-ruby-server-sdk/configuration.rb
|
168
|
+
- lib/devcycle-ruby-server-sdk/eval_hooks_runner.rb
|
169
|
+
- lib/devcycle-ruby-server-sdk/eval_reasons.rb
|
168
170
|
- lib/devcycle-ruby-server-sdk/localbucketing/bucketed_user_config.rb
|
169
171
|
- lib/devcycle-ruby-server-sdk/localbucketing/bucketing-lib.release.wasm
|
170
172
|
- lib/devcycle-ruby-server-sdk/localbucketing/config_manager.rb
|
@@ -179,6 +181,8 @@ files:
|
|
179
181
|
- lib/devcycle-ruby-server-sdk/localbucketing/proto/variableForUserParams_pb.rb
|
180
182
|
- lib/devcycle-ruby-server-sdk/localbucketing/update_wasm.sh
|
181
183
|
- lib/devcycle-ruby-server-sdk/models/error_response.rb
|
184
|
+
- lib/devcycle-ruby-server-sdk/models/eval_hook.rb
|
185
|
+
- lib/devcycle-ruby-server-sdk/models/eval_hook_context.rb
|
182
186
|
- lib/devcycle-ruby-server-sdk/models/event.rb
|
183
187
|
- lib/devcycle-ruby-server-sdk/models/feature.rb
|
184
188
|
- lib/devcycle-ruby-server-sdk/models/inline_response201.rb
|
@@ -190,6 +194,8 @@ files:
|
|
190
194
|
- spec/api_client_spec.rb
|
191
195
|
- spec/configuration_spec.rb
|
192
196
|
- spec/devcycle_provider_spec.rb
|
197
|
+
- spec/eval_hooks_runner_spec.rb
|
198
|
+
- spec/eval_hooks_spec.rb
|
193
199
|
- spec/spec_helper.rb
|
194
200
|
homepage: https://devcycle.com
|
195
201
|
licenses:
|
@@ -209,7 +215,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
209
215
|
- !ruby/object:Gem::Version
|
210
216
|
version: '0'
|
211
217
|
requirements: []
|
212
|
-
rubygems_version: 3.6.
|
218
|
+
rubygems_version: 3.6.9
|
213
219
|
specification_version: 4
|
214
220
|
summary: DevCycle Bucketing API Ruby Gem
|
215
221
|
test_files:
|
@@ -217,4 +223,6 @@ test_files:
|
|
217
223
|
- spec/api_client_spec.rb
|
218
224
|
- spec/configuration_spec.rb
|
219
225
|
- spec/devcycle_provider_spec.rb
|
226
|
+
- spec/eval_hooks_runner_spec.rb
|
227
|
+
- spec/eval_hooks_spec.rb
|
220
228
|
- spec/spec_helper.rb
|