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