mirror of
https://github.com/hakanensari/frankfurter.git
synced 2024-11-21 18:42:29 +01:00
Spring cleaning
- bumped gems - rm bots - rm pry byebug - added rubocop-shopify and corrected generated warnings
This commit is contained in:
parent
243269a322
commit
e5815737c1
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@ -1,11 +0,0 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "bundler" # See documentation for possible values
|
|
||||||
directory: "/" # Location of package manifests
|
|
||||||
schedule:
|
|
||||||
interval: "weekly"
|
|
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
@ -1,17 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 60
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 7
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- pinned
|
|
||||||
- security
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: wontfix
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has been automatically marked as stale because it has not had
|
|
||||||
recent activity. It will be closed if no further activity occurs. Thank you
|
|
||||||
for your contributions.
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
@ -1,3 +1,6 @@
|
|||||||
|
inherit_gem:
|
||||||
|
rubocop-shopify: rubocop.yml
|
||||||
|
|
||||||
inherit_from: ".rubocop_todo.yml"
|
inherit_from: ".rubocop_todo.yml"
|
||||||
|
|
||||||
require:
|
require:
|
||||||
|
48
Gemfile
48
Gemfile
@ -1,34 +1,34 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
source 'https://rubygems.org'
|
source "https://rubygems.org"
|
||||||
|
|
||||||
ruby File.read('.ruby-version').chomp
|
ruby File.read(".ruby-version").chomp
|
||||||
|
|
||||||
gem 'money'
|
gem "money"
|
||||||
gem 'oj'
|
gem "oj"
|
||||||
gem 'ox'
|
gem "ox"
|
||||||
gem 'rack-contrib'
|
gem "rack-contrib"
|
||||||
gem 'rack-cors'
|
gem "rack-cors"
|
||||||
gem 'rake'
|
gem "rake"
|
||||||
gem 'roda'
|
gem "roda"
|
||||||
gem 'rufus-scheduler'
|
gem "rufus-scheduler"
|
||||||
gem 'sequel_pg'
|
gem "sequel_pg"
|
||||||
gem 'unicorn'
|
gem "unicorn"
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'pry-byebug'
|
gem "rubocop-minitest"
|
||||||
gem 'rubocop-minitest'
|
gem "rubocop-performance"
|
||||||
gem 'rubocop-performance'
|
gem "rubocop-rake"
|
||||||
gem 'rubocop-rake'
|
gem "rubocop-sequel"
|
||||||
gem 'rubocop-sequel'
|
gem "rubocop-shopify"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'minitest'
|
gem "minitest"
|
||||||
gem 'minitest-around'
|
gem "minitest-around"
|
||||||
gem 'minitest-focus'
|
gem "minitest-focus"
|
||||||
gem 'rack-test'
|
gem "rack-test"
|
||||||
gem 'simplecov'
|
gem "simplecov"
|
||||||
gem 'vcr'
|
gem "vcr"
|
||||||
gem 'webmock'
|
gem "webmock"
|
||||||
end
|
end
|
||||||
|
21
Gemfile.lock
21
Gemfile.lock
@ -6,8 +6,6 @@ GEM
|
|||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bigdecimal (3.1.8)
|
bigdecimal (3.1.8)
|
||||||
byebug (11.1.3)
|
|
||||||
coderay (1.1.3)
|
|
||||||
concurrent-ruby (1.3.4)
|
concurrent-ruby (1.3.4)
|
||||||
crack (1.0.0)
|
crack (1.0.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
@ -18,13 +16,12 @@ GEM
|
|||||||
fugit (1.11.1)
|
fugit (1.11.1)
|
||||||
et-orbi (~> 1, >= 1.2.11)
|
et-orbi (~> 1, >= 1.2.11)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
hashdiff (1.1.1)
|
hashdiff (1.1.2)
|
||||||
i18n (1.14.6)
|
i18n (1.14.6)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
json (2.8.2)
|
json (2.8.2)
|
||||||
kgio (2.11.4)
|
kgio (2.11.4)
|
||||||
language_server-protocol (3.17.0.3)
|
language_server-protocol (3.17.0.3)
|
||||||
method_source (1.1.0)
|
|
||||||
minitest (5.25.1)
|
minitest (5.25.1)
|
||||||
minitest-around (0.5.0)
|
minitest-around (0.5.0)
|
||||||
minitest (~> 5.0)
|
minitest (~> 5.0)
|
||||||
@ -35,19 +32,13 @@ GEM
|
|||||||
oj (3.16.7)
|
oj (3.16.7)
|
||||||
bigdecimal (>= 3.0)
|
bigdecimal (>= 3.0)
|
||||||
ostruct (>= 0.2)
|
ostruct (>= 0.2)
|
||||||
ostruct (0.6.0)
|
ostruct (0.6.1)
|
||||||
ox (2.14.18)
|
ox (2.14.18)
|
||||||
parallel (1.26.3)
|
parallel (1.26.3)
|
||||||
parser (3.3.6.0)
|
parser (3.3.6.0)
|
||||||
ast (~> 2.4.1)
|
ast (~> 2.4.1)
|
||||||
racc
|
racc
|
||||||
pg (1.5.8)
|
pg (1.5.9)
|
||||||
pry (0.14.2)
|
|
||||||
coderay (~> 1.1)
|
|
||||||
method_source (~> 1.0)
|
|
||||||
pry-byebug (3.10.1)
|
|
||||||
byebug (~> 11.0)
|
|
||||||
pry (>= 0.13, < 0.15)
|
|
||||||
public_suffix (6.0.1)
|
public_suffix (6.0.1)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
@ -87,10 +78,12 @@ GEM
|
|||||||
rubocop (~> 1.0)
|
rubocop (~> 1.0)
|
||||||
rubocop-sequel (0.3.7)
|
rubocop-sequel (0.3.7)
|
||||||
rubocop (~> 1.0)
|
rubocop (~> 1.0)
|
||||||
|
rubocop-shopify (2.15.1)
|
||||||
|
rubocop (~> 1.51)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
rufus-scheduler (3.9.2)
|
rufus-scheduler (3.9.2)
|
||||||
fugit (~> 1.1, >= 1.11.1)
|
fugit (~> 1.1, >= 1.11.1)
|
||||||
sequel (5.85.0)
|
sequel (5.86.0)
|
||||||
bigdecimal
|
bigdecimal
|
||||||
sequel_pg (1.17.1)
|
sequel_pg (1.17.1)
|
||||||
pg (>= 0.18.0, != 1.2.0)
|
pg (>= 0.18.0, != 1.2.0)
|
||||||
@ -124,7 +117,6 @@ DEPENDENCIES
|
|||||||
money
|
money
|
||||||
oj
|
oj
|
||||||
ox
|
ox
|
||||||
pry-byebug
|
|
||||||
rack-contrib
|
rack-contrib
|
||||||
rack-cors
|
rack-cors
|
||||||
rack-test
|
rack-test
|
||||||
@ -134,6 +126,7 @@ DEPENDENCIES
|
|||||||
rubocop-performance
|
rubocop-performance
|
||||||
rubocop-rake
|
rubocop-rake
|
||||||
rubocop-sequel
|
rubocop-sequel
|
||||||
|
rubocop-shopify
|
||||||
rufus-scheduler
|
rufus-scheduler
|
||||||
sequel_pg
|
sequel_pg
|
||||||
simplecov
|
simplecov
|
||||||
|
2
Rakefile
2
Rakefile
@ -1,3 +1,3 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
Dir.glob('lib/tasks/*.rake').each { |r| import r }
|
Dir.glob("lib/tasks/*.rake").each { |r| import r }
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rufus-scheduler'
|
require "rufus-scheduler"
|
||||||
|
|
||||||
scheduler = Rufus::Scheduler.new
|
scheduler = Rufus::Scheduler.new
|
||||||
|
|
||||||
scheduler.cron '*/30 15,16,17 * * 1-5' do
|
scheduler.cron("*/30 15,16,17 * * 1-5") do
|
||||||
`rake rates:current`
|
%x(rake rates:current)
|
||||||
end
|
end
|
||||||
|
|
||||||
scheduler.join
|
scheduler.join
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require './config/environment'
|
require "./config/environment"
|
||||||
require 'web/server'
|
require "web/server"
|
||||||
|
|
||||||
run Web::Server.freeze.app
|
run Web::Server.freeze.app
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'pathname'
|
require "pathname"
|
||||||
|
|
||||||
# Encapsulates app configuration
|
# Encapsulates app configuration
|
||||||
module App
|
module App
|
||||||
class << self
|
class << self
|
||||||
def env
|
def env
|
||||||
ENV['APP_ENV'] || 'development'
|
ENV["APP_ENV"] || "development"
|
||||||
end
|
end
|
||||||
|
|
||||||
def root
|
def root
|
||||||
Pathname.new(File.expand_path('..', __dir__))
|
Pathname.new(File.expand_path("..", __dir__))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'app'
|
require_relative "app"
|
||||||
$LOAD_PATH << App.root.join('lib')
|
$LOAD_PATH << App.root.join("lib")
|
||||||
Dir[App.root.join('config/initializers/*.rb')].each { |f| require f }
|
Dir[App.root.join("config/initializers/*.rb")].each { |f| require f }
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'pg'
|
require "pg"
|
||||||
require 'sequel'
|
require "sequel"
|
||||||
|
|
||||||
Sequel.extension :pg_json_ops
|
Sequel.extension(:pg_json_ops)
|
||||||
Sequel.single_threaded = true
|
Sequel.single_threaded = true
|
||||||
Sequel.connect(ENV['DATABASE_URL'] ||
|
Sequel.connect(ENV["DATABASE_URL"] ||
|
||||||
"postgres://localhost:#{ENV.fetch('PGPORT', nil)}/frankfurter_#{App.env}")
|
"postgres://localhost:#{ENV.fetch("PGPORT", nil)}/frankfurter_#{App.env}")
|
||||||
.extension :pg_json
|
.extension(:pg_json)
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
`rake db:prepare`
|
%x(rake db:prepare)
|
||||||
|
|
||||||
worker_process_count = (ENV['WORKER_PROCESSES'] || 4).to_i
|
worker_process_count = (ENV["WORKER_PROCESSES"] || 4).to_i
|
||||||
|
|
||||||
preload_app true
|
preload_app true
|
||||||
worker_processes worker_process_count
|
worker_processes worker_process_count
|
||||||
@ -12,7 +12,7 @@ initialized = false
|
|||||||
before_fork do |_server, _worker|
|
before_fork do |_server, _worker|
|
||||||
Sequel::DATABASES.each(&:disconnect)
|
Sequel::DATABASES.each(&:disconnect)
|
||||||
unless initialized
|
unless initialized
|
||||||
require 'scheduler/daemon'
|
require "scheduler/daemon"
|
||||||
Scheduler::Daemon.start
|
Scheduler::Daemon.start
|
||||||
initialized = true
|
initialized = true
|
||||||
end
|
end
|
||||||
|
@ -7,7 +7,7 @@ Sequel.migration do
|
|||||||
String :iso_code
|
String :iso_code
|
||||||
Float :rate
|
Float :rate
|
||||||
|
|
||||||
index %i[date iso_code], unique: true
|
index [:date, :iso_code], unique: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ Sequel.migration do
|
|||||||
date :date
|
date :date
|
||||||
string :iso_code
|
string :iso_code
|
||||||
float :rate
|
float :rate
|
||||||
index %i[date iso_code], unique: true
|
index [:date, :iso_code], unique: true
|
||||||
end
|
end
|
||||||
drop_table :days
|
drop_table :days
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'day'
|
require "day"
|
||||||
require 'bank/feed'
|
require "bank/feed"
|
||||||
|
|
||||||
module Bank
|
module Bank
|
||||||
class << self
|
class << self
|
||||||
|
@ -1,36 +1,38 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'net/http'
|
require "net/http"
|
||||||
require 'ox'
|
require "ox"
|
||||||
|
|
||||||
module Bank
|
module Bank
|
||||||
class Feed
|
class Feed
|
||||||
include Enumerable
|
include Enumerable
|
||||||
|
|
||||||
def self.current
|
class << self
|
||||||
url = URI('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml')
|
def current
|
||||||
xml = Net::HTTP.get(url)
|
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
|
||||||
|
xml = Net::HTTP.get(url)
|
||||||
|
|
||||||
new(xml)
|
new(xml)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ninety_days
|
def ninety_days
|
||||||
url = URI('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml')
|
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml")
|
||||||
xml = Net::HTTP.get(url)
|
xml = Net::HTTP.get(url)
|
||||||
|
|
||||||
new(xml)
|
new(xml)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.historical
|
def historical
|
||||||
url = URI('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml')
|
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml")
|
||||||
xml = Net::HTTP.get(url)
|
xml = Net::HTTP.get(url)
|
||||||
|
|
||||||
new(xml)
|
new(xml)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.saved_data
|
def saved_data
|
||||||
xml = File.read(File.join(__dir__, 'eurofxref-hist.xml'))
|
xml = File.read(File.join(__dir__, "eurofxref-hist.xml"))
|
||||||
new(xml)
|
new(xml)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(xml)
|
def initialize(xml)
|
||||||
@ -38,8 +40,8 @@ module Bank
|
|||||||
end
|
end
|
||||||
|
|
||||||
def each
|
def each
|
||||||
@document.locate('gesmes:Envelope/Cube/Cube').each do |day|
|
@document.locate("gesmes:Envelope/Cube/Cube").each do |day|
|
||||||
yield(date: Date.parse(day['time']),
|
yield(date: Date.parse(day["time"]),
|
||||||
rates: day.nodes.each_with_object({}) do |currency, rates|
|
rates: day.nodes.each_with_object({}) do |currency, rates|
|
||||||
rates[currency[:currency]] = Float(currency[:rate])
|
rates[currency[:currency]] = Float(currency[:rate])
|
||||||
end)
|
end)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'day'
|
require "day"
|
||||||
require 'forwardable'
|
require "forwardable"
|
||||||
|
|
||||||
class Currency < Sequel::Model(Day.currencies)
|
class Currency < Sequel::Model(Day.currencies)
|
||||||
class << self
|
class << self
|
||||||
@ -17,7 +17,7 @@ class Currency < Sequel::Model(Day.currencies)
|
|||||||
|
|
||||||
def between(interval)
|
def between(interval)
|
||||||
case interval.last - interval.first
|
case interval.last - interval.first
|
||||||
when 366.. then super.sample('week')
|
when 366.. then super.sample("week")
|
||||||
else super
|
else super
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'currency'
|
require "currency"
|
||||||
require 'money/currency'
|
require "money/currency"
|
||||||
|
|
||||||
class CurrencyNames
|
class CurrencyNames
|
||||||
def cache_key
|
def cache_key
|
||||||
@ -17,7 +17,7 @@ class CurrencyNames
|
|||||||
private
|
private
|
||||||
|
|
||||||
def iso_codes
|
def iso_codes
|
||||||
currencies.map(&:iso_code).append('EUR').sort
|
currencies.map(&:iso_code).append("EUR").sort
|
||||||
end
|
end
|
||||||
|
|
||||||
def currencies
|
def currencies
|
||||||
|
10
lib/day.rb
10
lib/day.rb
@ -3,7 +3,7 @@
|
|||||||
class Day < Sequel::Model
|
class Day < Sequel::Model
|
||||||
dataset_module do
|
dataset_module do
|
||||||
def latest(date = Date.today)
|
def latest(date = Date.today)
|
||||||
where(date: select(:date).where(Sequel.lit('date <= ?', date))
|
where(date: select(:date).where(Sequel.lit("date <= ?", date))
|
||||||
.order(Sequel.desc(:date))
|
.order(Sequel.desc(:date))
|
||||||
.limit(1))
|
.limit(1))
|
||||||
end
|
end
|
||||||
@ -13,9 +13,11 @@ class Day < Sequel::Model
|
|||||||
end
|
end
|
||||||
|
|
||||||
def currencies
|
def currencies
|
||||||
select(:date,
|
select(
|
||||||
Sequel.lit('rates.key').as(:iso_code),
|
:date,
|
||||||
Sequel.lit('rates.value::text::float').as(:rate))
|
Sequel.lit("rates.key").as(:iso_code),
|
||||||
|
Sequel.lit("rates.value::text::float").as(:rate),
|
||||||
|
)
|
||||||
.join(Sequel.function(:jsonb_each, :rates).lateral.as(:rates), true)
|
.join(Sequel.function(:jsonb_each, :rates).lateral.as(:rates), true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Query
|
class Query
|
||||||
def self.build(params)
|
class << self
|
||||||
new(params).to_h
|
def build(params)
|
||||||
|
new(params).to_h
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize(params = {})
|
def initialize(params = {})
|
||||||
@ -20,7 +22,7 @@ class Query
|
|||||||
end
|
end
|
||||||
|
|
||||||
def symbols
|
def symbols
|
||||||
@params.values_at(:to, :symbols).compact.first&.upcase&.split(',')
|
@params.values_at(:to, :symbols).compact.first&.upcase&.split(",")
|
||||||
end
|
end
|
||||||
|
|
||||||
def date
|
def date
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'quote/end_of_day'
|
require "quote/end_of_day"
|
||||||
require 'quote/interval'
|
require "quote/interval"
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'roundable'
|
require "roundable"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
class Base
|
class Base
|
||||||
include Roundable
|
include Roundable
|
||||||
|
|
||||||
DEFAULT_BASE = 'EUR'
|
DEFAULT_BASE = "EUR"
|
||||||
|
|
||||||
attr_reader :amount, :base, :date, :symbols, :result
|
attr_reader :amount, :base, :date, :symbols, :result
|
||||||
|
|
||||||
def initialize(date:, amount: 1.0, base: 'EUR', symbols: nil)
|
def initialize(date:, amount: 1.0, base: "EUR", symbols: nil)
|
||||||
@date = date
|
@date = date
|
||||||
@amount = amount
|
@amount = amount
|
||||||
@base = base
|
@base = base
|
||||||
@ -29,7 +29,7 @@ module Quote
|
|||||||
end
|
end
|
||||||
|
|
||||||
def must_rebase?
|
def must_rebase?
|
||||||
base != 'EUR'
|
base != "EUR"
|
||||||
end
|
end
|
||||||
|
|
||||||
def formatted
|
def formatted
|
||||||
@ -64,16 +64,16 @@ module Quote
|
|||||||
|
|
||||||
def rebase_rates
|
def rebase_rates
|
||||||
result.each do |date, rates|
|
result.each do |date, rates|
|
||||||
rates['EUR'] = amount if symbols.nil? || symbols.include?('EUR')
|
rates["EUR"] = amount if symbols.nil? || symbols.include?("EUR")
|
||||||
divisor = rates.delete(base)
|
divisor = rates.delete(base)
|
||||||
if divisor.nil? || rates.empty?
|
if divisor.nil? || rates.empty?
|
||||||
result.delete(date)
|
result.delete(date)
|
||||||
else
|
else
|
||||||
result[date] = rates.sort
|
result[date] = rates.sort
|
||||||
.map! do |iso_code, rate|
|
.map! do |iso_code, rate|
|
||||||
[iso_code, round(amount * rate / divisor)]
|
[iso_code, round(amount * rate / divisor)]
|
||||||
end
|
end
|
||||||
.to_h
|
.to_h
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'quote/base'
|
require "quote/base"
|
||||||
require 'digest'
|
require "digest"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
class EndOfDay < Base
|
class EndOfDay < Base
|
||||||
def formatted
|
def formatted
|
||||||
{ amount:,
|
{
|
||||||
|
amount:,
|
||||||
base:,
|
base:,
|
||||||
date: result.keys.first,
|
date: result.keys.first,
|
||||||
rates: result.values.first }
|
rates: result.values.first,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_key
|
def cache_key
|
||||||
@ -21,7 +23,7 @@ module Quote
|
|||||||
private
|
private
|
||||||
|
|
||||||
def fetch_data
|
def fetch_data
|
||||||
require 'currency'
|
require "currency"
|
||||||
|
|
||||||
scope = Currency.latest(date)
|
scope = Currency.latest(date)
|
||||||
scope = scope.only(*(symbols + [base])) if symbols
|
scope = scope.only(*(symbols + [base])) if symbols
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'quote/base'
|
require "quote/base"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
class Interval < Base
|
class Interval < Base
|
||||||
def formatted
|
def formatted
|
||||||
{ amount:,
|
{
|
||||||
|
amount:,
|
||||||
base:,
|
base:,
|
||||||
start_date: result.keys.first,
|
start_date: result.keys.first,
|
||||||
end_date: result.keys.last,
|
end_date: result.keys.last,
|
||||||
rates: result }
|
rates: result,
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_key
|
def cache_key
|
||||||
@ -21,7 +23,7 @@ module Quote
|
|||||||
private
|
private
|
||||||
|
|
||||||
def fetch_data
|
def fetch_data
|
||||||
require 'currency'
|
require "currency"
|
||||||
|
|
||||||
scope = Currency.between(date)
|
scope = Currency.between(date)
|
||||||
scope = scope.only(*(symbols + [base])) if symbols
|
scope = scope.only(*(symbols + [base])) if symbols
|
||||||
|
@ -13,18 +13,18 @@ module Roundable
|
|||||||
if value > 5000
|
if value > 5000
|
||||||
value.round
|
value.round
|
||||||
elsif value > 80
|
elsif value > 80
|
||||||
Float(format('%<value>.2f', value:))
|
Float(format("%<value>.2f", value:))
|
||||||
elsif value > 20
|
elsif value > 20
|
||||||
Float(format('%<value>.3f', value:))
|
Float(format("%<value>.3f", value:))
|
||||||
elsif value > 1
|
elsif value > 1
|
||||||
Float(format('%<value>.4f', value:))
|
Float(format("%<value>.4f", value:))
|
||||||
# I had originally opted to round smaller numbers simply to five decimal
|
# I had originally opted to round smaller numbers simply to five decimal
|
||||||
# places but introduced this refinement to handle an edge case where a
|
# places but introduced this refinement to handle an edge case where a
|
||||||
# lower-rate base currency like IDR produces less precise quotes.
|
# lower-rate base currency like IDR produces less precise quotes.
|
||||||
elsif value > 0.0001
|
elsif value > 0.0001
|
||||||
Float(format('%<value>.5f', value:))
|
Float(format("%<value>.5f", value:))
|
||||||
else
|
else
|
||||||
Float(format('%<value>.6f', value:))
|
Float(format("%<value>.6f", value:))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -4,8 +4,10 @@ module Scheduler
|
|||||||
class Daemon
|
class Daemon
|
||||||
attr_reader :pid
|
attr_reader :pid
|
||||||
|
|
||||||
def self.start
|
class << self
|
||||||
new.start
|
def start
|
||||||
|
new.start
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@ -25,7 +27,7 @@ module Scheduler
|
|||||||
private
|
private
|
||||||
|
|
||||||
def run
|
def run
|
||||||
load 'bin/schedule'
|
load("bin/schedule")
|
||||||
end
|
end
|
||||||
|
|
||||||
def monitor_child
|
def monitor_child
|
||||||
@ -33,7 +35,7 @@ module Scheduler
|
|||||||
|
|
||||||
@child_monitor = Thread.new do
|
@child_monitor = Thread.new do
|
||||||
loop do
|
loop do
|
||||||
sleep 5
|
sleep(5)
|
||||||
unless alive?(pid)
|
unless alive?(pid)
|
||||||
@pid = nil
|
@pid = nil
|
||||||
start
|
start
|
||||||
@ -46,7 +48,7 @@ module Scheduler
|
|||||||
Thread.new do
|
Thread.new do
|
||||||
loop do
|
loop do
|
||||||
exit unless alive?(@parent_pid)
|
exit unless alive?(@parent_pid)
|
||||||
sleep 1
|
sleep(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,26 +1,26 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
namespace :db do
|
namespace :db do
|
||||||
desc 'Run database migrations'
|
desc "Run database migrations"
|
||||||
task migrate: :environment do
|
task migrate: :environment do
|
||||||
Sequel.extension(:migration)
|
Sequel.extension(:migration)
|
||||||
db = Sequel::DATABASES.first
|
db = Sequel::DATABASES.first
|
||||||
dir = App.root.join('db/migrate')
|
dir = App.root.join("db/migrate")
|
||||||
opts = {}
|
opts = {}
|
||||||
opts.update(target: ENV['VERSION'].to_i) if ENV['VERSION']
|
opts.update(target: ENV["VERSION"].to_i) if ENV["VERSION"]
|
||||||
|
|
||||||
Sequel::IntegerMigrator.new(db, dir, opts).run
|
Sequel::IntegerMigrator.new(db, dir, opts).run
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Run database migrations and seed data'
|
desc "Run database migrations and seed data"
|
||||||
task prepare: %w[db:migrate rates:all]
|
task prepare: ["db:migrate", "rates:all"]
|
||||||
|
|
||||||
namespace :test do
|
namespace :test do
|
||||||
desc 'Run database migrations and seed with saved data'
|
desc "Run database migrations and seed with saved data"
|
||||||
task :prepare do
|
task :prepare do
|
||||||
ENV['APP_ENV'] ||= 'test'
|
ENV["APP_ENV"] ||= "test"
|
||||||
Rake::Task['db:migrate'].invoke
|
Rake::Task["db:migrate"].invoke
|
||||||
Rake::Task['rates:seed_with_saved_data'].invoke
|
Rake::Task["rates:seed_with_saved_data"].invoke
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
desc 'Load environment'
|
desc "Load environment"
|
||||||
task :environment do
|
task :environment do
|
||||||
require './config/environment'
|
require "./config/environment"
|
||||||
end
|
end
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
namespace :rates do
|
namespace :rates do
|
||||||
desc 'Load all'
|
desc "Load all"
|
||||||
task all: :environment do
|
task all: :environment do
|
||||||
require 'bank'
|
require "bank"
|
||||||
Bank.fetch_all!
|
Bank.fetch_all!
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Load last 90 days'
|
desc "Load last 90 days"
|
||||||
task ninety_days: :environment do
|
task ninety_days: :environment do
|
||||||
require 'bank'
|
require "bank"
|
||||||
Bank.fetch_ninety_days!
|
Bank.fetch_ninety_days!
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Load current'
|
desc "Load current"
|
||||||
task current: :environment do
|
task current: :environment do
|
||||||
require 'bank'
|
require "bank"
|
||||||
Bank.fetch_current!
|
Bank.fetch_current!
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Seed with saved data'
|
desc "Seed with saved data"
|
||||||
task :seed_with_saved_data do
|
task :seed_with_saved_data do
|
||||||
require 'bank'
|
require "bank"
|
||||||
Bank.seed_with_saved_data!
|
Bank.seed_with_saved_data!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
begin
|
begin
|
||||||
require 'sitemap_generator/tasks'
|
require "sitemap_generator/tasks"
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'rake/testtask'
|
require "rake/testtask"
|
||||||
begin
|
begin
|
||||||
require 'rubocop/rake_task'
|
require "rubocop/rake_task"
|
||||||
rescue LoadError
|
rescue LoadError
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Rake::TestTask.new(test: :environment) do |t|
|
Rake::TestTask.new(test: :environment) do |t|
|
||||||
t.libs.push('lib')
|
t.libs.push("lib")
|
||||||
t.test_files = FileList['spec/**/*_spec.rb']
|
t.test_files = FileList["spec/**/*_spec.rb"]
|
||||||
t.ruby_opts += ['-W0']
|
t.ruby_opts += ["-W0"]
|
||||||
end
|
end
|
||||||
|
|
||||||
RuboCop::RakeTask.new
|
RuboCop::RakeTask.new
|
||||||
|
|
||||||
task default: %w[rubocop db:test:prepare test]
|
task default: ["rubocop", "db:test:prepare", "test"]
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'oj'
|
require "oj"
|
||||||
require 'rack/contrib/jsonp'
|
require "rack/contrib/jsonp"
|
||||||
require 'rack/cors'
|
require "rack/cors"
|
||||||
require 'roda'
|
require "roda"
|
||||||
|
|
||||||
require 'currency_names'
|
require "currency_names"
|
||||||
require 'query'
|
require "query"
|
||||||
require 'quote'
|
require "quote"
|
||||||
|
|
||||||
module Web
|
module Web
|
||||||
class Server < Roda
|
class Server < Roda
|
||||||
use Rack::Cors do
|
use Rack::Cors do
|
||||||
allow do
|
allow do
|
||||||
origins '*'
|
origins "*"
|
||||||
resource '*', headers: :any, methods: %i[get options]
|
resource "*", headers: :any, methods: [:get, :options]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
use Rack::JSONP
|
use Rack::JSONP
|
||||||
@ -23,51 +23,52 @@ module Web
|
|||||||
|
|
||||||
plugin :halt
|
plugin :halt
|
||||||
plugin :error_handler do |error|
|
plugin :error_handler do |error|
|
||||||
request.halt 422, { message: error.message }
|
request.halt(422, { message: error.message })
|
||||||
end
|
end
|
||||||
|
|
||||||
plugin :indifferent_params
|
plugin :indifferent_params
|
||||||
|
|
||||||
plugin :json, content_type: 'application/json; charset=utf-8',
|
plugin :json,
|
||||||
serializer: ->(o) { Oj.dump(o, mode: :compat) }
|
content_type: "application/json; charset=utf-8",
|
||||||
|
serializer: ->(o) { Oj.dump(o, mode: :compat) }
|
||||||
|
|
||||||
plugin :params_capturing
|
plugin :params_capturing
|
||||||
|
|
||||||
route do |r|
|
route do |r|
|
||||||
response.cache_control public: true, max_age: 900
|
response.cache_control(public: true, max_age: 900)
|
||||||
|
|
||||||
r.root do
|
r.root do
|
||||||
{ docs: 'https://www.frankfurter.app/docs' }
|
{ docs: "https://www.frankfurter.app/docs" }
|
||||||
end
|
end
|
||||||
|
|
||||||
r.is(/latest|current/) do
|
r.is(/latest|current/) do
|
||||||
r.params['date'] = Date.today.to_s
|
r.params["date"] = Date.today.to_s
|
||||||
quote = quote_end_of_day(r)
|
quote = quote_end_of_day(r)
|
||||||
r.etag quote.cache_key
|
r.etag(quote.cache_key)
|
||||||
|
|
||||||
quote.formatted
|
quote.formatted
|
||||||
end
|
end
|
||||||
|
|
||||||
r.is(/(\d{4}-\d{2}-\d{2})/) do
|
r.is(/(\d{4}-\d{2}-\d{2})/) do
|
||||||
r.params['date'] = r.params['captures'].first
|
r.params["date"] = r.params["captures"].first
|
||||||
quote = quote_end_of_day(r)
|
quote = quote_end_of_day(r)
|
||||||
r.etag quote.cache_key
|
r.etag(quote.cache_key)
|
||||||
|
|
||||||
quote.formatted
|
quote.formatted
|
||||||
end
|
end
|
||||||
|
|
||||||
r.is(/(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})?/) do
|
r.is(/(\d{4}-\d{2}-\d{2})\.\.(\d{4}-\d{2}-\d{2})?/) do
|
||||||
r.params['start_date'] = r.params['captures'].first
|
r.params["start_date"] = r.params["captures"].first
|
||||||
r.params['end_date'] = r.params['captures'][1] || Date.today.to_s
|
r.params["end_date"] = r.params["captures"][1] || Date.today.to_s
|
||||||
quote = quote_interval(r)
|
quote = quote_interval(r)
|
||||||
r.etag quote.cache_key
|
r.etag(quote.cache_key)
|
||||||
|
|
||||||
quote.formatted
|
quote.formatted
|
||||||
end
|
end
|
||||||
|
|
||||||
r.is 'currencies' do
|
r.is("currencies") do
|
||||||
currency_names = CurrencyNames.new
|
currency_names = CurrencyNames.new
|
||||||
r.etag currency_names.cache_key
|
r.etag(currency_names.cache_key)
|
||||||
|
|
||||||
currency_names.formatted
|
currency_names.formatted
|
||||||
end
|
end
|
||||||
@ -79,7 +80,7 @@ module Web
|
|||||||
query = Query.build(request.params)
|
query = Query.build(request.params)
|
||||||
quote = Quote::EndOfDay.new(**query)
|
quote = Quote::EndOfDay.new(**query)
|
||||||
quote.perform
|
quote.perform
|
||||||
request.halt 404, { message: 'not found' } if quote.not_found?
|
request.halt(404, { message: "not found" }) if quote.not_found?
|
||||||
|
|
||||||
quote
|
quote
|
||||||
end
|
end
|
||||||
@ -88,7 +89,7 @@ module Web
|
|||||||
query = Query.build(request.params)
|
query = Query.build(request.params)
|
||||||
quote = Quote::Interval.new(**query)
|
quote = Quote::Interval.new(**query)
|
||||||
quote.perform
|
quote.perform
|
||||||
request.halt 404, { message: 'not found' } if quote.not_found?
|
request.halt(404, { message: "not found" }) if quote.not_found?
|
||||||
|
|
||||||
quote
|
quote
|
||||||
end
|
end
|
||||||
|
@ -1,46 +1,46 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../helper'
|
require_relative "../helper"
|
||||||
require 'bank/feed'
|
require "bank/feed"
|
||||||
|
|
||||||
module Bank
|
module Bank
|
||||||
describe Feed do
|
describe Feed do
|
||||||
before do
|
before do
|
||||||
VCR.insert_cassette 'feed'
|
VCR.insert_cassette("feed")
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
VCR.eject_cassette
|
VCR.eject_cassette
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches current rates' do
|
it "fetches current rates" do
|
||||||
feed = Feed.current
|
feed = Feed.current
|
||||||
_(feed.count).must_be :==, 1
|
_(feed.count).must_be(:==, 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches rates for the past 90 days' do
|
it "fetches rates for the past 90 days" do
|
||||||
feed = Feed.ninety_days
|
feed = Feed.ninety_days
|
||||||
_(feed.count).must_be :>, 1
|
_(feed.count).must_be(:>, 1)
|
||||||
_(feed.count).must_be :<=, 90
|
_(feed.count).must_be(:<=, 90)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches historical rates' do
|
it "fetches historical rates" do
|
||||||
feed = Feed.historical
|
feed = Feed.historical
|
||||||
_(feed.count).must_be :>, 90
|
_(feed.count).must_be(:>, 90)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses dates' do
|
it "parses dates" do
|
||||||
feed = Feed.current
|
feed = Feed.current
|
||||||
day = feed.first
|
day = feed.first
|
||||||
_(day[:date]).must_be_kind_of Date
|
_(day[:date]).must_be_kind_of(Date)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'parses rates' do
|
it "parses rates" do
|
||||||
feed = Feed.current
|
feed = Feed.current
|
||||||
day = feed.first
|
day = feed.first
|
||||||
day[:rates].each do |iso_code, value|
|
day[:rates].each do |iso_code, value|
|
||||||
_(iso_code).must_be_kind_of String
|
_(iso_code).must_be_kind_of(String)
|
||||||
_(value).must_be_kind_of Float
|
_(value).must_be_kind_of(Float)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'bank'
|
require "bank"
|
||||||
|
|
||||||
describe Bank do
|
describe Bank do
|
||||||
around do |test|
|
around do |test|
|
||||||
@ -13,55 +13,55 @@ describe Bank do
|
|||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
VCR.insert_cassette 'feed'
|
VCR.insert_cassette("feed")
|
||||||
end
|
end
|
||||||
|
|
||||||
after do
|
after do
|
||||||
VCR.eject_cassette
|
VCR.eject_cassette
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches all rates' do
|
it "fetches all rates" do
|
||||||
Bank.fetch_all!
|
Bank.fetch_all!
|
||||||
_(Day.count).must_be :>, 90
|
_(Day.count).must_be(:>, 90)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not duplicate when fetching all rates' do
|
it "does not duplicate when fetching all rates" do
|
||||||
Bank.fetch_all!
|
Bank.fetch_all!
|
||||||
count = Day.count
|
count = Day.count
|
||||||
Bank.fetch_all!
|
Bank.fetch_all!
|
||||||
_(Day.count).must_equal count
|
_(Day.count).must_equal(count)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches rates for last 90 days' do
|
it "fetches rates for last 90 days" do
|
||||||
Bank.fetch_ninety_days!
|
Bank.fetch_ninety_days!
|
||||||
_(Day.count).must_be :>, 1
|
_(Day.count).must_be(:>, 1)
|
||||||
_(Day.count).must_be :<, 90
|
_(Day.count).must_be(:<, 90)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'seeds rates with saved data' do
|
it "seeds rates with saved data" do
|
||||||
Bank.seed_with_saved_data!
|
Bank.seed_with_saved_data!
|
||||||
_(Day.count).must_be :>, 90
|
_(Day.count).must_be(:>, 90)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not duplicate when fetching rates for last 90 days' do
|
it "does not duplicate when fetching rates for last 90 days" do
|
||||||
Bank.fetch_ninety_days!
|
Bank.fetch_ninety_days!
|
||||||
count = Day.count
|
count = Day.count
|
||||||
Bank.fetch_ninety_days!
|
Bank.fetch_ninety_days!
|
||||||
_(Day.count).must_equal count
|
_(Day.count).must_equal(count)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fetches current rates' do
|
it "fetches current rates" do
|
||||||
Bank.fetch_current!
|
Bank.fetch_current!
|
||||||
_(Day.count).must_equal 1
|
_(Day.count).must_equal(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not duplicate when fetching current rates' do
|
it "does not duplicate when fetching current rates" do
|
||||||
2.times { Bank.fetch_current! }
|
2.times { Bank.fetch_current! }
|
||||||
_(Day.count).must_equal 1
|
_(Day.count).must_equal(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'replaces all rates' do
|
it "replaces all rates" do
|
||||||
Bank.replace_all!
|
Bank.replace_all!
|
||||||
_(Day.count).must_be :>, 90
|
_(Day.count).must_be(:>, 90)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'currency_names'
|
require "currency_names"
|
||||||
|
|
||||||
describe CurrencyNames do
|
describe CurrencyNames do
|
||||||
let(:currency_names) do
|
let(:currency_names) do
|
||||||
CurrencyNames.new
|
CurrencyNames.new
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns currency codes and names' do
|
it "returns currency codes and names" do
|
||||||
_(currency_names.formatted['USD']).must_equal 'United States Dollar'
|
_(currency_names.formatted["USD"]).must_equal("United States Dollar")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a cache key' do
|
it "has a cache key" do
|
||||||
_(currency_names.cache_key).wont_be :empty?
|
_(currency_names.cache_key).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'currency'
|
require "currency"
|
||||||
require 'minitest/autorun'
|
require "minitest/autorun"
|
||||||
|
|
||||||
describe Currency do
|
describe Currency do
|
||||||
describe '.latest' do
|
describe ".latest" do
|
||||||
it 'returns latest rates' do
|
it "returns latest rates" do
|
||||||
data = Currency.latest.all
|
data = Currency.latest.all
|
||||||
_(data.count).must_be :>, 1
|
_(data.count).must_be(:>, 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.between' do
|
describe ".between" do
|
||||||
let(:day) do
|
let(:day) do
|
||||||
Date.parse('2010-01-01')
|
Date.parse("2010-01-01")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns everything up to a year' do
|
it "returns everything up to a year" do
|
||||||
interval = day..day + 365
|
interval = day..day + 365
|
||||||
_(Currency.between(interval).map(:date).uniq.count).must_be :>, 52
|
_(Currency.between(interval).map(:date).uniq.count).must_be(:>, 52)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'samples weekly over a year' do
|
it "samples weekly over a year" do
|
||||||
interval = day..day + 366
|
interval = day..day + 366
|
||||||
_(Currency.between(interval).map(:date).uniq.count).must_be :<, 54
|
_(Currency.between(interval).map(:date).uniq.count).must_be(:<, 54)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts by date when sampling' do
|
it "sorts by date when sampling" do
|
||||||
interval = day..day + 366
|
interval = day..day + 366
|
||||||
dates = Currency.between(interval).map(:date)
|
dates = Currency.between(interval).map(:date)
|
||||||
_(dates).must_equal dates.sort
|
_(dates).must_equal(dates.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.only' do
|
describe ".only" do
|
||||||
it 'filters symbols' do
|
it "filters symbols" do
|
||||||
iso_codes = %w[CAD USD]
|
iso_codes = ["CAD", "USD"]
|
||||||
data = Currency.latest.only(*iso_codes).all
|
data = Currency.latest.only(*iso_codes).all
|
||||||
_(data.map(&:iso_code).sort).must_equal iso_codes
|
_(data.map(&:iso_code).sort).must_equal(iso_codes)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nothing if no matches' do
|
it "returns nothing if no matches" do
|
||||||
_(Currency.only('FOO').all).must_be_empty
|
_(Currency.only("FOO").all).must_be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'day'
|
require "day"
|
||||||
|
|
||||||
describe Day do
|
describe Day do
|
||||||
describe '.latest' do
|
describe ".latest" do
|
||||||
it 'returns latest rates before given date' do
|
it "returns latest rates before given date" do
|
||||||
date = Date.parse('2010-01-01')
|
date = Date.parse("2010-01-01")
|
||||||
data = Day.latest(date)
|
data = Day.latest(date)
|
||||||
_(data.first.date).must_be :<=, date
|
_(data.first.date).must_be(:<=, date)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nothing if there are no rates before given date' do
|
it "returns nothing if there are no rates before given date" do
|
||||||
_(Day.latest(Date.parse('1998-01-01'))).must_be_empty
|
_(Day.latest(Date.parse("1998-01-01"))).must_be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.between' do
|
describe ".between" do
|
||||||
it 'returns rates between given dates' do
|
it "returns rates between given dates" do
|
||||||
start_date = Date.parse('2010-01-01')
|
start_date = Date.parse("2010-01-01")
|
||||||
end_date = Date.parse('2010-01-31')
|
end_date = Date.parse("2010-01-31")
|
||||||
dates = Day.between((start_date..end_date)).map(:date).sort
|
dates = Day.between((start_date..end_date)).map(:date).sort
|
||||||
_(dates.first).must_be :>=, start_date
|
_(dates.first).must_be(:>=, start_date)
|
||||||
_(dates.last).must_be :<=, end_date
|
_(dates.last).must_be(:<=, end_date)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nothing if there are no rates between given dates' do
|
it "returns nothing if there are no rates between given dates" do
|
||||||
interval = (Date.parse('1998-01-01')..Date.parse('1998-01-31'))
|
interval = (Date.parse("1998-01-01")..Date.parse("1998-01-31"))
|
||||||
_(Day.between(interval)).must_be_empty
|
_(Day.between(interval)).must_be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'rack/test'
|
require "rack/test"
|
||||||
require 'web/server'
|
require "web/server"
|
||||||
|
|
||||||
describe 'the server' do
|
describe "the server" do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
let(:app) { Web::Server.freeze }
|
let(:app) { Web::Server.freeze }
|
||||||
@ -13,38 +13,38 @@ describe 'the server' do
|
|||||||
Oj.load(last_response.body)
|
Oj.load(last_response.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles unfound pages' do
|
it "handles unfound pages" do
|
||||||
get '/foo'
|
get "/foo"
|
||||||
_(last_response.status).must_equal 404
|
_(last_response.status).must_equal(404)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'will not process an invalid date' do
|
it "will not process an invalid date" do
|
||||||
get '/2010-31-01'
|
get "/2010-31-01"
|
||||||
_(last_response).must_be :unprocessable?
|
_(last_response).must_be(:unprocessable?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'will not process a date before 2000' do
|
it "will not process a date before 2000" do
|
||||||
get '/1999-01-01'
|
get "/1999-01-01"
|
||||||
_(last_response).must_be :not_found?
|
_(last_response).must_be(:not_found?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'will not process an unavailable base' do
|
it "will not process an unavailable base" do
|
||||||
get '/latest?base=UAH'
|
get "/latest?base=UAH"
|
||||||
_(last_response).must_be :not_found?
|
_(last_response).must_be(:not_found?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles malformed queries' do
|
it "handles malformed queries" do
|
||||||
get '/latest?base=USD?callback=?'
|
get "/latest?base=USD?callback=?"
|
||||||
_(last_response).must_be :not_found?
|
_(last_response).must_be(:not_found?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return stale dates' do
|
it "does not return stale dates" do
|
||||||
Day.db.transaction do
|
Day.db.transaction do
|
||||||
get '/latest'
|
get "/latest"
|
||||||
date = json['date']
|
date = json["date"]
|
||||||
Day.latest.delete
|
Day.latest.delete
|
||||||
get '/latest'
|
get "/latest"
|
||||||
_(json['date']).wont_equal date
|
_(json["date"]).wont_equal(date)
|
||||||
raise Sequel::Rollback
|
raise Sequel::Rollback
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
ENV['APP_ENV'] ||= 'test'
|
ENV["APP_ENV"] ||= "test"
|
||||||
|
|
||||||
# Keep SimpleCov at top.
|
# Keep SimpleCov at top.
|
||||||
require 'simplecov'
|
require "simplecov"
|
||||||
|
|
||||||
SimpleCov.start do
|
SimpleCov.start do
|
||||||
add_filter '/spec/'
|
add_filter "/spec/"
|
||||||
end
|
end
|
||||||
|
|
||||||
require_relative '../config/environment'
|
require_relative "../config/environment"
|
||||||
|
|
||||||
require 'minitest/autorun'
|
require "minitest/autorun"
|
||||||
require 'minitest/around/spec'
|
require "minitest/around/spec"
|
||||||
require 'minitest/focus'
|
require "minitest/focus"
|
||||||
require 'pry-byebug'
|
require "vcr"
|
||||||
require 'vcr'
|
require "webmock"
|
||||||
require 'webmock'
|
|
||||||
|
|
||||||
VCR.configure do |c|
|
VCR.configure do |c|
|
||||||
c.cassette_library_dir = 'spec/vcr_cassettes'
|
c.cassette_library_dir = "spec/vcr_cassettes"
|
||||||
c.hook_into :webmock
|
c.hook_into(:webmock)
|
||||||
end
|
end
|
||||||
|
@ -1,72 +1,72 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'query'
|
require "query"
|
||||||
|
|
||||||
describe Query do
|
describe Query do
|
||||||
it 'builds a query hash' do
|
it "builds a query hash" do
|
||||||
_(Query.build(date: '2014-01-01')).must_be_kind_of Hash
|
_(Query.build(date: "2014-01-01")).must_be_kind_of(Hash)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns given amount' do
|
it "returns given amount" do
|
||||||
query = Query.new(amount: '100')
|
query = Query.new(amount: "100")
|
||||||
_(query.amount).must_equal 100.0
|
_(query.amount).must_equal(100.0)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults amount to nothing' do
|
it "defaults amount to nothing" do
|
||||||
query = Query.new
|
query = Query.new
|
||||||
_(query.amount).must_be_nil
|
_(query.amount).must_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns given base' do
|
it "returns given base" do
|
||||||
query = Query.new(base: 'USD')
|
query = Query.new(base: "USD")
|
||||||
_(query.base).must_equal 'USD'
|
_(query.base).must_equal("USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'upcases given base' do
|
it "upcases given base" do
|
||||||
query = Query.new(base: 'usd')
|
query = Query.new(base: "usd")
|
||||||
_(query.base).must_equal 'USD'
|
_(query.base).must_equal("USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults base to nothing' do
|
it "defaults base to nothing" do
|
||||||
query = Query.new
|
query = Query.new
|
||||||
_(query.base).must_be_nil
|
_(query.base).must_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'aliases base with from' do
|
it "aliases base with from" do
|
||||||
query = Query.new(from: 'USD')
|
query = Query.new(from: "USD")
|
||||||
_(query.base).must_equal 'USD'
|
_(query.base).must_equal("USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns given symbols' do
|
it "returns given symbols" do
|
||||||
query = Query.new(symbols: 'USD,GBP')
|
query = Query.new(symbols: "USD,GBP")
|
||||||
_(query.symbols).must_equal %w[USD GBP]
|
_(query.symbols).must_equal(["USD", "GBP"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'upcases given symbols' do
|
it "upcases given symbols" do
|
||||||
query = Query.new(symbols: 'usd,gbp')
|
query = Query.new(symbols: "usd,gbp")
|
||||||
_(query.symbols).must_equal %w[USD GBP]
|
_(query.symbols).must_equal(["USD", "GBP"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'aliases symbols with to' do
|
it "aliases symbols with to" do
|
||||||
query = Query.new(to: 'USD')
|
query = Query.new(to: "USD")
|
||||||
_(query.symbols).must_equal ['USD']
|
_(query.symbols).must_equal(["USD"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults symbols to nothing' do
|
it "defaults symbols to nothing" do
|
||||||
query = Query.new
|
query = Query.new
|
||||||
_(query.symbols).must_be_nil
|
_(query.symbols).must_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns given date' do
|
it "returns given date" do
|
||||||
date = '2014-01-01'
|
date = "2014-01-01"
|
||||||
query = Query.new(date:)
|
query = Query.new(date:)
|
||||||
_(query.date).must_equal Date.parse(date)
|
_(query.date).must_equal(Date.parse(date))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns given date interval' do
|
it "returns given date interval" do
|
||||||
start_date = '2014-01-01'
|
start_date = "2014-01-01"
|
||||||
end_date = '2014-12-31'
|
end_date = "2014-12-31"
|
||||||
query = Query.new(start_date:, end_date:)
|
query = Query.new(start_date:, end_date:)
|
||||||
_(query.date).must_equal((Date.parse(start_date)..Date.parse(end_date)))
|
_(query.date).must_equal((Date.parse(start_date)..Date.parse(end_date)))
|
||||||
end
|
end
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../helper'
|
require_relative "../helper"
|
||||||
require 'quote/base'
|
require "quote/base"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
describe Base do
|
describe Base do
|
||||||
@ -13,83 +13,83 @@ module Quote
|
|||||||
klass.new(date: Date.today)
|
klass.new(date: Date.today)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'requires data' do
|
it "requires data" do
|
||||||
_ { quote.perform }.must_raise NotImplementedError
|
_ { quote.perform }.must_raise(NotImplementedError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not know how to format result' do
|
it "does not know how to format result" do
|
||||||
_ { quote.formatted }.must_raise NotImplementedError
|
_ { quote.formatted }.must_raise(NotImplementedError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not know how to generate a cache key' do
|
it "does not know how to generate a cache key" do
|
||||||
_ { quote.cache_key }.must_raise NotImplementedError
|
_ { quote.cache_key }.must_raise(NotImplementedError)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults base to Euro' do
|
it "defaults base to Euro" do
|
||||||
_(quote.base).must_equal 'EUR'
|
_(quote.base).must_equal("EUR")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'defaults amount to 1' do
|
it "defaults amount to 1" do
|
||||||
_(quote.amount).must_equal 1
|
_(quote.amount).must_equal(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given data' do
|
describe "when given data" do
|
||||||
before do
|
before do
|
||||||
def quote.fetch_data
|
def quote.fetch_data
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'performs' do
|
it "performs" do
|
||||||
assert quote.perform
|
assert quote.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'performs only once' do
|
it "performs only once" do
|
||||||
quote.perform
|
quote.perform
|
||||||
|
|
||||||
refute quote.perform
|
refute quote.perform
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when rebasing from an unavailable currency' do
|
describe "when rebasing from an unavailable currency" do
|
||||||
let(:date) do
|
let(:date) do
|
||||||
Date.parse('2000-01-01')
|
Date.parse("2000-01-01")
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
klass.new(date:, base: 'ILS')
|
klass.new(date:, base: "ILS")
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
def quote.fetch_data
|
def quote.fetch_data
|
||||||
[{ date:, iso_code: 'USD', rate: 1 }]
|
[{ date:, iso_code: "USD", rate: 1 }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds nothing' do
|
it "finds nothing" do
|
||||||
quote.perform
|
quote.perform
|
||||||
_(quote.not_found?).must_equal true
|
_(quote.not_found?).must_equal(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when rebasing and converting to an unavailable currency' do
|
describe "when rebasing and converting to an unavailable currency" do
|
||||||
let(:date) do
|
let(:date) do
|
||||||
Date.today
|
Date.today
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
klass.new(date:, base: 'USD', symbols: ['FOO'])
|
klass.new(date:, base: "USD", symbols: ["FOO"])
|
||||||
end
|
end
|
||||||
|
|
||||||
before do
|
before do
|
||||||
def quote.fetch_data
|
def quote.fetch_data
|
||||||
[{ date:, iso_code: 'USD', rate: 1 }]
|
[{ date:, iso_code: "USD", rate: 1 }]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'finds nothing' do
|
it "finds nothing" do
|
||||||
quote.perform
|
quote.perform
|
||||||
_(quote.not_found?).must_equal true
|
_(quote.not_found?).must_equal(true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../helper'
|
require_relative "../helper"
|
||||||
require 'quote/end_of_day'
|
require "quote/end_of_day"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
describe EndOfDay do
|
describe EndOfDay do
|
||||||
let(:date) do
|
let(:date) do
|
||||||
Date.parse('2010-10-10')
|
Date.parse("2010-10-10")
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
@ -17,66 +17,66 @@ module Quote
|
|||||||
quote.perform
|
quote.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns rates' do
|
it "returns rates" do
|
||||||
_(quote.formatted[:rates]).wont_be :empty?
|
_(quote.formatted[:rates]).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes given date' do
|
it "quotes given date" do
|
||||||
_(Date.parse(quote.formatted[:date])).must_be :<=, date
|
_(Date.parse(quote.formatted[:date])).must_be(:<=, date)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes against the Euro' do
|
it "quotes against the Euro" do
|
||||||
_(quote.formatted[:rates].keys).wont_include 'EUR'
|
_(quote.formatted[:rates].keys).wont_include("EUR")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
rates = quote.formatted[:rates]
|
rates = quote.formatted[:rates]
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a cache key' do
|
it "has a cache key" do
|
||||||
_(quote.cache_key).wont_be :empty?
|
_(quote.cache_key).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'given a new base' do
|
describe "given a new base" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
EndOfDay.new(date:, base: 'USD')
|
EndOfDay.new(date:, base: "USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes against that base' do
|
it "quotes against that base" do
|
||||||
_(quote.formatted[:rates].keys).wont_include 'USD'
|
_(quote.formatted[:rates].keys).wont_include("USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
rates = quote.formatted[:rates]
|
rates = quote.formatted[:rates]
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'given symbols' do
|
describe "given symbols" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
EndOfDay.new(date:, symbols: %w[USD GBP JPY])
|
EndOfDay.new(date:, symbols: ["USD", "GBP", "JPY"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes only for those symbols' do
|
it "quotes only for those symbols" do
|
||||||
rates = quote.formatted[:rates]
|
rates = quote.formatted[:rates]
|
||||||
_(rates.keys).must_include 'USD'
|
_(rates.keys).must_include("USD")
|
||||||
_(rates.keys).wont_include 'CAD'
|
_(rates.keys).wont_include("CAD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
rates = quote.formatted[:rates]
|
rates = quote.formatted[:rates]
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given an amount' do
|
describe "when given an amount" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
EndOfDay.new(date:, amount: 100)
|
EndOfDay.new(date:, amount: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calculates quotes for that amount' do
|
it "calculates quotes for that amount" do
|
||||||
_(quote.formatted[:rates]['USD']).must_be :>, 10
|
_(quote.formatted[:rates]["USD"]).must_be(:>, 10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../helper'
|
require_relative "../helper"
|
||||||
require 'quote/interval'
|
require "quote/interval"
|
||||||
|
|
||||||
module Quote
|
module Quote
|
||||||
describe Interval do
|
describe Interval do
|
||||||
let(:dates) do
|
let(:dates) do
|
||||||
(Date.parse('2010-01-01')..Date.parse('2010-12-31'))
|
(Date.parse("2010-01-01")..Date.parse("2010-12-31"))
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
@ -17,76 +17,76 @@ module Quote
|
|||||||
quote.perform
|
quote.perform
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns rates' do
|
it "returns rates" do
|
||||||
_(quote.formatted[:rates]).wont_be :empty?
|
_(quote.formatted[:rates]).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes given date interval' do
|
it "quotes given date interval" do
|
||||||
_(Date.parse(quote.formatted[:start_date])).must_be :>=, dates.first
|
_(Date.parse(quote.formatted[:start_date])).must_be(:>=, dates.first)
|
||||||
_(Date.parse(quote.formatted[:end_date])).must_be :<=, dates.last
|
_(Date.parse(quote.formatted[:end_date])).must_be(:<=, dates.last)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes against the Euro' do
|
it "quotes against the Euro" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).wont_include 'EUR'
|
_(rates.keys).wont_include("EUR")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has a cache key' do
|
it "has a cache key" do
|
||||||
_(quote.cache_key).wont_be :empty?
|
_(quote.cache_key).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'given a new base' do
|
describe "given a new base" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
Interval.new(date: dates, base: 'USD')
|
Interval.new(date: dates, base: "USD")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes against that base' do
|
it "quotes against that base" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).wont_include 'USD'
|
_(rates.keys).wont_include("USD")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'given symbols' do
|
describe "given symbols" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
Interval.new(date: dates, symbols: %w[USD GBP JPY])
|
Interval.new(date: dates, symbols: ["USD", "GBP", "JPY"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'quotes only for those symbols' do
|
it "quotes only for those symbols" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).must_include 'USD'
|
_(rates.keys).must_include("USD")
|
||||||
_(rates.keys).wont_include 'CAD'
|
_(rates.keys).wont_include("CAD")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it "sorts rates" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates.keys).must_equal rates.keys.sort
|
_(rates.keys).must_equal(rates.keys.sort)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given an amount' do
|
describe "when given an amount" do
|
||||||
let(:quote) do
|
let(:quote) do
|
||||||
Interval.new(date: dates, amount: 100)
|
Interval.new(date: dates, amount: 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'calculates quotes for that amount' do
|
it "calculates quotes for that amount" do
|
||||||
quote.formatted[:rates].each_value do |rates|
|
quote.formatted[:rates].each_value do |rates|
|
||||||
_(rates['USD']).must_be :>, 10
|
_(rates["USD"]).must_be(:>, 10)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,44 +1,44 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative "helper"
|
||||||
require 'roundable'
|
require "roundable"
|
||||||
|
|
||||||
describe Roundable do
|
describe Roundable do
|
||||||
include Roundable
|
include Roundable
|
||||||
|
|
||||||
it 'rounds values over 5,000 to zero decimal places' do
|
it "rounds values over 5,000 to zero decimal places" do
|
||||||
_(round(5000.123456)).must_equal 5000
|
_(round(5000.123456)).must_equal(5000)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rounds values over 80 and below 5,000 to two decimal places' do
|
it "rounds values over 80 and below 5,000 to two decimal places" do
|
||||||
_(round(80.123456)).must_equal 80.12
|
_(round(80.123456)).must_equal(80.12)
|
||||||
_(round(4999.123456)).must_equal 4999.12
|
_(round(4999.123456)).must_equal(4999.12)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rounds values over 20 and below 80 to three decimal places' do
|
it "rounds values over 20 and below 80 to three decimal places" do
|
||||||
_(round(79.123456)).must_equal 79.123
|
_(round(79.123456)).must_equal(79.123)
|
||||||
_(round(20.123456)).must_equal 20.123
|
_(round(20.123456)).must_equal(20.123)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rounds values over 1 and below 20 to four decimal places' do
|
it "rounds values over 1 and below 20 to four decimal places" do
|
||||||
_(round(19.123456)).must_equal 19.1235
|
_(round(19.123456)).must_equal(19.1235)
|
||||||
_(round(1.123456)).must_equal 1.1235
|
_(round(1.123456)).must_equal(1.1235)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rounds values below 1 to five decimal places' do
|
it "rounds values below 1 to five decimal places" do
|
||||||
_(round(0.123456)).must_equal 0.12346
|
_(round(0.123456)).must_equal(0.12346)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rounds values below 0.0001 to six decimal places' do
|
it "rounds values below 0.0001 to six decimal places" do
|
||||||
_(round(0.0000655)).must_equal 0.000066
|
_(round(0.0000655)).must_equal(0.000066)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'conforms to ECB conventions' do
|
it "conforms to ECB conventions" do
|
||||||
skip "We don't conform ¯_(ツ)_/¯"
|
skip "We don't conform ¯_(ツ)_/¯"
|
||||||
require 'day'
|
require "day"
|
||||||
rates = Day.all.sample.rates.to_a
|
rates = Day.all.sample.rates.to_a
|
||||||
rates.shuffle.each_value do |rate|
|
rates.shuffle.each_value do |rate|
|
||||||
_(round(rate)).must_equal rate
|
_(round(rate)).must_equal(rate)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,119 +1,119 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative '../helper'
|
require_relative "../helper"
|
||||||
require 'rack/test'
|
require "rack/test"
|
||||||
require 'web/server'
|
require "web/server"
|
||||||
|
|
||||||
describe 'the server' do
|
describe "the server" do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
let(:app) { Web::Server.freeze }
|
let(:app) { Web::Server.freeze }
|
||||||
let(:json) { Oj.load(last_response.body) }
|
let(:json) { Oj.load(last_response.body) }
|
||||||
let(:headers) { last_response.headers }
|
let(:headers) { last_response.headers }
|
||||||
|
|
||||||
it 'returns link to docs' do
|
it "returns link to docs" do
|
||||||
get '/'
|
get "/"
|
||||||
_(json['docs']).wont_be_nil
|
_(json["docs"]).wont_be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns latest quotes' do
|
it "returns latest quotes" do
|
||||||
get '/latest'
|
get "/latest"
|
||||||
_(last_response).must_be :ok?
|
_(last_response).must_be(:ok?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets base currency' do
|
it "sets base currency" do
|
||||||
get '/latest'
|
get "/latest"
|
||||||
res = Oj.load(last_response.body)
|
res = Oj.load(last_response.body)
|
||||||
get '/latest?from=USD'
|
get "/latest?from=USD"
|
||||||
_(json).wont_equal res
|
_(json).wont_equal(res)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets base amount' do
|
it "sets base amount" do
|
||||||
get '/latest?amount=10'
|
get "/latest?amount=10"
|
||||||
_(json['rates']['USD']).must_be :>, 10
|
_(json["rates"]["USD"]).must_be(:>, 10)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters symbols' do
|
it "filters symbols" do
|
||||||
get '/latest?to=USD'
|
get "/latest?to=USD"
|
||||||
_(json['rates'].keys).must_equal %w[USD]
|
_(json["rates"].keys).must_equal(["USD"])
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns historical quotes' do
|
it "returns historical quotes" do
|
||||||
get '/2012-11-20'
|
get "/2012-11-20"
|
||||||
_(json['rates']).wont_be :empty?
|
_(json["rates"]).wont_be(:empty?)
|
||||||
_(json['date']).must_equal '2012-11-20'
|
_(json["date"]).must_equal("2012-11-20")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'works around holidays' do
|
it "works around holidays" do
|
||||||
get '/2010-01-01'
|
get "/2010-01-01"
|
||||||
_(json['rates']).wont_be :empty?
|
_(json["rates"]).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns an ETag' do
|
it "returns an ETag" do
|
||||||
%w[/latest /2012-11-20].each do |path|
|
["/latest", "/2012-11-20"].each do |path|
|
||||||
get path
|
get path
|
||||||
_(headers['ETag']).wont_be_nil
|
_(headers["ETag"]).wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a cache control header' do
|
it "returns a cache control header" do
|
||||||
%w[/latest /2012-11-20].each do |path|
|
["/latest", "/2012-11-20"].each do |path|
|
||||||
get path
|
get path
|
||||||
_(headers['Cache-Control']).wont_be_nil
|
_(headers["Cache-Control"]).wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows cross-origin requests' do
|
it "allows cross-origin requests" do
|
||||||
%w[/ /latest /2012-11-20].each do |path|
|
["/", "/latest", "/2012-11-20"].each do |path|
|
||||||
header 'Origin', '*'
|
header "Origin", "*"
|
||||||
get path
|
get path
|
||||||
|
|
||||||
assert headers.key?('Access-Control-Allow-Methods')
|
assert headers.key?("Access-Control-Allow-Methods")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'responds to preflight requests' do
|
it "responds to preflight requests" do
|
||||||
%w[/ /latest /2012-11-20].each do |path|
|
["/", "/latest", "/2012-11-20"].each do |path|
|
||||||
header 'Origin', '*'
|
header "Origin", "*"
|
||||||
header 'Access-Control-Request-Method', 'GET'
|
header "Access-Control-Request-Method", "GET"
|
||||||
header 'Access-Control-Request-Headers', 'Content-Type'
|
header "Access-Control-Request-Headers", "Content-Type"
|
||||||
options path
|
options path
|
||||||
|
|
||||||
assert headers.key?('Access-Control-Allow-Methods')
|
assert headers.key?("Access-Control-Allow-Methods")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'converts an amount' do
|
it "converts an amount" do
|
||||||
get '/latest?from=GBP&to=USD&amount=100'
|
get "/latest?from=GBP&to=USD&amount=100"
|
||||||
_(json['rates']['USD']).must_be :>, 100
|
_(json["rates"]["USD"]).must_be(:>, 100)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns rates for a given period' do
|
it "returns rates for a given period" do
|
||||||
get '/2010-01-01..2010-12-31'
|
get "/2010-01-01..2010-12-31"
|
||||||
_(json['start_date']).wont_be :empty?
|
_(json["start_date"]).wont_be(:empty?)
|
||||||
_(json['end_date']).wont_be :empty?
|
_(json["end_date"]).wont_be(:empty?)
|
||||||
_(json['rates']).wont_be :empty?
|
_(json["rates"]).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns rates when given period does not include end date' do
|
it "returns rates when given period does not include end date" do
|
||||||
get '/2010-01-01..'
|
get "/2010-01-01.."
|
||||||
_(json['start_date']).wont_be :empty?
|
_(json["start_date"]).wont_be(:empty?)
|
||||||
_(json['end_date']).wont_be :empty?
|
_(json["end_date"]).wont_be(:empty?)
|
||||||
_(json['rates']).wont_be :empty?
|
_(json["rates"]).wont_be(:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns currencies' do
|
it "returns currencies" do
|
||||||
get '/currencies'
|
get "/currencies"
|
||||||
_(json['USD']).must_equal 'United States Dollar'
|
_(json["USD"]).must_equal("United States Dollar")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles JSONP' do
|
it "handles JSONP" do
|
||||||
get '/latest?callback=foo'
|
get "/latest?callback=foo"
|
||||||
_(last_response.body).must_be :start_with?, '/**/foo'
|
_(last_response.body).must_be(:start_with?, "/**/foo")
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets charset to utf-8' do
|
it "sets charset to utf-8" do
|
||||||
get '/currencies'
|
get "/currencies"
|
||||||
_(last_response.headers['content-type']).must_be :end_with?, 'charset=utf-8'
|
_(last_response.headers["content-type"]).must_be(:end_with?, "charset=utf-8")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user