Skip to content

Instantly share code, notes, and snippets.

@dudo
Last active July 8, 2020 20:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dudo/c3dae539d7d803b620b69dc16c36e1b3 to your computer and use it in GitHub Desktop.
Save dudo/c3dae539d7d803b620b69dc16c36e1b3 to your computer and use it in GitHub Desktop.
RSpec.describe Api::ClientBase do
subject(:api) { described_class.new(config) }
let(:scheme) { 'https' }
let(:host) { Faker::Internet.domain_name }
let(:port) { 3000 }
let(:config) do
{
'scheme' => scheme,
'host' => host,
'port' => port
}
end
describe '.new' do
it { expect(api.path).to eq '/' }
it { expect(api.request_method).to eq :get }
context 'when no host is provided' do
let(:host) { nil }
it { expect { api }.to raise_error(ArgumentError, '`host` must be provided within configuration') }
end
end
describe '#call!' do
let(:body) { {} }
let(:response) { OpenStruct.new(code: code, body: body.to_json) }
before { allow(api).to receive(:response).and_return(response) }
context 'when 2xx' do
let(:code) { [*200..299].sample }
before { api.call! }
it { expect(api.call!).to eq response }
it { expect(api.response).to eq response }
it { expect(api.response_body).to eq({}) }
context 'when body is present' do
let(:body) { { 'foo' => 'bar' } }
it { expect(api.response_body).to eq(body) }
end
end
context 'when NOT 2xx' do
let(:code) { [*300..599].sample }
it { expect { api.call! }.to raise_error(RuntimeError, "Received non-2xx status: #{code}") }
end
end
describe '#base_uri' do
it { expect(api.base_uri).to eq "#{scheme}://#{host}:#{port}" }
context 'when no scheme is provided' do
let(:scheme) { nil }
it { expect(api.base_uri).to eq "http://#{host}:#{port}" }
end
context 'when no port is provided' do
let(:port) { nil }
it { expect(api.base_uri).to eq "#{scheme}://#{host}" }
end
end
describe '#uri' do
it { expect(api.uri.to_s).to eq "#{api.base_uri}/" }
end
describe '#headers' do
it { expect(api.headers).to eq({}) }
end
describe '#body' do
it { expect(api.body).to eq({}) }
end
describe '#query' do
it { expect(api.query).to eq({}) }
end
describe '#options' do
it { expect(api.options).to eq({}) }
end
describe '#response' do
before do
stub_request(:get, "#{scheme}://#{host}:#{port}").to_return(status: 200, body: '')
end
it { expect(api.response).to be_kind_of HTTParty::Response }
end
end
# frozen_string_literal: true
# frozen_string_literal: true
module Api
class ClientBase
include HTTParty
attr_reader :config, :path, :request_method
def initialize(config)
@config = config
@path ||= '/'
@request_method ||= :get
raise ArgumentError, '`host` must be provided within configuration' if config['host'].blank?
end
def call!
# This will typically be overridden in the subclass to act on the response
case response.code.to_i
when 200..299
response
else
raise "Received non-2xx status: #{response.code}"
end
end
def base_uri
@base_uri ||= [scheme, '://', host].join
end
def uri
URI(base_uri).tap do |uri|
uri.path = path
end
end
def headers
{}
end
def body
{}
end
def query
{}
end
def options
{}
end
def response
@response ||= self.class.send(
request_method,
uri,
headers: headers,
body: body,
query: query,
**options
)
end
def response_body
@response_body ||= response.body.blank? ? {} : JSON.parse(response.body).with_indifferent_access
end
private
def scheme
@scheme ||= config['scheme'] || 'http'
end
def host
@host ||= [config['host'], config['port']].compact.join(':')
end
end
end
module Api
module Example
class Base < ClientBase
def initialize
super(Rails.application.config_for(:example))
end
def headers
{
Authorization: "Bearer #{auth_token}",
content_type: 'application/json'
}
end
end
end
end
module Api
module Example
module Things
class Fetch < Base
def initialize(id:)
@path = "/v1/things/#{id}"
super()
end
end
end
end
end
default: &default
scheme: https
host: sandbox.example.com
port: 9443
development: &development
<<: *default
test:
<<: *development
production:
host: api.example.com
port: 10443
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment