Skip to content

Commit c574289

Browse files
feat: add rest api manager (#311)
* add rest api manager
1 parent cfc1efa commit c574289

File tree

3 files changed

+169
-1
lines changed

3 files changed

+169
-1
lines changed

lib/optimizely/helpers/constants.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ module Constants
384384

385385
ODP_LOGS = {
386386
FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).',
387-
ODP_EVENT_FAILED: 'ODP event send failed (invalid url).',
387+
ODP_EVENT_FAILED: 'ODP event send failed (%s).',
388388
ODP_NOT_ENABLED: 'ODP is not enabled.'
389389
}.freeze
390390

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2022, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'json'
20+
21+
module Optimizely
22+
class ZaiusRestApiManager
23+
# Interface that handles sending ODP events.
24+
25+
def initialize(logger: nil, proxy_config: nil)
26+
@logger = logger || NoOpLogger.new
27+
@proxy_config = proxy_config
28+
end
29+
30+
# Send events to the ODP Events API.
31+
#
32+
# @param api_key - public api key
33+
# @param api_host - domain url of the host
34+
# @param events - array of events to send
35+
36+
def send_odp_events(api_key, api_host, events)
37+
should_retry = false
38+
url = "#{api_host}/v3/events"
39+
40+
headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s}
41+
42+
begin
43+
response = Helpers::HttpUtils.make_request(
44+
url, :post, events.to_json, headers, Optimizely::Helpers::Constants::ODP_REST_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config
45+
)
46+
rescue SocketError, Timeout::Error, Errno::ECONNRESET, Errno::EHOSTUNREACH, Errno::EFAULT, Errno::ENETUNREACH, Errno::ENETDOWN, Errno::ECONNREFUSED
47+
log_failure('network error')
48+
should_retry = true
49+
return should_retry
50+
rescue StandardError => e
51+
log_failure(e)
52+
return should_retry
53+
end
54+
55+
status = response.code.to_i
56+
if status >= 400
57+
log_failure(!response.body.empty? ? response.body : "#{status}: #{response.message}")
58+
should_retry = status >= 500
59+
end
60+
should_retry
61+
end
62+
63+
private
64+
65+
def log_failure(message, level = Logger::ERROR)
66+
@logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:ODP_EVENT_FAILED], message))
67+
end
68+
end
69+
end
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# frozen_string_literal: true
2+
3+
#
4+
# Copyright 2022, Optimizely and contributors
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
require 'spec_helper'
19+
require 'optimizely/odp/zaius_rest_api_manager'
20+
21+
describe Optimizely::ZaiusRestApiManager do
22+
let(:user_key) { 'vuid' }
23+
let(:user_value) { 'test-user-value' }
24+
let(:api_key) { 'test-api-key' }
25+
let(:api_host) { 'https://test-host.com' }
26+
let(:spy_logger) { spy('logger') }
27+
let(:events) do
28+
[
29+
{type: 't1', action: 'a1', identifiers: {'id-key-1': 'id-value-1'}, data: {'key-1': 'value1'}},
30+
{type: 't2', action: 'a2', identifiers: {'id-key-2': 'id-value-2'}, data: {'key-2': 'value2'}}
31+
]
32+
end
33+
let(:failure_response_data) do
34+
{
35+
title: 'Bad Request', status: 400, timestamp: '2022-07-01T20:44:00.945Z',
36+
detail: {
37+
invalids: [{event: 0, message: "missing 'type' field"}]
38+
}
39+
}.to_json
40+
end
41+
42+
describe '.fetch_segments' do
43+
it 'should send odp events successfully and return false' do
44+
stub_request(:post, "#{api_host}/v3/events")
45+
.with(
46+
headers: {'content-type': 'application/json', 'x-api-key': api_key},
47+
body: events.to_json
48+
).to_return(status: 200)
49+
50+
api_manager = Optimizely::ZaiusRestApiManager.new
51+
expect(spy_logger).not_to receive(:log)
52+
should_retry = api_manager.send_odp_events(api_key, api_host, events)
53+
54+
expect(should_retry).to be false
55+
end
56+
57+
it 'should return true on network error' do
58+
allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_raise(SocketError)
59+
api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger)
60+
expect(spy_logger).to receive(:log).with(Logger::ERROR, 'ODP event send failed (network error).')
61+
62+
should_retry = api_manager.send_odp_events(api_key, api_host, events)
63+
64+
expect(should_retry).to be true
65+
end
66+
67+
it 'should return false with 400 error' do
68+
stub_request(:post, "#{api_host}/v3/events")
69+
.with(
70+
body: events.to_json
71+
).to_return(status: [400, 'Bad Request'], body: failure_response_data)
72+
73+
api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger)
74+
expect(spy_logger).to receive(:log).with(
75+
Logger::ERROR, 'ODP event send failed ({"title":"Bad Request","status":400,' \
76+
'"timestamp":"2022-07-01T20:44:00.945Z","detail":{"invalids":' \
77+
'[{"event":0,"message":"missing \'type\' field"}]}}).'
78+
)
79+
80+
should_retry = api_manager.send_odp_events(api_key, api_host, events)
81+
82+
expect(should_retry).to be false
83+
end
84+
85+
it 'should return true with 500 error' do
86+
stub_request(:post, "#{api_host}/v3/events")
87+
.with(
88+
body: events.to_json
89+
).to_return(status: [500, 'Internal Server Error'])
90+
91+
api_manager = Optimizely::ZaiusRestApiManager.new(logger: spy_logger)
92+
expect(spy_logger).to receive(:log).with(Logger::ERROR, 'ODP event send failed (500: Internal Server Error).')
93+
94+
should_retry = api_manager.send_odp_events(api_key, api_host, events)
95+
96+
expect(should_retry).to be true
97+
end
98+
end
99+
end

0 commit comments

Comments
 (0)