Skip to content

Commit 5cb9de7

Browse files
committed
Update the test
1 parent 4d1d707 commit 5cb9de7

File tree

1 file changed

+145
-156
lines changed

1 file changed

+145
-156
lines changed

spec/cmab_client_spec.rb

Lines changed: 145 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
require 'optimizely/cmab/cmab_client'
2121

2222
describe Optimizely::DefaultCmabClient do
23-
let(:mock_http_client) { double('http_client') }
2423
let(:spy_logger) { spy('logger') }
25-
let(:retry_config) { Optimizely::CmabRetryConfig.new(max_retries: 3, retry_delay: 0.01, max_backoff: 1, backoff_multiplier: 2) }
2624
let(:rule_id) { 'test_rule' }
2725
let(:user_id) { 'user123' }
2826
let(:attributes) { {'attr1': 'value1', 'attr2': 'value2'} }
@@ -49,179 +47,170 @@
4947

5048
after do
5149
RSpec::Mocks.space.proxy_for(spy_logger).reset
52-
RSpec::Mocks.space.proxy_for(mock_http_client).reset
5350
end
5451

55-
it 'should return the variation id on success without retrying' do
56-
client = described_class.new(mock_http_client, nil, spy_logger)
57-
mock_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'abc123'}]})
58-
allow(mock_http_client).to receive(:post).and_return(mock_response)
59-
60-
result = client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
61-
62-
expect(result).to eq('abc123')
63-
expect(mock_http_client).to have_received(:post).with(
64-
expected_url,
65-
hash_including(
66-
json: expected_body,
67-
headers: expected_headers,
68-
timeout: 10
69-
)
70-
).once
71-
expect(Kernel).not_to have_received(:sleep)
52+
context 'when client is configured without retries' do
53+
let(:mock_http_client) { double('http_client') }
54+
let(:client) { described_class.new(mock_http_client, nil, spy_logger) }
55+
56+
it 'should return the variation id on success' do
57+
mock_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'abc123'}]})
58+
allow(mock_http_client).to receive(:post).and_return(mock_response)
59+
60+
result = client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
61+
62+
expect(result).to eq('abc123')
63+
expect(mock_http_client).to have_received(:post).with(
64+
expected_url,
65+
hash_including(
66+
json: expected_body,
67+
headers: expected_headers,
68+
timeout: 10
69+
)
70+
).once
71+
expect(Kernel).not_to have_received(:sleep)
72+
end
73+
74+
it 'should return HTTP exception' do
75+
allow(mock_http_client).to receive(:post).and_raise(StandardError.new('Connection error'))
76+
77+
expect do
78+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
79+
end.to raise_error(Optimizely::CmabFetchError, /Connection error/)
80+
81+
expect(mock_http_client).to have_received(:post).once
82+
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Connection error'))
83+
expect(Kernel).not_to have_received(:sleep)
84+
end
85+
86+
it 'should not return 200 status' do
87+
mock_response = double('response', status_code: 500, json: nil)
88+
allow(mock_http_client).to receive(:post).and_return(mock_response)
89+
90+
expect do
91+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
92+
end.to raise_error(Optimizely::CmabFetchError, /500/)
93+
94+
expect(mock_http_client).to have_received(:post).with(
95+
expected_url,
96+
hash_including(
97+
json: expected_body,
98+
headers: expected_headers,
99+
timeout: 10
100+
)
101+
).once
102+
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('500'))
103+
expect(Kernel).not_to have_received(:sleep)
104+
end
105+
106+
it 'should return invalid json' do
107+
mock_response = double('response', status_code: 200)
108+
allow(mock_response).to receive(:json).and_raise(JSON::ParserError.new('Expecting value'))
109+
allow(mock_http_client).to receive(:post).and_return(mock_response)
110+
111+
expect do
112+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
113+
end.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
114+
115+
expect(mock_http_client).to have_received(:post).with(
116+
expected_url,
117+
hash_including(
118+
json: expected_body,
119+
headers: expected_headers,
120+
timeout: 10
121+
)
122+
).once
123+
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Invalid CMAB fetch response'))
124+
expect(Kernel).not_to have_received(:sleep)
125+
end
126+
127+
it 'should return invalid response structure' do
128+
mock_response = double('response', status_code: 200, json: {'no_predictions' => []})
129+
allow(mock_http_client).to receive(:post).and_return(mock_response)
130+
131+
expect do
132+
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
133+
end.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
134+
135+
expect(mock_http_client).to have_received(:post).with(
136+
expected_url,
137+
hash_including(
138+
json: expected_body,
139+
headers: expected_headers,
140+
timeout: 10
141+
)
142+
).once
143+
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Invalid CMAB fetch response'))
144+
expect(Kernel).not_to have_received(:sleep)
145+
end
72146
end
73147

74-
it 'should return HTTP exception without retrying' do
75-
client = described_class.new(mock_http_client, nil, spy_logger)
76-
allow(mock_http_client).to receive(:post).and_raise(StandardError.new('Connection error'))
148+
context 'when client is configured with retries' do
149+
let(:mock_http_client) { double('http_client') } # Fresh double for this context
150+
let(:retry_config) { Optimizely::CmabRetryConfig.new(max_retries: 3, retry_delay: 0.01, max_backoff: 1, backoff_multiplier: 2) }
151+
let(:client_with_retry) { described_class.new(mock_http_client, retry_config, spy_logger) }
77152

78-
expect do
79-
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
80-
end.to raise_error(Optimizely::CmabFetchError, /Connection error/)
153+
it 'should return the variation id on first try with retry config but no retry needed' do
154+
mock_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'abc123'}]})
155+
allow(mock_http_client).to receive(:post).and_return(mock_response)
81156

82-
expect(mock_http_client).to have_received(:post).once
83-
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Connection error'))
84-
expect(Kernel).not_to have_received(:sleep)
85-
end
86-
87-
it 'should not return 200 status without retrying' do
88-
client = described_class.new(mock_http_client, nil, spy_logger)
89-
mock_response = double('response', status_code: 500, json: nil)
90-
allow(mock_http_client).to receive(:post).and_return(mock_response)
91-
92-
expect do
93-
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
94-
end.to raise_error(Optimizely::CmabFetchError, /500/)
95-
96-
expect(mock_http_client).to have_received(:post).with(
97-
expected_url,
98-
hash_including(
99-
json: expected_body,
100-
headers: expected_headers,
101-
timeout: 10
102-
)
103-
).once
104-
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('500'))
105-
expect(Kernel).not_to have_received(:sleep)
106-
end
157+
result = client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
107158

108-
it 'should return invalid json without retrying' do
109-
client = described_class.new(mock_http_client, nil, spy_logger)
110-
mock_response = double('response', status_code: 200)
111-
allow(mock_response).to receive(:json).and_raise(JSON::ParserError.new('Expecting value'))
112-
allow(mock_http_client).to receive(:post).and_return(mock_response)
113-
114-
expect do
115-
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
116-
end.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
117-
118-
expect(mock_http_client).to have_received(:post).with(
119-
expected_url,
120-
hash_including(
121-
json: expected_body,
122-
headers: expected_headers,
123-
timeout: 10
124-
)
125-
).once
126-
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Invalid CMAB fetch response'))
127-
expect(Kernel).not_to have_received(:sleep)
128-
end
159+
expect(result).to eq('abc123')
160+
expect(mock_http_client).to have_received(:post).with(
161+
expected_url,
162+
hash_including(
163+
json: expected_body,
164+
headers: expected_headers,
165+
timeout: 10
166+
)
167+
).once
168+
expect(Kernel).not_to have_received(:sleep)
169+
end
129170

130-
it 'should return invalid response structure without retrying' do
131-
client = described_class.new(mock_http_client, nil, spy_logger)
132-
mock_response = double('response', status_code: 200, json: {'no_predictions' => []})
133-
allow(mock_http_client).to receive(:post).and_return(mock_response)
134-
135-
expect do
136-
client.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
137-
end.to raise_error(Optimizely::CmabInvalidResponseError, /Invalid CMAB fetch response/)
138-
139-
expect(mock_http_client).to have_received(:post).with(
140-
expected_url,
141-
hash_including(
142-
json: expected_body,
143-
headers: expected_headers,
144-
timeout: 10
145-
)
146-
).once
147-
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Invalid CMAB fetch response'))
148-
expect(Kernel).not_to have_received(:sleep)
149-
end
171+
it 'should return the variation id on third try' do
172+
failure_response = double('response', status_code: 500)
173+
success_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'xyz456'}]})
150174

151-
it 'should return the variation id on first try with retry config but no retry needed' do
152-
client_with_retry = described_class.new(mock_http_client, retry_config, spy_logger)
153-
154-
# Mock successful response
155-
mock_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'abc123'}]})
156-
allow(mock_http_client).to receive(:post).and_return(mock_response)
157-
158-
result = client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
159-
160-
expect(result).to eq('abc123')
161-
expect(mock_http_client).to have_received(:post).with(
162-
expected_url,
163-
hash_including(
164-
json: expected_body,
165-
headers: expected_headers,
166-
timeout: 10
167-
)
168-
).once
169-
expect(Kernel).not_to have_received(:sleep)
170-
end
175+
# Use a sequence to control responses
176+
allow(mock_http_client).to receive(:post).and_return(failure_response, failure_response, success_response)
171177

172-
it 'should return the variation id on third try with retry config' do
173-
client_with_retry = described_class.new(mock_http_client, retry_config, spy_logger)
178+
result = client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
174179

175-
# Create failure and success responses
176-
failure_response = double('response', status_code: 500)
177-
success_response = double('response', status_code: 200, json: {'predictions' => [{'variationId' => 'xyz456'}]})
180+
expect(result).to eq('xyz456')
181+
expect(mock_http_client).to have_received(:post).exactly(3).times
178182

179-
# First two calls fail, third succeeds
180-
allow(mock_http_client).to receive(:post).and_return(failure_response, failure_response, success_response)
181-
182-
result = client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
183-
184-
expect(result).to eq('xyz456')
185-
expect(mock_http_client).to have_received(:post).exactly(3).times
186-
187-
# Verify retry logging
188-
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 1) after 0.01 seconds...').once
189-
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 2) after 0.02 seconds...').once
190-
expect(spy_logger).not_to have_received(:log).with(Logger::INFO, a_string_including('Retrying CMAB request (attempt 3)'))
191-
192-
# Verify sleep was called with correct backoff times
193-
expect(Kernel).to have_received(:sleep).with(0.01).once
194-
expect(Kernel).to have_received(:sleep).with(0.02).once
195-
expect(Kernel).not_to have_received(:sleep).with(0.08)
196-
end
183+
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 1) after 0.01 seconds...').once
184+
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 2) after 0.02 seconds...').once
185+
expect(spy_logger).not_to have_received(:log).with(Logger::INFO, a_string_including('Retrying CMAB request (attempt 3)'))
197186

198-
it 'should exhausts all retry attempts' do
199-
client_with_retry = described_class.new(mock_http_client, retry_config, spy_logger)
187+
expect(Kernel).to have_received(:sleep).with(0.01).once
188+
expect(Kernel).to have_received(:sleep).with(0.02).once
189+
expect(Kernel).not_to have_received(:sleep).with(0.04)
190+
expect(Kernel).not_to have_received(:sleep).with(0.08)
191+
end
200192

201-
# Create failure response
202-
failure_response = double('response', status_code: 500)
193+
it 'should exhaust all retry attempts' do
194+
failure_response = double('response', status_code: 500)
203195

204-
# All attempts fail
205-
allow(mock_http_client).to receive(:post).and_return(failure_response, failure_response, failure_response, failure_response)
196+
# All attempts fail
197+
allow(mock_http_client).to receive(:post).and_return(failure_response, failure_response, failure_response, failure_response)
206198

207-
expect do
208-
client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
209-
end.to raise_error(Optimizely::CmabFetchError)
199+
expect do
200+
client_with_retry.fetch_decision(rule_id, user_id, attributes, cmab_uuid)
201+
end.to raise_error(Optimizely::CmabFetchError)
210202

211-
# Verify all attempts were made (1 initial + 3 retries = 4 calls)
212-
expect(mock_http_client).to have_received(:post).exactly(4).times
203+
expect(mock_http_client).to have_received(:post).exactly(4).times
213204

214-
# Verify retry logging
215-
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 1) after 0.01 seconds...').once
216-
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 2) after 0.02 seconds...').once
217-
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 3) after 0.08 seconds...').once
205+
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 1) after 0.01 seconds...').once
206+
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 2) after 0.02 seconds...').once
207+
expect(spy_logger).to have_received(:log).with(Logger::INFO, 'Retrying CMAB request (attempt 3) after 0.08 seconds...').once
218208

219-
# Verify sleep was called for each retry
220-
expect(Kernel).to have_received(:sleep).with(0.01).once
221-
expect(Kernel).to have_received(:sleep).with(0.02).once
222-
expect(Kernel).to have_received(:sleep).with(0.08).once
209+
expect(Kernel).to have_received(:sleep).with(0.01).once
210+
expect(Kernel).to have_received(:sleep).with(0.02).once
211+
expect(Kernel).to have_received(:sleep).with(0.08).once
223212

224-
# Verify final error logging
225-
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Max retries exceeded for CMAB request'))
213+
expect(spy_logger).to have_received(:log).with(Logger::ERROR, a_string_including('Max retries exceeded for CMAB request'))
214+
end
226215
end
227216
end

0 commit comments

Comments
 (0)