From b204f5237e20c9095602db36629cb40e26c27cd1 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Thu, 4 Aug 2022 11:39:18 -0400 Subject: [PATCH 1/5] add ZaiusGraphQlApiManager --- lib/optimizely/helpers/constants.rb | 14 + .../odp/zaius_graphql_api_manager.rb | 103 ++++ spec/odp/zaius_graphql_api_manager_spec.rb | 438 ++++++++++++++++++ 3 files changed, 555 insertions(+) create mode 100644 lib/optimizely/odp/zaius_graphql_api_manager.rb create mode 100644 spec/odp/zaius_graphql_api_manager_spec.rb diff --git a/lib/optimizely/helpers/constants.rb b/lib/optimizely/helpers/constants.rb index eae4906f..806e4c4c 100644 --- a/lib/optimizely/helpers/constants.rb +++ b/lib/optimizely/helpers/constants.rb @@ -382,6 +382,12 @@ module Constants 'EVALUATING_AUDIENCES_COMBINED' => "Evaluating audiences for rule '%s': %s." }.merge(AUDIENCE_EVALUATION_LOGS).freeze + ODP_LOGS = { + FETCH_SEGMENTS_FAILED: 'Audience segments fetch failed (%s).', + ODP_EVENT_FAILED: 'ODP event send failed (invalid url).', + ODP_NOT_ENABLED: 'ODP is not enabled.' + }.freeze + DECISION_NOTIFICATION_TYPES = { 'AB_TEST' => 'ab-test', 'FEATURE' => 'feature', @@ -406,6 +412,14 @@ module Constants 'REQUEST_TIMEOUT' => 10 }.freeze + ODP_GRAPHQL_API_CONFIG = { + REQUEST_TIMEOUT: 10 + }.freeze + + ODP_REST_API_CONFIG = { + REQUEST_TIMEOUT: 10 + }.freeze + HTTP_HEADERS = { 'IF_MODIFIED_SINCE' => 'If-Modified-Since', 'LAST_MODIFIED' => 'Last-Modified' diff --git a/lib/optimizely/odp/zaius_graphql_api_manager.rb b/lib/optimizely/odp/zaius_graphql_api_manager.rb new file mode 100644 index 00000000..823773df --- /dev/null +++ b/lib/optimizely/odp/zaius_graphql_api_manager.rb @@ -0,0 +1,103 @@ +# frozen_string_literal: true + +# +# Copyright 2022, Optimizely and contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'json' + +module Optimizely + class ZaiusGraphQlApiManager + # Interface that handles fetching audience segments. + + def initialize(logger: nil, proxy_config: nil) + @logger = logger || NoOpLogger.new + @proxy_config = proxy_config + end + + # Fetch segments from the ODP GraphQL API. + # + # @param api_key - public api key + # @param api_host - domain url of the host + # @param user_key - vuid or fs_user_id (client device id or fullstack id) + # @param user_value - value of user_key + # @param segments_to_check - array of segments to check + + def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) + url = "#{api_host}/v3/graphql" + + headers = {'Content-Type' => 'application/json', 'x-api-key' => api_key.to_s} + + payload = { + 'query' => %'query {customer(#{user_key}: "#{user_value}")' \ + "{audiences(subset:#{segments_to_check || '[]'}) {edges {node {name state}}}}}" + }.to_json + + begin + response = Helpers::HttpUtils.make_request( + url, :post, payload, headers, Optimizely::Helpers::Constants::ODP_GRAPHQL_API_CONFIG[:REQUEST_TIMEOUT], @proxy_config + ) + rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e + @logger.log(Logger::DEBUG, "GraphQL download failed: #{e}") + log_error(:FETCH_SEGMENTS_FAILED, 'network error') + return nil + rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e + log_error(:FETCH_SEGMENTS_FAILED, e) + return nil + end + + status = response.code.to_i + if status >= 400 + log_error(:FETCH_SEGMENTS_FAILED, status) + return nil + end + + begin + response = JSON.parse(response.body) + rescue JSON::ParserError + log_error(:FETCH_SEGMENTS_FAILED, 'JSON decode error') + return nil + end + + if response.include?('errors') + error_class = response['errors']&.first&.dig('extensions', 'classification') || 'decode error' + log_error(:FETCH_SEGMENTS_FAILED, error_class == 'InvalidIdentifierException' ? 'invalid identifier' : error_class) + return nil + end + + audiences = response.dig('data', 'customer', 'audiences', 'edges') + unless audiences + log_error(:FETCH_SEGMENTS_FAILED, 'decode error') + return nil + end + + audiences.filter_map do |edge| + name = edge.dig('node', 'name') + state = edge.dig('node', 'state') + unless name && state + log_error(:FETCH_SEGMENTS_FAILED, 'decode error') + return nil + end + state == 'qualified' ? name : nil + end + end + + private + + def log_error(type, error) + @logger.log(Logger::ERROR, format(Optimizely::Helpers::Constants::ODP_LOGS[type], error)) + end + end +end diff --git a/spec/odp/zaius_graphql_api_manager_spec.rb b/spec/odp/zaius_graphql_api_manager_spec.rb new file mode 100644 index 00000000..5693f1cb --- /dev/null +++ b/spec/odp/zaius_graphql_api_manager_spec.rb @@ -0,0 +1,438 @@ +# frozen_string_literal: true + +# +# Copyright 2019-2020, Optimizely and contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +require 'spec_helper' +require 'optimizely/odp/zaius_graphql_api_manager' + +describe Optimizely::ZaiusGraphQlApiManager do + let(:user_key) { 'vuid' } + let(:user_value) { 'test-user-value' } + let(:api_key) { 'test-api-key' } + let(:api_host) { 'https://test-host' } + let(:error_handler) { Optimizely::RaiseErrorHandler.new } + let(:spy_logger) { spy('logger') } + let(:zaius_manager) { Optimizely::ZaiusGraphQlApiManager.new(logger: spy_logger) } + let(:good_response_data) do + { + data: { + customer: { + audiences: { + edges: [ + { + node: { + name: 'a', + state: 'qualified', + description: 'qualifed sample 1' + } + }, + { + node: { + name: 'b', + state: 'qualified', + description: 'qualifed sample 2' + } + }, + { + node: { + name: 'c', + state: 'not_qualified', + description: 'not-qualified sample' + } + } + ] + } + } + } + } + end + let(:good_empty_response_data) do + { + data: { + customer: { + audiences: { + edges: [] + } + } + } + } + end + let(:invalid_identifier_response_data) do + { + errors: [ + { + message: "Exception while fetching data (/customer) :\ + java.lang.RuntimeException: could not resolve _fs_user_id = asdsdaddddd", + locations: [ + { + line: 2, + column: 3 + } + ], + path: [ + 'customer' + ], + extensions: { + classification: 'InvalidIdentifierException' + } + } + ], + data: { + customer: nil + } + } + end + let(:node_missing_response_data) do + { + data: { + customer: { + audiences: { + edges: [ + {} + ] + } + } + } + } + end + let(:mixed_missing_keys_response_data) do + { + data: { + customer: { + audiences: { + edges: [ + { + node: { + state: 'qualified' + } + }, + { + node: { + name: 'a' + } + }, + { + "other-name": { + name: 'a', + state: 'qualified' + } + } + ] + } + } + } + } + end + let(:other_exception_response_data) do + { + errors: [ + { + message: "Exception while fetching data (/customer) :\ + java.lang.RuntimeException: could not resolve _fs_user_id = asdsdaddddd", + extensions: { + classification: 'TestExceptionClass' + } + } + ], + data: { + customer: nil + } + } + end + let(:bad_response_data) { {data: {}} } + let(:name_invalid_response_data) do + '{ + "data": { + "customer": { + "audiences": { + "edges": [ + { + "node": { + "name": "a":::invalid-part-here:::, + "state": "qualified", + "description": "qualifed sample 1" + } + } + ] + } + } + } + }' + end + let(:invalid_edges_key_response_data) do + { + data: { + customer: { + audiences: { + invalid_test_key: [ + { + node: { + name: 'a', + state: 'qualified', + description: 'qualifed sample 1' + } + } + ] + } + } + } + } + end + let(:invalid_key_for_error_response_data) do + { + errors: [ + { + message: "Exception while fetching data (/customer) :\ + java.lang.RuntimeException: could not resolve _fs_user_id = asdsdaddddd", + locations: [ + { + line: 2, + column: 3 + } + ], + path: [ + 'customer' + ], + invalid_test_key: { + classification: 'InvalidIdentifierException' + } + } + ], + data: { + customer: nil + } + } + end + describe '.fetch_segments' do + it 'should get qualified segments when valid segments are given' do + stub_request(:post, "#{api_host}/v3/graphql") + .with( + headers: {'content-type': 'application/json', 'x-api-key': api_key}, + body: { + query: %'query {customer(#{user_key}: "#{user_value}")' \ + '{audiences(subset:["a", "b", "c"]) {edges {node {name state}}}}}' + } + ) + .to_return(status: 200, body: good_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b c]) + expect(segments).to match_array %w[a b] + end + + it 'should get empty array when empty array is given' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: good_empty_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, []) + expect(segments).to match_array [] + end + + it 'should log error and return nil when response is missing node' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: node_missing_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (decode error).' + ) + end + + it 'should log error and return nil when response keys are incorrect' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: mixed_missing_keys_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (decode error).' + ) + end + + it 'should log error and return nil with invalid identifier exception' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: invalid_identifier_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (invalid identifier).' + ) + end + + it 'should log error and return nil with other exception' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: other_exception_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (TestExceptionClass).' + ) + end + + it 'should log error and return nil with bad response data' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: bad_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (decode error).' + ) + end + + it 'should log error and return nil with invalid name' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: name_invalid_response_data) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (JSON decode error).' + ) + end + + it 'should log error and return nil with invalid key' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: invalid_edges_key_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (decode error).' + ) + end + + it 'should log error and return nil with invalid key in error body' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 200, body: invalid_key_for_error_response_data.to_json) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (decode error).' + ) + end + + it 'should log error and return nil with network error' do + stub_request(:post, "#{api_host}/v3/graphql") + .and_raise(SocketError) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (network error).' + ) + + expect(spy_logger).to have_received(:log).once.with( + Logger::DEBUG, + 'GraphQL download failed: Exception from WebMock' + ) + end + + it 'should log error and return nil with http status 400' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 400) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (400).' + ) + end + + it 'should log error and return nil with http status 500' do + stub_request(:post, "#{api_host}/v3/graphql") + .to_return(status: 500) + + segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) + expect(segments).to be_nil + + expect(spy_logger).to have_received(:log).once.with( + Logger::ERROR, + 'Audience segments fetch failed (500).' + ) + end + + it 'should create correct subset filter' do + stub_request(:post, "#{api_host}/v3/graphql") + .with( + body: { + query: %'query {customer(#{user_key}: "#{user_value}")' \ + '{audiences(subset:[]) {edges {node {name state}}}}}' + } + ) + zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, nil) + + stub_request(:post, "#{api_host}/v3/graphql") + .with( + body: { + query: %'query {customer(#{user_key}: "#{user_value}")' \ + '{audiences(subset:[]) {edges {node {name state}}}}}' + } + ) + zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, []) + + stub_request(:post, "#{api_host}/v3/graphql") + .with( + body: { + query: %'query {customer(#{user_key}: "#{user_value}")' \ + '{audiences(subset:["a"]) {edges {node {name state}}}}}' + } + ) + zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a]) + + stub_request(:post, "#{api_host}/v3/graphql") + .with( + body: { + query: %'query {customer(#{user_key}: "#{user_value}")' \ + '{audiences(subset:["a", "b", "c"]) {edges {node {name state}}}}}' + } + ) + zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b c]) + end + + it 'should pass the proxy config that is passed in' do + allow(Optimizely::Helpers::HttpUtils).to receive(:make_request).and_raise(SocketError) + stub_request(:post, "#{api_host}/v3/graphql") + + zaius_manager = Optimizely::ZaiusGraphQlApiManager.new(logger: spy_logger, proxy_config: :proxy_config) + zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, []) + expect(Optimizely::Helpers::HttpUtils).to have_received(:make_request).with(anything, anything, anything, anything, anything, :proxy_config) + end + end +end From 8a5d4c9db970b6bdbe27bfe6a8cd1ba20a8cab8a Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Fri, 5 Aug 2022 17:17:56 -0400 Subject: [PATCH 2/5] add event_dispatch_config --- lib/optimizely/event_dispatcher.rb | 6 ++---- lib/optimizely/helpers/constants.rb | 4 ++++ spec/event_dispatcher_spec.rb | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/optimizely/event_dispatcher.rb b/lib/optimizely/event_dispatcher.rb index aaa0b593..874a43db 100644 --- a/lib/optimizely/event_dispatcher.rb +++ b/lib/optimizely/event_dispatcher.rb @@ -17,6 +17,7 @@ # require_relative 'exceptions' require_relative 'helpers/http_utils' +require_relative 'helpers/constants' module Optimizely class NoOpEventDispatcher @@ -26,9 +27,6 @@ def dispatch_event(event); end end class EventDispatcher - # @api constants - REQUEST_TIMEOUT = 10 - def initialize(logger: nil, error_handler: nil, proxy_config: nil) @logger = logger || NoOpLogger.new @error_handler = error_handler || NoOpErrorHandler.new @@ -40,7 +38,7 @@ def initialize(logger: nil, error_handler: nil, proxy_config: nil) # @param event - Event object def dispatch_event(event) response = Helpers::HttpUtils.make_request( - event.url, event.http_verb, event.params.to_json, event.headers, REQUEST_TIMEOUT, @proxy_config + event.url, event.http_verb, event.params.to_json, event.headers, Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT], @proxy_config ) error_msg = "Event failed to dispatch with response code: #{response.code}" diff --git a/lib/optimizely/helpers/constants.rb b/lib/optimizely/helpers/constants.rb index 806e4c4c..83ce92db 100644 --- a/lib/optimizely/helpers/constants.rb +++ b/lib/optimizely/helpers/constants.rb @@ -412,6 +412,10 @@ module Constants 'REQUEST_TIMEOUT' => 10 }.freeze + EVENT_DISPATCH_CONFIG = { + REQUEST_TIMEOUT: 10 + }.freeze + ODP_GRAPHQL_API_CONFIG = { REQUEST_TIMEOUT: 10 }.freeze diff --git a/spec/event_dispatcher_spec.rb b/spec/event_dispatcher_spec.rb index 499e8f09..193f584d 100644 --- a/spec/event_dispatcher_spec.rb +++ b/spec/event_dispatcher_spec.rb @@ -52,7 +52,7 @@ event.http_verb, event.params.to_json, event.headers, - Optimizely::EventDispatcher::REQUEST_TIMEOUT, + Optimizely::Helpers::Constants::EVENT_DISPATCH_CONFIG[:REQUEST_TIMEOUT], proxy_config ) From b9797a3c3a393e90be4f93ee969ee9f6b062cc22 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Mon, 8 Aug 2022 10:34:44 -0400 Subject: [PATCH 3/5] update copyright --- lib/optimizely/helpers/constants.rb | 2 +- spec/odp/zaius_graphql_api_manager_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/optimizely/helpers/constants.rb b/lib/optimizely/helpers/constants.rb index 83ce92db..84c57dea 100644 --- a/lib/optimizely/helpers/constants.rb +++ b/lib/optimizely/helpers/constants.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # -# Copyright 2016-2020, Optimizely and contributors +# Copyright 2016-2020, 2022, Optimizely and contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/spec/odp/zaius_graphql_api_manager_spec.rb b/spec/odp/zaius_graphql_api_manager_spec.rb index 5693f1cb..8764f8f0 100644 --- a/spec/odp/zaius_graphql_api_manager_spec.rb +++ b/spec/odp/zaius_graphql_api_manager_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # -# Copyright 2019-2020, Optimizely and contributors +# Copyright 2022, Optimizely and contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 14ca7de1f74612d35ba429a219a1ad50871824ab Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Mon, 8 Aug 2022 15:17:19 -0400 Subject: [PATCH 4/5] switch invalid identifer to warning --- .../odp/zaius_graphql_api_manager.rb | 22 +++++++++++-------- spec/odp/zaius_graphql_api_manager_spec.rb | 4 ++-- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/optimizely/odp/zaius_graphql_api_manager.rb b/lib/optimizely/odp/zaius_graphql_api_manager.rb index 823773df..8e1cfa56 100644 --- a/lib/optimizely/odp/zaius_graphql_api_manager.rb +++ b/lib/optimizely/odp/zaius_graphql_api_manager.rb @@ -51,35 +51,39 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) ) rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e @logger.log(Logger::DEBUG, "GraphQL download failed: #{e}") - log_error(:FETCH_SEGMENTS_FAILED, 'network error') + log_failure('network error') return nil rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e - log_error(:FETCH_SEGMENTS_FAILED, e) + log_failure(e) return nil end status = response.code.to_i if status >= 400 - log_error(:FETCH_SEGMENTS_FAILED, status) + log_failure(status) return nil end begin response = JSON.parse(response.body) rescue JSON::ParserError - log_error(:FETCH_SEGMENTS_FAILED, 'JSON decode error') + log_failure('JSON decode error') return nil end if response.include?('errors') error_class = response['errors']&.first&.dig('extensions', 'classification') || 'decode error' - log_error(:FETCH_SEGMENTS_FAILED, error_class == 'InvalidIdentifierException' ? 'invalid identifier' : error_class) + if error_class == 'InvalidIdentifierException' + log_failure('invalid identifier', Logger::WARN) + else + log_failure(error_class) + end return nil end audiences = response.dig('data', 'customer', 'audiences', 'edges') unless audiences - log_error(:FETCH_SEGMENTS_FAILED, 'decode error') + log_failure('decode error') return nil end @@ -87,7 +91,7 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) name = edge.dig('node', 'name') state = edge.dig('node', 'state') unless name && state - log_error(:FETCH_SEGMENTS_FAILED, 'decode error') + log_failure('decode error') return nil end state == 'qualified' ? name : nil @@ -96,8 +100,8 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) private - def log_error(type, error) - @logger.log(Logger::ERROR, format(Optimizely::Helpers::Constants::ODP_LOGS[type], error)) + def log_failure(message, level = Logger::ERROR) + @logger.log(level, format(Optimizely::Helpers::Constants::ODP_LOGS[:FETCH_SEGMENTS_FAILED], message)) end end end diff --git a/spec/odp/zaius_graphql_api_manager_spec.rb b/spec/odp/zaius_graphql_api_manager_spec.rb index 8764f8f0..2a3faf74 100644 --- a/spec/odp/zaius_graphql_api_manager_spec.rb +++ b/spec/odp/zaius_graphql_api_manager_spec.rb @@ -266,7 +266,7 @@ ) end - it 'should log error and return nil with invalid identifier exception' do + it 'should log warning and return nil with invalid identifier exception' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: invalid_identifier_response_data.to_json) @@ -274,7 +274,7 @@ expect(segments).to be_nil expect(spy_logger).to have_received(:log).once.with( - Logger::ERROR, + Logger::WARN, 'Audience segments fetch failed (invalid identifier).' ) end From 35d677a878807080fa39a138ac7a57d94f01ddb2 Mon Sep 17 00:00:00 2001 From: Andy Leap Date: Mon, 8 Aug 2022 15:22:52 -0400 Subject: [PATCH 5/5] switch nil return to empty array --- .../odp/zaius_graphql_api_manager.rb | 14 +++--- spec/odp/zaius_graphql_api_manager_spec.rb | 44 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/optimizely/odp/zaius_graphql_api_manager.rb b/lib/optimizely/odp/zaius_graphql_api_manager.rb index 8e1cfa56..8123b69e 100644 --- a/lib/optimizely/odp/zaius_graphql_api_manager.rb +++ b/lib/optimizely/odp/zaius_graphql_api_manager.rb @@ -52,23 +52,23 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) rescue SocketError, Timeout::Error, Net::ProtocolError, Errno::ECONNRESET => e @logger.log(Logger::DEBUG, "GraphQL download failed: #{e}") log_failure('network error') - return nil + return [] rescue Errno::EINVAL, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError => e log_failure(e) - return nil + return [] end status = response.code.to_i if status >= 400 log_failure(status) - return nil + return [] end begin response = JSON.parse(response.body) rescue JSON::ParserError log_failure('JSON decode error') - return nil + return [] end if response.include?('errors') @@ -78,13 +78,13 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) else log_failure(error_class) end - return nil + return [] end audiences = response.dig('data', 'customer', 'audiences', 'edges') unless audiences log_failure('decode error') - return nil + return [] end audiences.filter_map do |edge| @@ -92,7 +92,7 @@ def fetch_segments(api_key, api_host, user_key, user_value, segments_to_check) state = edge.dig('node', 'state') unless name && state log_failure('decode error') - return nil + return [] end state == 'qualified' ? name : nil end diff --git a/spec/odp/zaius_graphql_api_manager_spec.rb b/spec/odp/zaius_graphql_api_manager_spec.rb index 2a3faf74..91c3a14b 100644 --- a/spec/odp/zaius_graphql_api_manager_spec.rb +++ b/spec/odp/zaius_graphql_api_manager_spec.rb @@ -240,12 +240,12 @@ expect(segments).to match_array [] end - it 'should log error and return nil when response is missing node' do + it 'should log error and return empty array when response is missing node' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: node_missing_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -253,12 +253,12 @@ ) end - it 'should log error and return nil when response keys are incorrect' do + it 'should log error and return empty array when response keys are incorrect' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: mixed_missing_keys_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -266,12 +266,12 @@ ) end - it 'should log warning and return nil with invalid identifier exception' do + it 'should log warning and return empty array with invalid identifier exception' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: invalid_identifier_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::WARN, @@ -279,12 +279,12 @@ ) end - it 'should log error and return nil with other exception' do + it 'should log error and return empty array with other exception' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: other_exception_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -292,12 +292,12 @@ ) end - it 'should log error and return nil with bad response data' do + it 'should log error and return empty array with bad response data' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: bad_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -305,12 +305,12 @@ ) end - it 'should log error and return nil with invalid name' do + it 'should log error and return empty array with invalid name' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: name_invalid_response_data) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -318,12 +318,12 @@ ) end - it 'should log error and return nil with invalid key' do + it 'should log error and return empty array with invalid key' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: invalid_edges_key_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -331,12 +331,12 @@ ) end - it 'should log error and return nil with invalid key in error body' do + it 'should log error and return empty array with invalid key in error body' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 200, body: invalid_key_for_error_response_data.to_json) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -344,12 +344,12 @@ ) end - it 'should log error and return nil with network error' do + it 'should log error and return empty array with network error' do stub_request(:post, "#{api_host}/v3/graphql") .and_raise(SocketError) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -362,12 +362,12 @@ ) end - it 'should log error and return nil with http status 400' do + it 'should log error and return empty array with http status 400' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 400) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR, @@ -375,12 +375,12 @@ ) end - it 'should log error and return nil with http status 500' do + it 'should log error and return empty array with http status 500' do stub_request(:post, "#{api_host}/v3/graphql") .to_return(status: 500) segments = zaius_manager.fetch_segments(api_key, api_host, user_key, user_value, %w[a b]) - expect(segments).to be_nil + expect(segments).to match_array([]) expect(spy_logger).to have_received(:log).once.with( Logger::ERROR,