diff --git a/lib/api.rb b/lib/api.rb index 7e5b7ba..9f122e1 100644 --- a/lib/api.rb +++ b/lib/api.rb @@ -4,7 +4,6 @@ require 'oj' require 'sinatra' require 'rack/cors' require 'quote' -require 'converter' configure :development do set :show_exceptions, :after_handler @@ -32,12 +31,8 @@ helpers do end end - def converter - @converter ||= Converter.new(params) - end - def symbols - @symbols ||= params.values_at('symbols', 'currencies').first + @symbols ||= params.values_at('symbols', 'to').compact.first end def jsonp(data) @@ -83,11 +78,6 @@ get(/(?\d{4}-\d{2}-\d{2})/) do jsonp quote_attributes end -get '/converter' do - params[:base] = params[:from] - jsonp Hash(amount: converter.convert(quote)) -end - not_found do halt 404, encode_json(error: 'Not found') end diff --git a/lib/converter.rb b/lib/converter.rb deleted file mode 100644 index 71605e9..0000000 --- a/lib/converter.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require 'bigdecimal' -require 'quote' - -class Converter - attr_reader :to, :from - - def initialize(params = {}) - @amount = params[:amount] - @from = params[:from] - @to = params[:to] - end - - # Converts an amount into a new currency - # @param quote [Quote] the quote object - # @return amount [BigDecimal] the converted amount - def convert(quote = latest_quote) - return amount if quote.base == to - amount * quote.rates.fetch(to) { raise Quote::Invalid, 'Invalid to' } - end - - def amount - BigDecimal(@amount.to_s) - end - - private - - def latest_quote - @latest_quote ||= Quote.new(base: from) - end -end diff --git a/lib/quote.rb b/lib/quote.rb index 6550b1f..003d976 100644 --- a/lib/quote.rb +++ b/lib/quote.rb @@ -5,12 +5,14 @@ require 'currency' class Quote Invalid = Class.new(StandardError) + DEFAULT_AMOUNT = 1 DEFAULT_BASE = 'EUR' - attr_reader :base, :date + attr_reader :amount, :base, :date def initialize(params = {}) - self.base = params[:base] + self.amount = params['amount'] + self.base = params.values_at(:base, :from).compact.first self.date = params[:date] end @@ -26,14 +28,18 @@ class Quote private - def base=(base) - @base = base&.upcase || DEFAULT_BASE + def amount=(value) + @amount = (value || DEFAULT_AMOUNT).to_f + raise Invalid, 'Invalid amount' if @amount.zero? end - def date=(date) - current_date = date ? Currency.current_date_before(date) : Currency.current_date - raise Invalid, 'Date too old' unless current_date - @date = current_date + def base=(value) + @base = value&.upcase || DEFAULT_BASE + end + + def date=(value) + @date = value ? Currency.current_date_before(value) : Currency.current_date + raise Invalid, 'Date too old' unless @date rescue Sequel::DatabaseError => ex raise Invalid, 'Invalid date' if ex.wrapped_exception.is_a?(PG::DataException) raise @@ -49,16 +55,16 @@ class Quote def find_default_rates Currency.where(date: date).reduce({}) do |rates, currency| - rates.update(currency.to_h) + rates.update(Hash[currency.to_h.map { |k, v| [k, round_rate(v * amount)] }]) end end def find_rebased_rates rates = find_default_rates - denominator = rates.update(DEFAULT_BASE => 1.0).delete(base) + denominator = rates.update(DEFAULT_BASE => amount).delete(base) raise Invalid, 'Invalid base' unless denominator rates.each do |iso_code, rate| - rates[iso_code] = round_rate(rate / denominator) + rates[iso_code] = round_rate(amount * rate / denominator) end rates diff --git a/spec/api_spec.rb b/spec/api_spec.rb index ee86446..7136309 100644 --- a/spec/api_spec.rb +++ b/spec/api_spec.rb @@ -26,11 +26,26 @@ describe 'the API' do json['base'].must_equal 'USD' end + it 'sets base amount' do + get '/latest?amount=10' + json['rates']['USD'].must_be :>, 10 + end + it 'filters symbols' do get '/latest?symbols=USD' json['rates'].keys.must_equal %w(USD) end + it 'aliases base as from' do + get '/latest?from=USD' + json['base'].must_equal 'USD' + end + + it 'aliases symbols as to' do + get '/latest?to=USD' + json['rates'].keys.must_equal %w(USD) + end + it 'returns historical quotes' do get '/2012-11-20' json['rates'].wont_be :empty? @@ -67,8 +82,8 @@ describe 'the API' do end end - it 'returns converted amount' do - get '/converter?to=USD&amount=100' - json['amount'].wont_be :nil? + it 'converts an amount' do + get '/latest?from=GBP&to=USD&amount=100' + json['rates']['USD'].must_be :>, 100 end end diff --git a/spec/converter_spec.rb b/spec/converter_spec.rb deleted file mode 100644 index 679aabc..0000000 --- a/spec/converter_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'converter' - -describe Converter do - let(:to) { 'USD' } - let(:from) { 'EUR' } - let(:amount) { '100.0' } - - let(:converter) { Converter.new to: to, from: from, amount: amount } - let(:quote) { Quote.new } - - describe '#amount' do - it 'casts to BigDecimal' do - converter.amount.must_be_kind_of BigDecimal - end - end - - describe '#convert' do - it 'should return the amount' do - quote.stub :base, to do - converter.convert(quote).must_equal BigDecimal(amount) - end - end - - it 'should use the rates to convert' do - quote.stub :rates, Hash(to => 1.2) do - converter.convert(quote).must_equal BigDecimal('120.0') - end - end - - it 'should raise with invalid currency' do - quote.stub :rates, {} do - -> { converter.convert(quote) }.must_raise Quote::Invalid, 'Invalid to' - end - end - - it 'should use latest quote as default' do - quote.stub :rates, Hash(to => 1.3) do - converter.stub :latest_quote, quote do - converter.convert.must_equal 130.0 - end - end - end - end -end diff --git a/spec/edge_cases_spec.rb b/spec/edge_cases_spec.rb index 9a9da6d..288ff79 100644 --- a/spec/edge_cases_spec.rb +++ b/spec/edge_cases_spec.rb @@ -34,6 +34,12 @@ describe 'the API' do json.wont_be_empty end + it 'will not process an invalid amount' do + get '/latest?amount=foo' + last_response.must_be :unprocessable? + json.wont_be_empty + end + it 'handles malformed queries' do get '/latest?base=USD?callback=?' last_response.must_be :unprocessable?