From 5575d98fd57b74a1212ed2ddd28b66b00dc8ee6d Mon Sep 17 00:00:00 2001 From: Robot Date: Wed, 31 Jul 2024 08:34:27 +0000 Subject: [PATCH] Changes from gocardless/gocardless-pro-ruby-template@f24ecf703484d38391fa3951bd2d98af33c7fab7 --- lib/gocardless_pro.rb | 3 + lib/gocardless_pro/client.rb | 5 + lib/gocardless_pro/resources/export.rb | 44 +++ .../services/exports_service.rb | 75 ++++ spec/resources/export_spec.rb | 191 +++++++++ spec/services/exports_service_spec.rb | 370 ++++++++++++++++++ 6 files changed, 688 insertions(+) create mode 100644 lib/gocardless_pro/resources/export.rb create mode 100644 lib/gocardless_pro/services/exports_service.rb create mode 100644 spec/resources/export_spec.rb create mode 100644 spec/services/exports_service_spec.rb diff --git a/lib/gocardless_pro.rb b/lib/gocardless_pro.rb index bab1ca2..9c60346 100644 --- a/lib/gocardless_pro.rb +++ b/lib/gocardless_pro.rb @@ -78,6 +78,9 @@ module GoCardlessPro require_relative 'gocardless_pro/resources/event' require_relative 'gocardless_pro/services/events_service' +require_relative 'gocardless_pro/resources/export' +require_relative 'gocardless_pro/services/exports_service' + require_relative 'gocardless_pro/resources/instalment_schedule' require_relative 'gocardless_pro/services/instalment_schedules_service' diff --git a/lib/gocardless_pro/client.rb b/lib/gocardless_pro/client.rb index f50b0ae..a916094 100644 --- a/lib/gocardless_pro/client.rb +++ b/lib/gocardless_pro/client.rb @@ -68,6 +68,11 @@ def events @events ||= Services::EventsService.new(@api_service) end + # Access to the service for export to make API calls + def exports + @exports ||= Services::ExportsService.new(@api_service) + end + # Access to the service for instalment_schedule to make API calls def instalment_schedules @instalment_schedules ||= Services::InstalmentSchedulesService.new(@api_service) diff --git a/lib/gocardless_pro/resources/export.rb b/lib/gocardless_pro/resources/export.rb new file mode 100644 index 0000000..d1f59ea --- /dev/null +++ b/lib/gocardless_pro/resources/export.rb @@ -0,0 +1,44 @@ +# +# This client is automatically generated from a template and JSON schema definition. +# See https://github.com/gocardless/gocardless-pro-ruby#contributing before editing. +# + +require 'uri' + +module GoCardlessPro + # A module containing classes for each of the resources in the GC Api + module Resources + # Represents an instance of a export resource returned from the API + + # File-based exports of data + class Export + attr_reader :created_at + attr_reader :currency + attr_reader :download_url + attr_reader :export_type + attr_reader :id + + # Initialize a export resource instance + # @param object [Hash] an object returned from the API + def initialize(object, response = nil) + @object = object + + @created_at = object['created_at'] + @currency = object['currency'] + @download_url = object['download_url'] + @export_type = object['export_type'] + @id = object['id'] + @response = response + end + + def api_response + ApiResponse.new(@response) + end + + # Provides the export resource as a hash of all its readable attributes + def to_h + @object + end + end + end +end diff --git a/lib/gocardless_pro/services/exports_service.rb b/lib/gocardless_pro/services/exports_service.rb new file mode 100644 index 0000000..c958497 --- /dev/null +++ b/lib/gocardless_pro/services/exports_service.rb @@ -0,0 +1,75 @@ +require_relative './base_service' + +# encoding: utf-8 +# +# This client is automatically generated from a template and JSON schema definition. +# See https://github.com/gocardless/gocardless-pro-ruby#contributing before editing. +# + +module GoCardlessPro + module Services + # Service for making requests to the Export endpoints + class ExportsService < BaseService + # Returns a single export. + # Example URL: /exports/:identity + # + # @param identity # Unique identifier, beginning with "EX". + # @param options [Hash] parameters as a hash, under a params key. + def get(identity, options = {}) + path = sub_url('/exports/:identity', { + 'identity' => identity, + }) + + options[:retry_failures] = true + + response = make_request(:get, path, options) + + return if response.body.nil? + + Resources::Export.new(unenvelope_body(response.body), response) + end + + # Returns a list of exports which are available for download. + # Example URL: /exports + # @param options [Hash] parameters as a hash, under a params key. + def list(options = {}) + path = '/exports' + + options[:retry_failures] = true + + response = make_request(:get, path, options) + + ListResponse.new( + response: response, + unenveloped_body: unenvelope_body(response.body), + resource_class: Resources::Export + ) + end + + # Get a lazily enumerated list of all the items returned. This is similar to the `list` method but will paginate for you automatically. + # + # @param options [Hash] parameters as a hash. If the request is a GET, these will be converted to query parameters. + # Otherwise they will be the body of the request. + def all(options = {}) + Paginator.new( + service: self, + options: options + ).enumerator + end + + private + + # Unenvelope the response of the body using the service's `envelope_key` + # + # @param body [Hash] + def unenvelope_body(body) + body[envelope_key] || body['data'] + end + + # return the key which API responses will envelope data under + def envelope_key + 'exports' + end + end + end +end diff --git a/spec/resources/export_spec.rb b/spec/resources/export_spec.rb new file mode 100644 index 0000000..701e942 --- /dev/null +++ b/spec/resources/export_spec.rb @@ -0,0 +1,191 @@ +require 'spec_helper' + +describe GoCardlessPro::Resources::Export do + let(:client) do + GoCardlessPro::Client.new( + access_token: 'SECRET_TOKEN' + ) + end + + let(:response_headers) { { 'Content-Type' => 'application/json' } } + + describe '#get' do + let(:id) { 'ID123' } + + subject(:get_response) { client.exports.get(id) } + + context 'passing in a custom header' do + let!(:stub) do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/). + with(headers: { 'Foo' => 'Bar' }). + to_return( + body: { + 'exports' => { + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }, + }.to_json, + headers: response_headers + ) + end + + subject(:get_response) do + client.exports.get(id, headers: { + 'Foo' => 'Bar', + }) + end + + it 'includes the header' do + get_response + expect(stub).to have_been_requested + end + end + + context 'when there is a export to return' do + before do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: { + 'exports' => { + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }, + }.to_json, + headers: response_headers + ) + end + + it 'wraps the response in a resource' do + expect(get_response).to be_a(GoCardlessPro::Resources::Export) + end + end + + context 'when nothing is returned' do + before do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: '', + headers: response_headers + ) + end + + it 'returns nil' do + expect(get_response).to be_nil + end + end + + context "when an ID is specified which can't be included in a valid URI" do + let(:id) { '`' } + + it "doesn't raise an error" do + expect { get_response }.to_not raise_error(/bad URI/) + end + end + end + + describe '#list' do + describe 'with no filters' do + subject(:get_list_response) { client.exports.list } + + before do + stub_request(:get, %r{.*api.gocardless.com/exports}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { + before: nil, + after: 'ABC123', + }, + }, + }.to_json, + headers: response_headers + ) + end + + it 'wraps each item in the resource class' do + expect(get_list_response.records.map { |x| x.class }.uniq.first).to eq(GoCardlessPro::Resources::Export) + + expect(get_list_response.records.first.created_at).to eq('created_at-input') + + expect(get_list_response.records.first.currency).to eq('currency-input') + + expect(get_list_response.records.first.download_url).to eq('download_url-input') + + expect(get_list_response.records.first.export_type).to eq('export_type-input') + + expect(get_list_response.records.first.id).to eq('id-input') + end + + it 'exposes the cursors for before and after' do + expect(get_list_response.before).to eq(nil) + expect(get_list_response.after).to eq('ABC123') + end + + specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') } + end + end + + describe '#all' do + let!(:first_response_stub) do + stub_request(:get, %r{.*api.gocardless.com/exports$}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { after: 'AB345' }, + limit: 1, + }, + }.to_json, + headers: response_headers + ) + end + + let!(:second_response_stub) do + stub_request(:get, %r{.*api.gocardless.com/exports\?after=AB345}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + limit: 2, + cursors: {}, + }, + }.to_json, + headers: response_headers + ) + end + + it 'automatically makes the extra requests' do + expect(client.exports.all.to_a.length).to eq(2) + expect(first_response_stub).to have_been_requested + expect(second_response_stub).to have_been_requested + end + end +end diff --git a/spec/services/exports_service_spec.rb b/spec/services/exports_service_spec.rb new file mode 100644 index 0000000..081b4aa --- /dev/null +++ b/spec/services/exports_service_spec.rb @@ -0,0 +1,370 @@ +require 'spec_helper' + +describe GoCardlessPro::Services::ExportsService do + let(:client) do + GoCardlessPro::Client.new( + access_token: 'SECRET_TOKEN' + ) + end + + let(:response_headers) { { 'Content-Type' => 'application/json' } } + + describe '#get' do + let(:id) { 'ID123' } + + subject(:get_response) { client.exports.get(id) } + + context 'passing in a custom header' do + let!(:stub) do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/). + with(headers: { 'Foo' => 'Bar' }). + to_return( + body: { + 'exports' => { + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }, + }.to_json, + headers: response_headers + ) + end + + subject(:get_response) do + client.exports.get(id, headers: { + 'Foo' => 'Bar', + }) + end + + it 'includes the header' do + get_response + expect(stub).to have_been_requested + end + end + + context 'when there is a export to return' do + before do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: { + 'exports' => { + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }, + }.to_json, + headers: response_headers + ) + end + + it 'wraps the response in a resource' do + expect(get_response).to be_a(GoCardlessPro::Resources::Export) + end + end + + context 'when nothing is returned' do + before do + stub_url = '/exports/:identity'.gsub(':identity', id) + stub_request(:get, /.*api.gocardless.com#{stub_url}/).to_return( + body: '', + headers: response_headers + ) + end + + it 'returns nil' do + expect(get_response).to be_nil + end + end + + context "when an ID is specified which can't be included in a valid URI" do + let(:id) { '`' } + + it "doesn't raise an error" do + expect { get_response }.to_not raise_error(/bad URI/) + end + end + + describe 'retry behaviour' do + before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) } + + it 'retries timeouts' do + stub_url = '/exports/:identity'.gsub(':identity', id) + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_timeout.then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + + it 'retries 5XX errors, other than 500s' do + stub_url = '/exports/:identity'.gsub(':identity', id) + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_return({ status: 502, + headers: { 'Content-Type' => 'text/html' }, + body: 'Response from Cloudflare' }). + then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + + it 'retries 500 errors returned by the API' do + stub_url = '/exports/:identity'.gsub(':identity', id) + + gocardless_error = { + 'error' => { + 'message' => 'Internal server error', + 'documentation_url' => 'https://developer.gocardless.com/#gocardless', + 'errors' => [{ + 'message' => 'Internal server error', + 'reason' => 'internal_server_error', + }], + 'type' => 'gocardless', + 'code' => 500, + 'request_id' => 'dummy_request_id', + 'id' => 'dummy_exception_id', + }, + } + + stub = stub_request(:get, /.*api.gocardless.com#{stub_url}/). + to_return({ status: 500, + headers: response_headers, + body: gocardless_error.to_json }). + then.to_return({ status: 200, headers: response_headers }) + + get_response + expect(stub).to have_been_requested.twice + end + end + end + + describe '#list' do + describe 'with no filters' do + subject(:get_list_response) { client.exports.list } + + let(:body) do + { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { + before: nil, + after: 'ABC123', + }, + }, + }.to_json + end + + before do + stub_request(:get, %r{.*api.gocardless.com/exports}).to_return( + body: body, + headers: response_headers + ) + end + + it 'wraps each item in the resource class' do + expect(get_list_response.records.map { |x| x.class }.uniq.first).to eq(GoCardlessPro::Resources::Export) + + expect(get_list_response.records.first.created_at).to eq('created_at-input') + + expect(get_list_response.records.first.currency).to eq('currency-input') + + expect(get_list_response.records.first.download_url).to eq('download_url-input') + + expect(get_list_response.records.first.export_type).to eq('export_type-input') + + expect(get_list_response.records.first.id).to eq('id-input') + end + + it 'exposes the cursors for before and after' do + expect(get_list_response.before).to eq(nil) + expect(get_list_response.after).to eq('ABC123') + end + + specify { expect(get_list_response.api_response.headers).to eql('content-type' => 'application/json') } + + describe 'retry behaviour' do + before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) } + + it 'retries timeouts' do + stub = stub_request(:get, %r{.*api.gocardless.com/exports}). + to_timeout.then.to_return({ status: 200, headers: response_headers, body: body }) + + get_list_response + expect(stub).to have_been_requested.twice + end + + it 'retries 5XX errors' do + stub = stub_request(:get, %r{.*api.gocardless.com/exports}). + to_return({ status: 502, + headers: { 'Content-Type' => 'text/html' }, + body: 'Response from Cloudflare' }). + then.to_return({ status: 200, headers: response_headers, body: body }) + + get_list_response + expect(stub).to have_been_requested.twice + end + end + end + end + + describe '#all' do + let!(:first_response_stub) do + stub_request(:get, %r{.*api.gocardless.com/exports$}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { after: 'AB345' }, + limit: 1, + }, + }.to_json, + headers: response_headers + ) + end + + let!(:second_response_stub) do + stub_request(:get, %r{.*api.gocardless.com/exports\?after=AB345}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + limit: 2, + cursors: {}, + }, + }.to_json, + headers: response_headers + ) + end + + it 'automatically makes the extra requests' do + expect(client.exports.all.to_a.length).to eq(2) + expect(first_response_stub).to have_been_requested + expect(second_response_stub).to have_been_requested + end + + describe 'retry behaviour' do + before { allow_any_instance_of(GoCardlessPro::Request).to receive(:sleep) } + + it 'retries timeouts' do + first_response_stub = stub_request(:get, %r{.*api.gocardless.com/exports$}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { after: 'AB345' }, + limit: 1, + }, + }.to_json, + headers: response_headers + ) + + second_response_stub = stub_request(:get, %r{.*api.gocardless.com/exports\?after=AB345}). + to_timeout.then. + to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + limit: 2, + cursors: {}, + }, + }.to_json, + headers: response_headers + ) + + client.exports.all.to_a + + expect(first_response_stub).to have_been_requested + expect(second_response_stub).to have_been_requested.twice + end + + it 'retries 5XX errors' do + first_response_stub = stub_request(:get, %r{.*api.gocardless.com/exports$}).to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + cursors: { after: 'AB345' }, + limit: 1, + }, + }.to_json, + headers: response_headers + ) + + second_response_stub = stub_request(:get, %r{.*api.gocardless.com/exports\?after=AB345}). + to_return( + status: 502, + body: 'Response from Cloudflare', + headers: { 'Content-Type' => 'text/html' } + ).then.to_return( + body: { + 'exports' => [{ + + 'created_at' => 'created_at-input', + 'currency' => 'currency-input', + 'download_url' => 'download_url-input', + 'export_type' => 'export_type-input', + 'id' => 'id-input', + }], + meta: { + limit: 2, + cursors: {}, + }, + }.to_json, + headers: response_headers + ) + + client.exports.all.to_a + + expect(first_response_stub).to have_been_requested + expect(second_response_stub).to have_been_requested.twice + end + end + end +end