Spring cleaning

- bumped gems
- rm bots
- rm pry byebug
- added rubocop-shopify and corrected generated warnings
This commit is contained in:
Hakan Ensari 2024-11-19 20:06:47 +01:00
parent 243269a322
commit e5815737c1
No known key found for this signature in database
45 changed files with 504 additions and 524 deletions

View File

@ -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
View File

@ -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

View File

@ -1,3 +1,6 @@
inherit_gem:
rubocop-shopify: rubocop.yml
inherit_from: ".rubocop_todo.yml"
require:

48
Gemfile
View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 }

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require 'day'
require 'bank/feed'
require "day"
require "bank/feed"
module Bank
class << self

View File

@ -1,36 +1,38 @@
# 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')
xml = Net::HTTP.get(url)
class << self
def current
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")
xml = Net::HTTP.get(url)
new(xml)
end
new(xml)
end
def self.ninety_days
url = URI('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml')
xml = Net::HTTP.get(url)
def ninety_days
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml")
xml = Net::HTTP.get(url)
new(xml)
end
new(xml)
end
def self.historical
url = URI('https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml')
xml = Net::HTTP.get(url)
def historical
url = URI("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml")
xml = Net::HTTP.get(url)
new(xml)
end
new(xml)
end
def self.saved_data
xml = File.read(File.join(__dir__, 'eurofxref-hist.xml'))
new(xml)
def saved_data
xml = File.read(File.join(__dir__, "eurofxref-hist.xml"))
new(xml)
end
end
def initialize(xml)
@ -38,8 +40,8 @@ module Bank
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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,8 +1,10 @@
# frozen_string_literal: true
class Query
def self.build(params)
new(params).to_h
class << self
def build(params)
new(params).to_h
end
end
def initialize(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

View File

@ -1,4 +1,4 @@
# frozen_string_literal: true
require 'quote/end_of_day'
require 'quote/interval'
require "quote/end_of_day"
require "quote/interval"

View File

@ -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,16 +64,16 @@ 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)
else
result[date] = rates.sort
.map! do |iso_code, rate|
[iso_code, round(amount * rate / divisor)]
end
.to_h
.map! do |iso_code, rate|
[iso_code, round(amount * rate / divisor)]
end
.to_h
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -4,8 +4,10 @@ module Scheduler
class Daemon
attr_reader :pid
def self.start
new.start
class << self
def start
new.start
end
end
def initialize
@ -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

View File

@ -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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
desc 'Load environment'
desc "Load environment"
task :environment do
require './config/environment'
require "./config/environment"
end

View File

@ -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

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
begin
require 'sitemap_generator/tasks'
require "sitemap_generator/tasks"
rescue LoadError
return
end

View File

@ -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"]

View File

@ -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',
serializer: ->(o) { Oj.dump(o, mode: :compat) }
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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