Skip to content

Commit b054dc4

Browse files
committed
Created test cases
1 parent 13c6a57 commit b054dc4

File tree

1 file changed

+209
-1
lines changed

1 file changed

+209
-1
lines changed

spec/cmab_client_spec.rb

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,214 @@
1919
require 'optimizely/cmab/cmab_client'
2020

2121
describe Optimizely::CmabClient do
22-
before(:context) do
22+
let(:mock_http_client) { double('http_client') }
23+
let(:mock_logger) { double('logger') }
24+
let(:retry_config) { Optimizely::CmabRetryConfig.new(max_retries: 3, initial_backoff: 0.01, max_backoff: 1, backoff_multiplier: 2) }
25+
let(:client) { described_class.new(http_client: mock_http_client, logger: mock_logger, retry_config: nil) }
26+
let(:rule_id) { 'test_rule' }
27+
let(:user_id) { 'user123' }
28+
let(:attributes) { {'attr1': 'value1', 'attr2': 'value2'} }
29+
let(:cmab_uuid) { 'uuid-1234' }
30+
let(:expected_url) { "https://prediction.cmab.optimizely.com/predict/#{rule_id}" }
31+
let(:expected_body) do
32+
{
33+
instances: [{
34+
visitorId: user_id,
35+
experimentId: rule_id,
36+
attributes: [
37+
{ id: 'attr1', value: 'value1', type: 'custom_attribute' },
38+
{ id: 'attr2', value: 'value2', type: 'custom_attribute' }
39+
],
40+
cmabUUID: cmab_uuid
41+
}]
42+
}
43+
let(:expected_headers) { {'Content-Type' => 'application/json'} }
44+
45+
it 'should return the variation id on success without retrying' do
46+
mock_response = double('response', status_code: 200, json: { 'predictions'=> [ {'variationId': 'abc123'}] })
47+
allow(mock_http_client).to receive(:post).and_return(mock_response)
48+
result = client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
49+
expect(result).to eq('abc123')
50+
expect(mock_http_client).to have_received(:post).with(
51+
expected_url,
52+
hash_including(
53+
json: expected_body,
54+
headers: expected_headers,
55+
timeout: 10
56+
)
57+
)
58+
end
59+
60+
it 'should return HTTP exception without retrying' do
61+
allow(mock_http_client).to receive(:post).and_raise(StandardError.new('Connection error'))
62+
allow(mock_logger).to receive(:error)
63+
expect {
64+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
65+
}.to raise_error(Optimizely::CmabFetchError, /Connection error/)
66+
expect(mock_http_client).to have_received(:post).once
67+
expect(mock_logger).to have_received(:error).with(a_string_including('Connection error'))
68+
end
69+
70+
it 'should not return 200 status without retrying' do
71+
mock_response = double('response', status_code: 500, json: nil)
72+
allow(mock_http_client).to receive(:post).and_return(mock_response)
73+
74+
expect {
75+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
76+
}.to raise_error(Optimizely::CmabFetchError, /500/)
77+
78+
expect(mock_http_client).to have_received(:post).with(
79+
expected_url,
80+
hash_including(
81+
json: expected_body,
82+
headers: expected_headers,
83+
timeout: 10
84+
)
85+
)
86+
expect(mock_logger).to have_received(:error).with(a_string_including('500'))
87+
end
88+
89+
it 'should return invalid json without retrying' do
90+
mock_response = double('response', status_code: 200)
91+
allow(mock_response).to receive(:json).and_raise(JSON::ParserError.new('Expecting value'))
92+
allow(mock_http_client).to receive(:post).and_return(mock_response)
93+
94+
expect {
95+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
96+
}.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
97+
98+
expect(mock_http_client).to have_received(:post).with(
99+
expected_url,
100+
hash_including(
101+
json: expected_body,
102+
headers: expected_headers,
103+
timeout: 10
104+
)
105+
)
106+
expect(mock_logger).to have_received(:error).with(a_string_including('Invalid CMAB fetch response'))
107+
end
108+
109+
it 'should return invalid response structure without retrying' do
110+
mock_response = double('response', status_code: 200, json: { 'no_predictions'=> [] })
111+
allow(mock_http_client).to receive(:post).and_return(mock_response)
112+
113+
expect {
114+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
115+
}.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
116+
117+
expect(mock_http_client).to have_received(:post).with(
118+
expected_url,
119+
hash_including(
120+
json: expected_body,
121+
headers: expected_headers,
122+
timeout: 10
123+
)
124+
)
125+
expect(mock_logger).to have_received(:error).with(a_string_including('Invalid CMAB fetch response'))
126+
end
127+
128+
it 'should return the variation id on first try with retry config but no retry needed' do
129+
client_with_retry = described_class.new(
130+
http_client: mock_http_client,
131+
logger: mock_logger,
132+
retry_config: retry_config
133+
)
134+
135+
# Mock successful response
136+
mock_response = double('response', status_code: 200, json: { 'predictions'=> [ {'variationId': 'abc123'}] })
137+
allow(mock_http_client).to receive(:post).and_return(mock_response)
138+
allow_any_instance_of(Object).to receive(:sleep)
139+
140+
result = client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
141+
142+
expect(result).to eq('abc123')
143+
expect(mock_http_client).to have_received(:post).with(
144+
expected_url,
145+
hash_including(
146+
json: expected_body,
147+
headers: expected_headers,
148+
timeout: 10
149+
)
150+
).once
151+
expect_any_instance_of(Object).not_to have_received(:sleep)
152+
end
153+
154+
it 'should return the variation id on third try with retry config' do
155+
client_with_retry = described_class.new(
156+
http_client: mock_http_client,
157+
logger: mock_logger,
158+
retry_config: retry_config
159+
)
160+
161+
# Create failure and success responses
162+
failure_response = double('response', status_code: 500)
163+
success_response = double('response', status_code: 200, json: { 'predictions'=> [ {'variationId': 'xyz456'}] })
164+
165+
# First two calls fail, third succeeds
166+
call_sequence = [failure_response, failure_response, success_response]
167+
allow(mock_http_client).to receive(:post) { call_sequence.shift }
168+
169+
allow(mock_logger).to receive(:info)
170+
allow_any_instance_of(Object).to receive(:sleep)
171+
172+
result = client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
173+
174+
expect(result).to eq('xyz456')
175+
expect(mock_http_client).to have_received(:post).exactly(3).times
176+
177+
# Verify all HTTP calls used correct parameters
178+
expect(mock_http_client).to have_received(:post).with(
179+
expected_url,
180+
hash_including(
181+
json: expected_body,
182+
headers: expected_headers,
183+
timeout: 10
184+
)
185+
)
186+
187+
# Verify retry logging
188+
expect(mock_logger).to have_received(:info).with('Retrying CMAB request (attempt 1) after 0.01 seconds...')
189+
expect(mock_logger).to have_received(:info).with('Retrying CMAB request (attempt 2) after 0.02 seconds...')
190+
191+
# Verify sleep was called with correct backoff times
192+
expect_any_instance_of(Object).to have_received(:sleep).with(0.01)
193+
expect_any_instance_of(Object).to have_received(:sleep).with(0.02)
194+
end
195+
196+
it 'should exhausts all retry attempts' do
197+
client_with_retry = described_class.new(
198+
http_client: mock_http_client,
199+
logger: mock_logger,
200+
retry_config: retry_config
201+
)
202+
203+
# Create failure response
204+
failure_response = double('response', status_code: 500)
205+
206+
# All attempts fail
207+
allow(mock_http_client).to receive(:post).and_return(failure_response)
208+
allow(mock_logger).to receive(:info)
209+
allow(mock_logger).to receive(:error)
210+
allow_any_instance_of(Object).to receive(:sleep)
211+
212+
expect {
213+
client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
214+
}.to raise_error(Optimizely::CmabFetchError)
215+
216+
# Verify all attempts were made (1 initial + 3 retries)
217+
expect(mock_http_client).to have_received(:post).exactly(4).times
218+
219+
# Verify retry logging
220+
expect(mock_logger).to have_received(:info).with('Retrying CMAB request (attempt 1) after 0.01 seconds...')
221+
expect(mock_logger).to have_received(:info).with('Retrying CMAB request (attempt 2) after 0.02 seconds...')
222+
expect(mock_logger).to have_received(:info).with('Retrying CMAB request (attempt 3) after 0.08 seconds...')
223+
224+
# Verify sleep was called for each retry
225+
expect_any_instance_of(Object).to have_received(:sleep).with(0.01)
226+
expect_any_instance_of(Object).to have_received(:sleep).with(0.02)
227+
expect_any_instance_of(Object).to have_received(:sleep).with(0.08)
228+
229+
# Verify final error logging
230+
expect(mock_logger).to have_received(:error).with(a_string_including('Max retries exceeded for CMAB request'))
23231
end
24232
end

0 commit comments

Comments
 (0)