Skip to content

Instantly share code, notes, and snippets.

@joalbertg
Created April 30, 2022 02:22
Show Gist options
  • Save joalbertg/f33cc067584d2b9b8d6fdf35629ad827 to your computer and use it in GitHub Desktop.
Save joalbertg/f33cc067584d2b9b8d6fdf35629ad827 to your computer and use it in GitHub Desktop.
EasyBroker
# frozen_string_literal: true
# Quickstart
#
# Install:
# > gem install rspec --no-document
#
# Run tests
# > rspec -f d property_titles_service.rb
#
# Run code
# > ruby property_titles_service.rb
#
# note: uncomment line block between 229 and 234 to print titles
require 'rspec'
require 'ostruct'
require 'uri'
require 'net/http'
require 'json'
require 'pry'
class ApplicationService
def self.call(*args, &bloc)
new(*args, &bloc).call
end
private
def response(success: false, payload: {}, error: nil)
OpenStruct.new(success?: success, payload: payload, error: error)
end
end
class HttpClient
def self.call(http_method: 'get', params: {})
uri = URI(params[:url])
uri.query = URI.encode_www_form(params[:query]) if params[:query]&.any?
puts params[:url]
Net::HTTP.send(http_method, uri, params[:headers])
end
end
class EasyBrokerHttpConfig
def initialize(params)
@endpoint = params[:endpoint]
@headers = params[:headers] || {}
@query = params[:query] || {}
@body = params[:body] || {}
end
def call
{
url: "#{BASE_URL}/#{endpoint}?limit=50",
headers: header_params.merge(headers),
query: query,
body: body
}
end
private
BASE_URL = 'https://api.stagingeb.com/v1'
TOKEN = 'l7u502p8v46ba3ppgvj5y2aad50lb9'
attr_reader :endpoint, :headers, :query, :body
def header_params
{
'X-Authorization': TOKEN,
'Accept': 'application/json'
}
end
end
class PropertyTitlesService < ApplicationService
def initialize(params)
super()
@http_client = params[:http_client]
@provider = params[:provider]
end
def call
response(success: true, payload: titles)
rescue StandardError
response(error: StandardError.new(self))
end
private
attr_reader :http_client, :provider
def titles
titles = []
proc = proc { |property| property['title'] }
response = http_client.call(params: provider.call)
data = JSON.parse(response)
loop do
next_page = data.dig('pagination', 'next_page')
titles.concat(data['content'].map(&proc))
break unless next_page
response = http_client.call(params: provider.call.merge(url: next_page))
data = JSON.parse(response)
end
titles
end
end
# ---------------------------------------------------------------------------------------------------------------------
RSpec.shared_examples 'common errors' do
it do
expect(subject.class).to eq(OpenStruct)
expect(subject.success?).to be_falsy
expect(subject.error.class).to eq(StandardError)
end
end
RSpec.describe(PropertyTitlesService, type: :service) do
context '#call' do
describe 'success' do
describe 'without properties' do
let(:empty_properties) { { pagination: { next_page: nil }, content: [] } }
let(:http_client_mock) { class_double(HttpClient, call: empty_properties.to_json).as_stubbed_const }
let(:provider) { EasyBrokerHttpConfig.new({}) }
let(:result) do
described_class.call(
http_client: http_client_mock,
provider: provider
)
end
it 'returns empty' do
expect(result.success?).to be_truthy
expect(result.payload.size).to eq(0)
end
end
describe 'with properties' do
let(:property_without_next_page) do
{
pagination: { next_page: nil },
content: [{ title: 'house three' }, { title: 'house four' }]
}
end
let(:property_with_next_page) do
{
pagination: { next_page: 'https://api.stagingeb.com/v1/properties?limit=50&page=2' },
content: [{ title: 'house one' }, { title: 'house two' }]
}
end
let(:http_client_mock) { class_double(HttpClient, call: property_without_next_page.to_json).as_stubbed_const }
let(:provider) { EasyBrokerHttpConfig.new(endpoint: 'properties') }
let(:result) do
described_class.call(
http_client: http_client_mock,
provider: provider
)
end
it 'has correct types' do
expect(result.class).to eq(OpenStruct)
expect(result.success?).to be_truthy
expect(result.payload.class).to eq(Array)
expect(result.error).to be_nil
end
it 'returns two titles' do
expect(http_client_mock).to receive(:call).exactly(1).times
expect(result.payload.size).to eq(2)
expect(result.payload).to all(be_a(String))
end
it 'returns four titles' do
allow(:properties)
http_client_mock = class_double(HttpClient).as_stubbed_const
allow(http_client_mock).to receive(:call).with(anything).and_return(
property_with_next_page.to_json,
property_without_next_page.to_json
)
expect(http_client_mock).to receive(:call).exactly(2).times
result = described_class.call(http_client: http_client_mock, provider: provider)
expect(result.payload.size).to eq(4)
expect(result.payload).to all(be_a(String))
end
end
end
context 'failure' do
let(:provider) { EasyBrokerHttpConfig.new({}) }
let(:result) do
described_class.call(
http_client: http_client,
provider: provider
)
end
describe 'returns an OpenStruct error due to invalid params' do
subject { described_class.call({}) }
include_examples 'common errors'
end
describe 'returns an OpenStruct error due to invalid provider' do
subject { described_class.call(http_client: HttpClient, provider: provider) }
include_examples 'common errors'
end
describe 'return an OpenStruct error due to request timeout' do
let(:provider) { EasyBrokerHttpConfig.new(endpoint: 'properties') }
before do
allow(Net::HTTP).to receive(:get).and_raise(Timeout::Error)
end
subject { described_class.call(http_client: HttpClient, provider: provider) }
include_examples 'common errors'
end
end
end
end
# titles = PropertyTitlesService.call(
# http_client: HttpClient,
# provider: EasyBrokerHttpConfig.new(endpoint: 'properties')
# ).payload
# puts titles
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment