From a96e56808e969bbc8e55279e55ce838ee5c1beac Mon Sep 17 00:00:00 2001 From: Hakan Ensari Date: Thu, 5 Jul 2018 20:19:37 +0100 Subject: [PATCH] Implement time series ... along with some minor miscellaneous refactoring This finally completes fixerAPI/fixer#22 --- .rubocop.yml | 6 +- README.md | 16 ++-- config/initializers/sequel.rb | 3 +- lib/currency.rb | 19 +++++ lib/query.rb | 13 ++- lib/quote.rb | 61 +------------ lib/quote/base.rb | 82 ++++++++++++++++++ lib/quote/end_of_day.rb | 26 ++++++ lib/quote/interval.rb | 29 +++++++ lib/roundable.rb | 23 +++++ lib/web/public/images/favicon.ico | Bin 0 -> 15406 bytes lib/web/public/images/frankfurter-icon.png | Bin 10392 -> 0 bytes lib/web/public/images/frankfurter.png | Bin 26738 -> 27096 bytes lib/web/public/stylesheets/application.css | 53 +++++++++--- lib/web/server.rb | 50 +++++++---- lib/web/views/index.erb | 34 ++++---- spec/currency_spec.rb | 67 +++++++++------ spec/edge_cases_spec.rb | 2 +- spec/helper.rb | 2 + spec/query_spec.rb | 19 +++-- spec/quote/base_spec.rb | 55 ++++++++++++ spec/quote/end_of_day_spec.rb | 83 ++++++++++++++++++ spec/quote/interval_spec.rb | 94 +++++++++++++++++++++ spec/quote_spec.rb | 53 ------------ spec/roundable_spec.rb | 31 +++++++ spec/web/server_spec.rb | 17 ++-- 26 files changed, 618 insertions(+), 220 deletions(-) create mode 100644 lib/quote/base.rb create mode 100644 lib/quote/end_of_day.rb create mode 100644 lib/quote/interval.rb create mode 100644 lib/roundable.rb create mode 100644 lib/web/public/images/favicon.ico delete mode 100644 lib/web/public/images/frankfurter-icon.png create mode 100644 spec/quote/base_spec.rb create mode 100644 spec/quote/end_of_day_spec.rb create mode 100644 spec/quote/interval_spec.rb delete mode 100644 spec/quote_spec.rb create mode 100644 spec/roundable_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index d402215..f92cf18 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -3,6 +3,6 @@ AllCops: Documentation: Enabled: false Metrics/BlockLength: - Enabled: false -Metrics/LineLength: - Enabled: false + ExcludedMethods: ['describe', 'helpers'] +Metrics/MethodLength: + Max: 11 diff --git a/README.md b/README.md index 55633d0..1137ca7 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ Rates are updated around 4PM CET every working day. ## Usage -Get the latest foreign exchange rates. +Get the current foreign exchange rates. ```http -GET /latest +GET /current ``` Get historical rates for any day since 1999. @@ -23,25 +23,25 @@ GET /2000-01-03 Rates quote against the Euro by default. Quote against a different currency. ```http -GET /latest?from=USD +GET /current?from=USD ``` Request specific exchange rates. ```http -GET /latest?to=GBP +GET /current?to=GBP ``` -Change the amount requested. +Change the converted amount. ```http -GET /latest?amount=100 +GET /current?amount=100 ``` Finally, use all the above together. ```http -GET /latest?from=EUR&to=GBP&amount=100 +GET /current?from=EUR&to=GBP&amount=100 ``` The primary use case is client side. For instance, with [money.js](https://openexchangerates.github.io/money.js/) in the browser @@ -52,7 +52,7 @@ let demo = () => { alert("£1 = $" + rate.toFixed(4)) } -fetch('https://yourdomain.com/latest') +fetch('https://yourdomain.com/current') .then((resp) => resp.json()) .then((data) => fx.rates = data.rates) .then(demo) diff --git a/config/initializers/sequel.rb b/config/initializers/sequel.rb index c857bae..e9010e5 100644 --- a/config/initializers/sequel.rb +++ b/config/initializers/sequel.rb @@ -4,4 +4,5 @@ require 'pg' require 'sequel' Sequel.single_threaded = true -Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost/frankfurter_#{App.env}") +Sequel.connect(ENV['DATABASE_URL'] || + "postgres://localhost/frankfurter_#{App.env}") diff --git a/lib/currency.rb b/lib/currency.rb index d5e1cfb..89853b9 100644 --- a/lib/currency.rb +++ b/lib/currency.rb @@ -7,5 +7,24 @@ class Currency < Sequel::Model .order(Sequel.desc(:date)) .limit(1)) end + + def between(date_interval) + query = where(date: date_interval).order(:date) + length = date_interval.last - date_interval.first + if length > 365 + query.sampled('month') + elsif length > 90 + query.sampled('week') + else + query + end + end + + def sampled(precision) + sampled_date = Sequel.lit("date_trunc('#{precision}', date)") + select(:iso_code).select_append { avg(rate).as(rate) } + .select_append(sampled_date.as(:date)) + .group(:iso_code, sampled_date) + end end end diff --git a/lib/query.rb b/lib/query.rb index 499ac52..f3f3fc5 100644 --- a/lib/query.rb +++ b/lib/query.rb @@ -6,19 +6,24 @@ class Query end def amount - @params[:amount].to_f if @params[:amount] # rubocop:disable Style/SafeNavigation + return unless @params[:amount] + @params[:amount].to_f end def base - @params.values_at(:base, :from).compact.first&.upcase + @params.values_at(:from, :base).compact.first&.upcase end def symbols - @params.values_at(:symbols, :to).compact.first&.split(',') + @params.values_at(:to, :symbols).compact.first&.split(',') end def date - @params[:date] + if @params[:date] + Date.parse(@params[:date]) + else + (Date.parse(@params[:start_date])..Date.parse(@params[:end_date])) + end end def to_h diff --git a/lib/quote.rb b/lib/quote.rb index 53e7e0c..359cf45 100644 --- a/lib/quote.rb +++ b/lib/quote.rb @@ -1,61 +1,4 @@ # frozen_string_literal: true -require 'currency' - -class Quote - DEFAULT_BASE = 'EUR' - - def initialize(amount: 1.0, - base: DEFAULT_BASE, - date: Date.today.to_s, - symbols: nil) - @amount = amount - @base = base - @date = date - @symbols = symbols - end - - def to_h - { base: @base, date: date, rates: calculate_rates } - end - - def date - currencies.first[:date].to_s - end - - private - - def calculate_rates # rubocop:disable Metrics/AbcSize - rates = currencies.each_with_object({}) do |currency, hsh| - hsh[currency[:iso_code]] = currency[:rate] - end - - return rates if @base == DEFAULT_BASE && @amount == 1.0 - - if @symbols.nil? || @symbols.include?(DEFAULT_BASE) || @base == DEFAULT_BASE - rates.update(DEFAULT_BASE => 1.0) - end - divisor = rates.delete(@base) - - rates.sort.map! { |ic, rate| [ic, round(@amount * rate / divisor)] }.to_h - end - - def currencies - @currencies ||= begin - scope = Currency.latest(@date) - scope = scope.where(iso_code: @symbols + [@base]) if @symbols - - scope.order(:iso_code).naked - end - end - - # To paraphrase Wikipedia, most currency pairs are quoted to four decimal - # places. An exception to this is exchange rates with a value of less than - # 1.000, which are quoted to five or six decimal places. Exchange rates - # greater than around 20 are usually quoted to three decimal places and - # exchange rates greater than 80 are quoted to two decimal places. - # Currencies over 5000 are usually quoted with no decimal places. - def round(rate) - Float(format('%.5g', rate)) - end -end +require 'quote/end_of_day' +require 'quote/interval' diff --git a/lib/quote/base.rb b/lib/quote/base.rb new file mode 100644 index 0000000..cf78abf --- /dev/null +++ b/lib/quote/base.rb @@ -0,0 +1,82 @@ +# frozen_string_literal: true + +require 'roundable' + +module Quote + class Base + include Roundable + + DEFAULT_BASE = 'EUR' + + attr_reader :amount, :base, :date, :symbols, :result + + def initialize(date:, amount: 1.0, base: 'EUR', symbols: nil) + @date = date + @amount = amount + @base = base + @symbols = symbols + @result = {} + end + + def perform + return false if result.frozen? + + prepare_rates + rebase_rates if must_rebase? + result.freeze + + true + end + + def must_rebase? + base != 'EUR' + end + + def formatted + raise NotImplementedError + end + + def not_found? + result.empty? + end + + def cache_key + raise NotImplementedError + end + + private + + def data + @data ||= fetch_data + end + + def fetch_data + raise NotImplementedError + end + + def prepare_rates + data.each_with_object(result) do |currency, result| + date = currency[:date].to_date.to_s + result[date] ||= {} + result[date][currency[:iso_code]] = round(amount * currency[:rate]) + end + end + + def rebase_rates + result.each do |date, rates| + add_euro(rates) + + divisor = rates.delete(base) + result[date] = rates.sort + .map! do |iso_code, rate| + [iso_code, round(amount * rate / divisor)] + end + .to_h + end + end + + def add_euro(rates) + rates['EUR'] = amount if symbols.nil? || symbols.include?('EUR') + end + end +end diff --git a/lib/quote/end_of_day.rb b/lib/quote/end_of_day.rb new file mode 100644 index 0000000..34f6c39 --- /dev/null +++ b/lib/quote/end_of_day.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'currency' +require 'quote/base' + +module Quote + class EndOfDay < Base + def formatted + { base: base, date: result.keys.first, rates: result.values.first } + end + + def cache_key + return if not_found? + Digest::MD5.hexdigest(result.keys.first) + end + + private + + def fetch_data + scope = Currency.latest(date) + scope = scope.where(iso_code: symbols + [base]) if symbols + + scope.naked + end + end +end diff --git a/lib/quote/interval.rb b/lib/quote/interval.rb new file mode 100644 index 0000000..5059708 --- /dev/null +++ b/lib/quote/interval.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'currency' +require 'quote/base' + +module Quote + class Interval < Base + def formatted + { base: base, + start_date: result.keys.first, + end_date: result.keys.last, + rates: result } + end + + def cache_key + return if not_found? + Digest::MD5.hexdigest(result.keys.last) + end + + private + + def fetch_data + scope = Currency.between(date) + scope = scope.where(iso_code: symbols + [base]) if symbols + + scope.naked + end + end +end diff --git a/lib/roundable.rb b/lib/roundable.rb new file mode 100644 index 0000000..a372e36 --- /dev/null +++ b/lib/roundable.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Roundable + # To paraphrase Wikipedia, most currency pairs are quoted to four decimal + # places. An exception to this is exchange rates with a value of less than + # 1.000, which are quoted to five or six decimal places. Exchange rates + # greater than around 20 are usually quoted to three decimal places and + # exchange rates greater than 80 are quoted to two decimal places. + # Currencies over 5000 are usually quoted with no decimal places. + def round(value) + if value > 5000 + value.round + elsif value > 80 + Float(format('%.2f', value)) + elsif value > 20 + Float(format('%.3f', value)) + elsif value > 1 + Float(format('%.4f', value)) + else + Float(format('%.5f', value)) + end + end +end diff --git a/lib/web/public/images/favicon.ico b/lib/web/public/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8c6f5d0b6cc853f86b2c184a280f33301f211d87 GIT binary patch literal 15406 zcmeHN%}*0S6n{ql06ci_$#CT4+5f?)F&g6+(h!Xa2qr{4pe6(kc;H}SL=$5)9yHKu zTS{y#rQjDR65~f}Dn_XqHBiLTBG0!j-PoqvOqcD>u6>jIy4l&8zL(eEd-I#=0&IYd zP*(?Zb%A>mz%~GIxzzjm%>V<`*U+%`d@Dd~3qU=Mp(!*&?War0Hn^h^?FQeW7QDCr zgkirvPt9O_FyQF|`O8jO+V;E}f(lFd~s-pR_zs&q2Czj{an3-SDMYOZW z^!lLjU#&uAKVw~e)w56aZ~F2Lob11%3;*HPs@muJ%j=KTI!Mbu8?67zK5&lqQF?fD z$5*}lD_eQghu>bsO}9UKx(5^95h1)7xM{k5tNF(Ec<*He2lUPbYNcN}C!r%+I6&ZY z63#g*a*k~<&zJ+X!vXK!WBBaiHB@w|g7LE#aA;?fV>D{uXgg*XmsD|2`=1Yy3H0u1 zX8$cZ1LgS3trZ-9j{kbrYvvF0hxzl5et0mB9;fODQbD<;U$~h1O86p#PY);2cd$j8 zUMi?R0_V5ILw+QZQvUPK=gfcQKk4W6#}vN5KZePXQ6ap%ejD%ZJ8lpDtm%Kp<}dGG zEay={{&M^|{uS<hr$r(o!ek@hqU;ckwg3)a@P=! WKgXXrz#MQY2k7-6ZX5x_5%>fAOq_%O literal 0 HcmV?d00001 diff --git a/lib/web/public/images/frankfurter-icon.png b/lib/web/public/images/frankfurter-icon.png deleted file mode 100644 index 73378ef213cb617e885bf3f8b4a5716d97abf4e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10392 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 delta 25841 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 H9{PU(^{Cvc diff --git a/lib/web/public/stylesheets/application.css b/lib/web/public/stylesheets/application.css index e7688b8..1217130 100644 --- a/lib/web/public/stylesheets/application.css +++ b/lib/web/public/stylesheets/application.css @@ -2,25 +2,44 @@ body { font-family: 'Open Sans', sans-serif; } -.navbar .navbar-nav .nav-link { +strong { + background-color: #f8f9fa; +} + +.navbar { + background-color: #337cf6; font-size: .875rem; + padding-bottom: 0; + padding-top: 0; +} + +.navbar .nav-link { + color: #fff; +} + +.navbar .nav-link:hover { + color: #f8f9fa; } .container { - max-width: 720px; + max-width: 600px; } .section { padding-top: 1em; } +.hero { + margin: 2em 0; +} + .hero .container { text-align: center; } .hero .logo { padding-bottom: 1em; - width: 120px; + width: 100px; } .footer .container { @@ -30,12 +49,10 @@ body { .hljs { background-color: #f8f9fa; - border: 2px solid #f8f9fa; padding: 1em; } .hljs:hover { - border-bottom-color: #dee2e6; cursor: pointer; } @@ -44,41 +61,49 @@ body { font-style: normal; } -#carbonads { - display: none; -} - -/* @media screen and (max-width: 767px) { + .hero .logo { + width: 75px; + } + #carbonads { display: none; } } + @media screen and (min-width: 768px) { #carbonads { - background-color: #f0f0f0; + background-color: #f8f9fa; position: absolute; right: 24px; - top: 36px; + top: 24px; width: 150px; } + + .carbon-text { + color: #495057; + } + .carbon-img, .carbon-poweredby, .carbon-text { display: block; margin: 10px; } + .carbon-text, .carbon-poweredby { line-height: 1.3; text-decoration: none; } + .carbon-text { font-size: 13px; } + .carbon-poweredby { + color: #6c757d; font-size: 11px; - color: #999; } } -*/ + diff --git a/lib/web/server.rb b/lib/web/server.rb index 2259106..ac8e6ac 100644 --- a/lib/web/server.rb +++ b/lib/web/server.rb @@ -15,6 +15,10 @@ use Rack::Cors do end configure :development do + require 'rack-livereload' + + use Rack::LiveReload + set :show_exceptions, :after_handler end @@ -27,13 +31,30 @@ configure :test do end helpers do - def quote - @quote ||= begin - query = Query.new(params) - Quote.new(query.to_h) + def end_of_day_quote + @end_of_day_quote ||= begin + quote = Quote::EndOfDay.new(query) + quote.perform + halt 404 if quote.not_found? + + quote end end + def interval_quote + @interval_quote ||= begin + quote = Quote::Interval.new(query) + quote.perform + halt 404 if quote.not_found? + + quote + end + end + + def query + Query.new(params).to_h + end + def json(data) json = Oj.dump(data, mode: :compat) callback = params.delete('callback') @@ -52,28 +73,25 @@ options '*' do 200 end -get '*' do - cache_control :public - pass -end - get '/' do erb :index end get '/(?:latest|current)', mustermann_opts: { type: :regexp } do - last_modified quote.date - json quote.to_h + params[:date] = Date.today.to_s + etag end_of_day_quote.cache_key + json end_of_day_quote.formatted end get '/(?\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do - last_modified quote.date - json quote.to_h + etag end_of_day_quote.cache_key + json end_of_day_quote.formatted end -get '/(?\d{4}-\d{2}-\d{2})\.\.(?\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do - last_modified quote.end_date - json quote.to_h +get '/(?\d{4}-\d{2}-\d{2})\.\.(?\d{4}-\d{2}-\d{2})', + mustermann_opts: { type: :regexp } do + etag interval_quote.cache_key + json interval_quote.formatted end not_found do diff --git a/lib/web/views/index.erb b/lib/web/views/index.erb index e47a894..6509c4d 100644 --- a/lib/web/views/index.erb +++ b/lib/web/views/index.erb @@ -7,43 +7,43 @@ Frankfurter - + - +
+

Frankfurter

Foreign exchange rates and currency conversion API

-

- Frankfurter is a lightweight API for querying current and historical forex rates published by the European Central Bank. + Frankfurter is an open-source API for current and historical forex rates published by the European Central Bank.

- Rates update around 4PM CET on every working day. Historical data goes back to early 1999. + The API updates rates around 4PM CET every working day. Historical data goes back to early 1999.

@@ -58,7 +58,11 @@

Get historical rates for any day since 1999.

-
GET /1999-12-31 HTTP/1.1
+
GET /2000-01-01 HTTP/1.1
+

+ Get historical exchange rates for a given time period. +

+
GET /2000-01-01..2000-12-31 HTTP/1.1

Rates are quoted against the Euro by default. Quote against a different currency by setting the from parameter in your request. @@ -69,9 +73,9 @@

GET /current?to=USD,GBP HTTP/1.1

- Convert a specific value. + Convert a specific value using amount.

-
GET /current?amount=1000&from=GBP&to=EUR HTTP/1.1
+
GET /current?amount=1000 HTTP/1.1
@@ -110,9 +114,9 @@ fetch('/current?from=GBP&to=USD')
- - - + + + diff --git a/spec/currency_spec.rb b/spec/currency_spec.rb index aa18dd8..6c5a3c9 100644 --- a/spec/currency_spec.rb +++ b/spec/currency_spec.rb @@ -4,37 +4,52 @@ require_relative 'helper' require 'currency' describe Currency do - around do |test| - Currency.db.transaction do - test.call - raise Sequel::Rollback + describe '.current' do + it 'returns current rates' do + date = Currency.order(Sequel.desc(:date)).first.date + rates = Currency.latest + rates.first.date.must_equal date + end + + it 'returns latest rates before given date' do + date = Date.parse('2010-01-01') + rates = Currency.latest(date) + rates.first.date.must_be :<=, date + end + + it 'returns nothing if there are no rates before given date' do + rates = Currency.latest(Date.parse('1998-01-01')) + rates.must_be_empty end end - before do - Currency.dataset.delete - @earlier = [ - Currency.create(iso_code: 'EUR', rate: 1, date: '2014-01-01'), - Currency.create(iso_code: 'USD', rate: 2, date: '2014-01-01') - ] - @later = [ - Currency.create(iso_code: 'EUR', rate: 1, date: '2015-01-01'), - Currency.create(iso_code: 'USD', rate: 2, date: '2015-01-01') - ] - end + describe '.between' do + it 'returns rates between given dates' do + start_date = Date.parse('2010-01-01') + end_date = Date.parse('2010-01-31') + dates = Currency.between((start_date..end_date)).map(:date).uniq.sort + dates.first.must_be :>=, start_date + dates.last.must_be :<=, end_date + end - it 'returns latest rates' do - data = Currency.latest.to_a - data.sample.date.must_equal @later.sample.date - end + it 'returns nothing if there are no rates between given dates' do + date_interval = (Date.parse('1998-01-01')..Date.parse('1998-01-31')) + Currency.between(date_interval).must_be_empty + end - it 'returns latest rates before given date' do - data = Currency.latest(@later.sample.date - 1).to_a - data.sample.date.must_equal @earlier.sample.date - end + it 'returns all rates up to 90 days' do + date_interval = (Date.parse('2010-01-01')..Date.parse('2010-03-01')) + Currency.between(date_interval).map(:date).uniq.count.must_be :>, 30 + end - it 'returns nothing if there are no rates before given date' do - data = Currency.latest(@earlier.sample.date - 1).to_a - data.must_be_empty + it 'samples weeks over 90 days and below 366 days' do + date_interval = (Date.parse('2010-01-01')..Date.parse('2010-12-31')) + Currency.between(date_interval).map(:date).uniq.count.must_be :<=, 52 + end + + it 'samples months over 365 days' do + date_interval = (Date.parse('2001-01-01')..Date.parse('2010-12-31')) + Currency.between(date_interval).map(:date).uniq.count.must_be :<=, 120 + end end end diff --git a/spec/edge_cases_spec.rb b/spec/edge_cases_spec.rb index dd11ec0..42ccb92 100644 --- a/spec/edge_cases_spec.rb +++ b/spec/edge_cases_spec.rb @@ -25,7 +25,7 @@ describe 'the API' do it 'will not process a date before 2000' do get '/1999-01-01' - last_response.must_be :unprocessable? + last_response.must_be :not_found? end it 'will not process an invalid base' do diff --git a/spec/helper.rb b/spec/helper.rb index f44e6ad..10aa3f2 100644 --- a/spec/helper.rb +++ b/spec/helper.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +ENV['APP_ENV'] ||= 'test' + require_relative '../config/environment' require 'minitest/autorun' diff --git a/spec/query_spec.rb b/spec/query_spec.rb index 4e000fa..9d89c30 100644 --- a/spec/query_spec.rb +++ b/spec/query_spec.rb @@ -9,7 +9,7 @@ describe Query do query.amount.must_equal 100.0 end - it 'defaults amount to nothin' do + it 'defaults amount to nothing' do query = Query.new query.amount.must_be_nil end @@ -24,7 +24,7 @@ describe Query do query.base.must_be_nil end - it 'aliases base as from' do + it 'aliases base with from' do query = Query.new(from: 'USD') query.base.must_equal 'USD' end @@ -34,7 +34,7 @@ describe Query do query.symbols.must_equal %w[USD GBP] end - it 'aliases symbols to to' do + it 'aliases symbols with to' do query = Query.new(to: 'USD') query.symbols.must_equal ['USD'] end @@ -45,12 +45,15 @@ describe Query do end it 'returns given date' do - query = Query.new(date: '2014-01-01') - query.date.must_equal '2014-01-01' + date = '2014-01-01' + query = Query.new(date: date) + query.date.must_equal Date.parse(date) end - it 'defaults date to nothing' do - query = Query.new - query.date.must_be_nil + it 'returns given date interval' do + start_date = '2014-01-01' + end_date = '2014-12-31' + query = Query.new(start_date: start_date, end_date: end_date) + query.date.must_equal((Date.parse(start_date)..Date.parse(end_date))) end end diff --git a/spec/quote/base_spec.rb b/spec/quote/base_spec.rb new file mode 100644 index 0000000..249d7a0 --- /dev/null +++ b/spec/quote/base_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require_relative '../helper' +require 'quote/base' + +module Quote + describe Base do + let(:klass) do + Class.new(Base) + end + + let(:quote) do + klass.new(date: Date.today) + end + + it 'requires data' do + -> { quote.perform }.must_raise NotImplementedError + end + + it 'does not know how to format result' do + -> { quote.formatted }.must_raise NotImplementedError + end + + it 'does not know how to generate a cache key' do + -> { quote.cache_key }.must_raise NotImplementedError + end + + it 'defaults base to Euro' do + quote.base.must_equal 'EUR' + end + + it 'defaults amount to 1' do + quote.amount.must_equal 1 + end + + describe 'when given data' do + let(:klass) do + Class.new(Base) do + def fetch_data + [] + end + end + end + + it 'performs' do + assert quote.perform + end + + it 'performs only once' do + quote.perform + refute quote.perform + end + end + end +end diff --git a/spec/quote/end_of_day_spec.rb b/spec/quote/end_of_day_spec.rb new file mode 100644 index 0000000..2751523 --- /dev/null +++ b/spec/quote/end_of_day_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require_relative '../helper' +require 'quote/end_of_day' + +module Quote + describe EndOfDay do + let(:date) do + Date.parse('2010-10-10') + end + + let(:quote) do + EndOfDay.new(date: date) + end + + before do + quote.perform + end + + it 'returns rates' do + quote.formatted[:rates].wont_be :empty? + end + + it 'quotes given date' do + Date.parse(quote.formatted[:date]).must_be :<=, date + end + + it 'quotes against the Euro' do + quote.formatted[:rates].keys.wont_include 'EUR' + end + + it 'sorts rates' do + rates = quote.formatted[:rates] + rates.keys.must_equal rates.keys.sort + end + + it 'has a cache key' do + quote.cache_key.wont_be :empty? + end + + describe 'given a new base' do + let(:quote) do + EndOfDay.new(date: date, base: 'USD') + end + + it 'quotes against that base' do + quote.formatted[:rates].keys.wont_include 'USD' + end + + it 'sorts rates' do + rates = quote.formatted[:rates] + rates.keys.must_equal rates.keys.sort + end + end + + describe 'given symbols' do + let(:quote) do + EndOfDay.new(date: date, symbols: %w[USD GBP JPY]) + end + + it 'quotes only for those symbols' do + rates = quote.formatted[:rates] + rates.keys.must_include 'USD' + rates.keys.wont_include 'CAD' + end + + it 'sorts rates' do + rates = quote.formatted[:rates] + rates.keys.must_equal rates.keys.sort + end + end + + describe 'when given an amount' do + let(:quote) do + EndOfDay.new(date: date, amount: 100) + end + + it 'calculates quotes for that amount' do + quote.formatted[:rates]['USD'].must_be :>, 10 + end + end + end +end diff --git a/spec/quote/interval_spec.rb b/spec/quote/interval_spec.rb new file mode 100644 index 0000000..f1570ea --- /dev/null +++ b/spec/quote/interval_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +require_relative '../helper' +require 'quote/interval' + +module Quote + describe Interval do + let(:date_interval) do + (Date.parse('2010-01-01')..Date.parse('2010-12-31')) + end + + let(:quote) do + Interval.new(date: date_interval) + end + + before do + quote.perform + end + + it 'returns rates' do + quote.formatted[:rates].wont_be :empty? + end + + it 'quotes given date interval' do + Date.parse(quote.formatted[:start_date]).must_be :>=, date_interval.first + Date.parse(quote.formatted[:end_date]).must_be :<=, date_interval.last + end + + it 'quotes against the Euro' do + quote.formatted[:rates].each_value do |rates| + rates.keys.wont_include 'EUR' + end + end + + it 'sorts rates' do + quote.formatted[:rates].each_value do |rates| + rates.keys.must_equal rates.keys.sort + end + end + + it 'has a cache key' do + quote.cache_key.wont_be :empty? + end + + describe 'given a new base' do + let(:quote) do + Interval.new(date: date_interval, base: 'USD') + end + + it 'quotes against that base' do + quote.formatted[:rates].each_value do |rates| + rates.keys.wont_include 'USD' + end + end + + it 'sorts rates' do + quote.formatted[:rates].each_value do |rates| + rates.keys.must_equal rates.keys.sort + end + end + end + + describe 'given symbols' do + let(:quote) do + Interval.new(date: date_interval, symbols: %w[USD GBP JPY]) + end + + it 'quotes only for those symbols' do + quote.formatted[:rates].each_value do |rates| + rates.keys.must_include 'USD' + rates.keys.wont_include 'CAD' + end + end + + it 'sorts rates' do + quote.formatted[:rates].each_value do |rates| + rates.keys.must_equal rates.keys.sort + end + end + end + + describe 'when given an amount' do + let(:quote) do + Interval.new(date: date_interval, amount: 100) + end + + it 'calculates quotes for that amount' do + quote.formatted[:rates].each_value do |rates| + rates['USD'].must_be :>, 10 + end + end + end + end +end diff --git a/spec/quote_spec.rb b/spec/quote_spec.rb deleted file mode 100644 index 38a24fe..0000000 --- a/spec/quote_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true - -require_relative 'helper' -require 'quote' - -describe Quote do - describe 'by default' do - it 'quotes against the Euro' do - quote = Quote.new - rates = quote.to_h[:rates] - rates.keys.wont_include 'EUR' - end - end - - describe 'when given a base' do - it 'quotes against that base' do - quote = Quote.new(base: 'USD') - rates = quote.to_h[:rates] - rates.keys.wont_include 'USD' - end - - it 'sorts rates' do - quote = Quote.new(base: 'USD') - rates = quote.to_h[:rates] - rates.keys.must_equal rates.keys.sort - end - end - - describe 'when given symbols' do - it 'quotes rates only for given symbols' do - quote = Quote.new(symbols: ['USD']) - rates = quote.to_h[:rates] - rates.keys.must_include 'USD' - rates.keys.wont_include 'GBP' - end - end - - describe 'when given an amount' do - it 'quotes for that amount' do - quote = Quote.new(amount: 100) - rates = quote.to_h[:rates] - rates['USD'].must_be :>, 10 - end - end - - describe 'when given an amount and symbols' do - it 'quotes for that amount' do - quote = Quote.new(amount: 100, symbols: ['USD']) - rates = quote.to_h[:rates] - rates['USD'].must_be :>, 10 - end - end -end diff --git a/spec/roundable_spec.rb b/spec/roundable_spec.rb new file mode 100644 index 0000000..260d44f --- /dev/null +++ b/spec/roundable_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require_relative 'helper' +require 'roundable' + +describe Roundable do + include Roundable + + it 'rounds values over 5,000 to zero decimal places' do + round(5000.123456).must_equal 5000.0 + end + + it 'rounds values over 80 and below 5,000 to two decimal places' do + round(80.123456).must_equal 80.12 + round(4999.123456).must_equal 4999.12 + end + + it 'rounds values over 20 and below 80 to three decimal places' do + round(79.123456).must_equal 79.123 + round(20.123456).must_equal 20.123 + end + + it 'rounds values over 1 and below 20 to four decimal places' do + round(19.123456).must_equal 19.1235 + round(1.123456).must_equal 1.1235 + end + + it 'rounds values below 1 to five decimal places' do + round(0.123456).must_equal 0.12346 + end +end diff --git a/spec/web/server_spec.rb b/spec/web/server_spec.rb index 96ec370..16f910a 100644 --- a/spec/web/server_spec.rb +++ b/spec/web/server_spec.rb @@ -49,17 +49,10 @@ describe 'the server' do json['rates'].wont_be :empty? end - it 'returns a cache control header' do - %w[/ /current /2012-11-20].each do |path| - get path - headers['Cache-Control'].wont_be_nil - end - end - - it 'returns a last modified header' do + it 'returns an ETag' do %w[/current /2012-11-20].each do |path| get path - headers['Last-Modified'].wont_be_nil + headers['ETag'].wont_be_nil end end @@ -88,8 +81,8 @@ describe 'the server' do it 'returns rates for a given period' do get '/2010-01-01..2010-12-31' - json['start_date'].must_equal '2010-01-01' - json['end_date'].must_equal '2010-12-31' - json['rates'].wont_be empty + json['start_date'].wont_be :empty? + json['end_date'].wont_be :empty? + json['rates'].wont_be :empty? end end