Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix a case when v1/action/query returns only InternalServerError #107

Merged
merged 12 commits into from
Dec 29, 2023
60 changes: 59 additions & 1 deletion lib/iron_bank/describe/excluded_fields.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module Describe
# current Zuora tenant, despites Zuora clearly marking these fields as
# `<selectable>true</true>` in their Describe API... /rant
#
# rubocop:disable Metrics/ClassLength
class ExcludedFields
extend Forwardable

Expand Down Expand Up @@ -87,9 +88,65 @@ def valid_query?
info "Successful query for #{object_name}"

true
rescue IronBank::InternalServerError, IronBank::BadRequestError => e
rescue IronBank::BadRequestError => e
@last_failed_fields = extract_fields_from_exception(e)
jurisgalang marked this conversation as resolved.
Show resolved Hide resolved

false
rescue IronBank::InternalServerError
@last_failed_fields = exctract_from_dividing
jurisgalang marked this conversation as resolved.
Show resolved Hide resolved

false
end

def exctract_from_dividing
jurisgalang marked this conversation as resolved.
Show resolved Hide resolved
@working_fields = []
@failed_fields = []
query_fields = object.query_fields.clone

divide_and_execute(query_fields)
# Set initial state for object.query_fields
object.query_fields.concat query_fields

info "Invalid fields '#{@failed_fields}' for #{object_name} query"

@failed_fields
end

# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def divide_and_execute(query_fields)
# Clear state before queries
object.query_fields.clear
# We repeat dividing until only one field has left
@failed_fields.push(query_fields.pop) if query_fields.one?
return if query_fields.empty?

mid = query_fields.size / 2
left = query_fields[0..mid - 1]
right = query_fields[mid..]

if execute_query(left)
@working_fields.concat(left)
else
divide_and_execute(left)
end

if execute_query(right)
@working_fields.concat(right)
else
divide_and_execute(right)
end
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize

def execute_query(fields)
object.query_fields.concat(@working_fields + fields)

object.where({ id: INVALID_OBJECT_ID })

true
rescue IronBank::InternalServerError
false
end

Expand All @@ -108,5 +165,6 @@ def extract_fields_from_exception(exception)
failed_fields
end
end
# rubocop:enable Metrics/ClassLength
jurisgalang marked this conversation as resolved.
Show resolved Hide resolved
end
end
92 changes: 46 additions & 46 deletions spec/iron_bank/describe/excluded_fields_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,63 @@
it "returns an empty array" do
expect(call).to eq([])
end
end

describe "when the object does have unqueryable fields" do
let(:object_name) { "Invoice" }
let(:error_message1) { "invalid field for query: invoice.invalidfield1" }
let(:error_message2) { "invalid field for query: invoice.invalidfield2" }
let(:query_fields) { %w[InvalidField2 InvalidField1 ValidField] }
let(:sorted_fields) { call.sort }
context "with invalid fields" do
let!(:fields_count) { query_fields.size }

before do
allow(object).to receive(:query_fields).and_return(query_fields)

num_query = 0

# Querying `Object#where(id: invalid_id)`:
# - The first time ("InvalidField2") raises an exception
# - The second time ("InvalidField1") raises an exception
# - All following calls are successful
allow(object).to receive(:where).with({ id: invalid_id }) do
num_query += 1

# NOTE: This ordering is dictated by the order of `query_fields`
case num_query
when 1 then raise IronBank::InternalServerError, error_message2
when 2 then raise IronBank::InternalServerError, error_message1
else anything
before do
allow(object).to receive(:query_fields).and_return(query_fields)

num_query = 0

# Querying `Object#where(id: invalid_id)`:
# - The first 2 queries are initial and failed
# - Plus 2 * invalid fields count and its failed
# - All following calls are successful
allow(object).to receive(:where).with({ id: invalid_id }) do
num_query += 1

# NOTE: This ordering is dictated by the order of `query_fields`
case num_query
when 0..invalid_fields_count * 2 then raise IronBank::InternalServerError
else anything
end
end
end
end

it "makes three queries" do
call
context "when 1 invalid field" do
let(:query_fields) { %w[InvalidField1 Fields2 Field3] }
let(:invalid_fields_count) { 1 }

expect(object).to have_received(:where).exactly(3).times
end
it "makes initial 2 queries and 2 * working query fields count queries" do
call

it "returns the unqueryable fields" do
expect(call).to contain_exactly("InvalidField1", "InvalidField2")
end
expect(object).to have_received(:where).exactly(2 + (invalid_fields_count * 2)).times
end

it "returns a sorted array of unqueryable fields" do
expect(call.sort).to eq(call)
end
end
it "returns the failed field" do
expect(call).to contain_exactly("InvalidField1")
end

describe "when the query results in a `InternalServerError` error" do
let(:object_name) { "Account" }
it "returns a sorted array of unqueryable fields" do
expect(call.sort).to eq(call)
end
end

before do
allow(object).
to receive(:where).
with({ id: invalid_id }).
and_raise(IronBank::InternalServerError)
end
context "when 2 invalid fields" do
let(:query_fields) { %w[InvalidField1 InvalidField2 Field3] }
let(:invalid_fields_count) { 2 }

it "raises a `RuntimeError` error" do
expect { call }.to raise_error(RuntimeError, /Could not parse error/)
it "makes initial 2 queries and 2 * working query fields count queries" do
call

expect(object).to have_received(:where).exactly(2 + (invalid_fields_count * 2)).times
end

it "returns the failed fields" do
expect(call).to contain_exactly("InvalidField1", "InvalidField2")
end
end
end
end
end