Rebrand, serve HTML

This commit is contained in:
Hakan Ensari 2018-04-16 16:19:15 +01:00
parent b30c23d0d8
commit 2c91173ecf
23 changed files with 411 additions and 78 deletions

View File

@ -1,5 +1,4 @@
APP_ENV=production APP_ENV=production
HTTPS_METHOD=noredirect
LETSENCRYPT_EMAIL=foo@bar.com LETSENCRYPT_EMAIL=foo@bar.com
LETSENCRYPT_HOST=example.com,www.example.com LETSENCRYPT_HOST=example.com
VIRTUAL_HOST=example.com,www.example.com VIRTUAL_HOST=example.com

16
.eslintrc Normal file
View File

@ -0,0 +1,16 @@
{
"extends": ["airbnb-standard"],
"globals": {
"fetch": true,
"hljs": true
},
"rules": {
"no-new-func": [
"off"
],
"no-param-reassign": [
"error",
{ "props": false }
]
}
}

View File

@ -1 +1 @@
ruby 2.5.0 ruby 2.5.1

View File

@ -3,12 +3,12 @@ sudo: false
language: ruby language: ruby
rvm: rvm:
- 2.5.0 - 2.5
addons: addons:
postgresql: "9.6" postgresql: "9.6"
before_install: before_install:
- "psql -c 'create database fixer_test;' -U postgres" - "psql -c 'create database frankfurter_test;' -U postgres"
bundler_args: "--without development" bundler_args: "--without development"
env: env:

View File

@ -1,9 +1,9 @@
FROM ruby:2.5.0 FROM ruby:2.5.1
RUN mkdir /app RUN mkdir /app
WORKDIR /app WORKDIR /app
ADD Gemfile /app/Gemfile ADD Gemfile /app/Gemfile
ADD Gemfile.lock /app/Gemfile.lock ADD Gemfile.lock /app/Gemfile.lock
RUN bundle install --jobs=8 --without development RUN bundle install --jobs=8 --without development test
ADD . /app ADD . /app
CMD ["unicorn", "-c", "./config/unicorn.rb"] CMD ["unicorn", "-c", "./config/unicorn.rb"]

View File

@ -2,7 +2,7 @@
source 'http://rubygems.org' source 'http://rubygems.org'
ruby '2.5.0' ruby '2.5.1'
gem 'oj' gem 'oj'
gem 'ox' gem 'ox'
@ -14,7 +14,12 @@ gem 'sinatra'
gem 'unicorn' gem 'unicorn'
group :development do group :development do
gem 'guard'
gem 'guard-livereload'
gem 'guard-minitest'
gem 'pry' gem 'pry'
gem 'rack-livereload'
gem 'shotgun'
end end
group :test do group :test do

View File

@ -7,19 +7,58 @@ GEM
coderay (1.1.2) coderay (1.1.2)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
et-orbi (1.0.9) em-websocket (0.5.1)
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.6.0)
et-orbi (1.1.2)
tzinfo tzinfo
eventmachine (1.2.7)
ffi (1.9.25)
formatador (0.2.5)
fugit (1.1.1)
et-orbi (~> 1.1, >= 1.1.1)
raabro (~> 1.1)
guard (2.14.2)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.9.12)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-compat (1.2.1)
guard-livereload (2.5.2)
em-websocket (~> 0.5)
guard (~> 2.8)
guard-compat (~> 1.0)
multi_json (~> 1.8)
guard-minitest (2.4.6)
guard-compat (~> 1.2)
minitest (>= 3.0)
hashdiff (0.3.7) hashdiff (0.3.7)
http_parser.rb (0.6.0)
jaro_winkler (1.4.0)
kgio (2.11.2) kgio (2.11.2)
listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7)
ruby_dep (~> 1.2)
lumberjack (1.0.13)
method_source (0.9.0) method_source (0.9.0)
minitest (5.11.3) minitest (5.11.3)
minitest-around (0.4.1) minitest-around (0.4.1)
minitest (~> 5.0) minitest (~> 5.0)
multi_json (1.13.1)
mustermann (1.0.2) mustermann (1.0.2)
oj (3.5.0) nenv (0.3.0)
ox (2.9.0) notiffany (0.1.1)
nenv (~> 0.1)
shellany (~> 0.0)
oj (3.6.2)
ox (2.9.2)
parallel (1.12.1) parallel (1.12.1)
parser (2.5.0.4) parser (2.5.1.0)
ast (~> 2.4.0) ast (~> 2.4.0)
pg (1.0.0) pg (1.0.0)
powerpack (0.1.1) powerpack (0.1.1)
@ -27,16 +66,23 @@ GEM
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.9.0) method_source (~> 0.9.0)
public_suffix (3.0.2) public_suffix (3.0.2)
rack (2.0.4) raabro (1.1.5)
rack (2.0.5)
rack-cors (1.0.2) rack-cors (1.0.2)
rack-protection (2.0.1) rack-livereload (0.3.17)
rack rack
rack-test (0.8.3) rack-protection (2.0.2)
rack
rack-test (1.0.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rainbow (3.0.0) rainbow (3.0.0)
raindrops (0.19.0) raindrops (0.19.0)
rake (12.3.1) rake (12.3.1)
rubocop (0.54.0) rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
rubocop (0.57.0)
jaro_winkler (~> 1.4.0)
parallel (~> 1.10) parallel (~> 1.10)
parser (>= 2.5) parser (>= 2.5)
powerpack (~> 0.1) powerpack (~> 0.1)
@ -44,28 +90,33 @@ GEM
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0) ruby-progressbar (1.9.0)
rufus-scheduler (3.4.2) ruby_dep (1.5.0)
et-orbi (~> 1.0) rufus-scheduler (3.5.0)
fugit (~> 1.1, >= 1.1.1)
safe_yaml (1.0.4) safe_yaml (1.0.4)
sequel (5.6.0) sequel (5.9.0)
sequel_pg (1.8.1) sequel_pg (1.8.2)
pg (>= 0.18.0) pg (>= 0.18.0)
sequel (>= 4.34.0) sequel (>= 4.34.0)
sinatra (2.0.1) shellany (0.0.1)
shotgun (0.9.2)
rack (>= 1.0)
sinatra (2.0.2)
mustermann (~> 1.0) mustermann (~> 1.0)
rack (~> 2.0) rack (~> 2.0)
rack-protection (= 2.0.1) rack-protection (= 2.0.2)
tilt (~> 2.0) tilt (~> 2.0)
thor (0.20.0)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.8) tilt (2.0.8)
tzinfo (1.2.5) tzinfo (1.2.5)
thread_safe (~> 0.1) thread_safe (~> 0.1)
unicode-display_width (1.3.0) unicode-display_width (1.4.0)
unicorn (5.4.0) unicorn (5.4.0)
kgio (~> 2.6) kgio (~> 2.6)
raindrops (~> 0.7) raindrops (~> 0.7)
vcr (4.0.0) vcr (4.0.0)
webmock (3.3.0) webmock (3.4.2)
addressable (>= 2.3.6) addressable (>= 2.3.6)
crack (>= 0.3.2) crack (>= 0.3.2)
hashdiff hashdiff
@ -74,24 +125,29 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
guard
guard-livereload
guard-minitest
minitest minitest
minitest-around minitest-around
oj oj
ox ox
pry pry
rack-cors rack-cors
rack-livereload
rack-test rack-test
rake rake
rubocop rubocop
rufus-scheduler rufus-scheduler
sequel_pg sequel_pg
shotgun
sinatra sinatra
unicorn unicorn
vcr vcr
webmock webmock
RUBY VERSION RUBY VERSION
ruby 2.5.0p0 ruby 2.5.1p57
BUNDLED WITH BUNDLED WITH
1.16.1 1.16.1

13
Guardfile Normal file
View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
guard 'livereload' do
extensions = %i[css js png gif jpg]
watch(%r{lib/web/public/.+\.(#{extensions * '|'})})
watch(%r{lib/web/views/.+\.erb$})
end
guard :minitest do
watch(%r{^spec/(.*)_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^spec/helper\.rb$}) { 'spec' }
end

View File

@ -1,8 +1,8 @@
# Fixer # Frankfurter
[![Travis](https://travis-ci.org/hakanensari/fixer.svg)](https://travis-ci.org/hakanensari/fixer) [![Travis](https://travis-ci.org/hakanensari/frankfurter.svg)](https://travis-ci.org/hakanensari/frankfurter)
Fixer is a free API for current and historical foreign exchange rates [published by the European Central Bank](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html). Frankfurter is a free API for current and historical foreign exchange rates [published by the European Central Bank](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html).
Rates are updated around 4PM CET every working day. Rates are updated around 4PM CET every working day.

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
require './config/environment' require './config/environment'
require 'api' require 'web/server'
run Sinatra::Application run Sinatra::Application

View File

@ -4,4 +4,4 @@ require 'pg'
require 'sequel' require 'sequel'
Sequel.single_threaded = true Sequel.single_threaded = true
Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost/fixer_#{App.env}") Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost/frankfurter_#{App.env}")

View File

@ -4,7 +4,7 @@ services:
restart: unless-stopped restart: unless-stopped
web: web:
env_file: .env env_file: .env
image: hakanensari/fixer image: hakanensari/frankfurter
logging: logging:
options: options:
max-size: "50m" max-size: "50m"
@ -12,7 +12,7 @@ services:
restart: unless-stopped restart: unless-stopped
scheduler: scheduler:
env_file: .env env_file: .env
image: hakanensari/fixer image: hakanensari/frankfurter
logging: logging:
options: options:
max-size: "50m" max-size: "50m"

View File

@ -2,7 +2,7 @@
require 'currency' require 'currency'
class Quotation class Quote
DEFAULT_BASE = 'EUR' DEFAULT_BASE = 'EUR'
def initialize(amount: 1.0, def initialize(amount: 1.0,
@ -15,7 +15,7 @@ class Quotation
@symbols = symbols @symbols = symbols
end end
def quote def to_h
{ base: @base, date: date, rates: calculate_rates } { base: @base, date: date, rates: calculate_rates }
end end

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@ -0,0 +1,29 @@
hljs.initHighlightingOnLoad();
// Make URLs clickable
document.querySelectorAll('.http').forEach((element) => {
element.onclick = () => {
if (element.innerHTML.indexOf('\n') > -1) {
const output = element.innerHTML.match(/^(.*)\n/)[1];
element.innerHTML = output;
} else {
const url = element.innerHTML.match(/>(\/[^<]*)/)[1];
fetch(url)
.then((resp) => {
const host = resp.url.match(/\/\/([^:/]+)/)[1];
element.insertAdjacentHTML('beforeend', `\nHost: ${host}\nContent-Type: ${resp.headers.get('Content-Type')}\nContent-Length: ${resp.headers.get('Content-Length')}\n\n`);
return resp.json();
})
.then((data) => {
element.insertAdjacentHTML('beforeend', `${JSON.stringify(data, undefined, 4)}`);
hljs.highlightBlock(element);
});
}
};
});
// Make the JavaScript examples clickable
document.querySelectorAll('.js').forEach((element) => {
const code = element.innerHTML.replace(/&gt;/g, '>');
element.onclick = () => new Function(code)();
});

View File

@ -0,0 +1,84 @@
body {
font-family: 'Open Sans', sans-serif;
}
.navbar .navbar-nav .nav-link {
font-size: .875rem;
}
.container {
max-width: 720px;
}
.section {
padding-top: 1em;
}
.hero .container {
text-align: center;
}
.hero .logo {
padding-bottom: 1em;
width: 120px;
}
.footer .container {
padding-bottom: 2em;
text-align: center;
}
.hljs {
background-color: #f8f9fa;
border: 2px solid #f8f9fa;
padding: 1em;
}
.hljs:hover {
border-bottom-color: #dee2e6;
cursor: pointer;
}
.hljs i {
color: #ced4da;
font-style: normal;
}
#carbonads {
display: none;
}
/*
@media screen and (max-width: 767px) {
#carbonads {
display: none;
}
}
@media screen and (min-width: 768px) {
#carbonads {
background-color: #f0f0f0;
position: absolute;
right: 24px;
top: 36px;
width: 150px;
}
.carbon-img,
.carbon-poweredby,
.carbon-text {
display: block;
margin: 10px;
}
.carbon-text,
.carbon-poweredby {
line-height: 1.3;
text-decoration: none;
}
.carbon-text {
font-size: 13px;
}
.carbon-poweredby {
font-size: 11px;
color: #999;
}
}
*/

View File

@ -1,11 +1,11 @@
# frozen_string_literal: true # frozen_string_literal: true
require 'oj' require 'oj'
require 'sinatra'
require 'rack/cors' require 'rack/cors'
require 'sinatra'
require 'query' require 'query'
require 'quotation' require 'quote'
use Rack::Cors do use Rack::Cors do
allow do allow do
@ -27,16 +27,17 @@ configure :test do
end end
helpers do helpers do
def quotation def quote
@quotation ||= begin @quote ||= begin
query = Query.new(params) query = Query.new(params)
Quotation.new(query.to_h) Quote.new(query.to_h)
end end
end end
def jsonp(data) def json(data)
json = Oj.dump(data, mode: :compat) json = Oj.dump(data, mode: :compat)
callback = params.delete('callback') callback = params.delete('callback')
if callback if callback
content_type :js content_type :js
"#{callback}(#{json})" "#{callback}(#{json})"
@ -52,22 +53,27 @@ options '*' do
end end
get '*' do get '*' do
cache_control :public, :must_revalidate, max_age: 900 cache_control :public
pass pass
end end
get '/' do get '/' do
jsonp source: 'https://github.com/hakanensari/fixer' erb :index
end end
get '/latest' do get '/(?:latest|current)', mustermann_opts: { type: :regexp } do
last_modified quotation.date last_modified quote.date
jsonp quotation.quote json quote.to_h
end end
get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
last_modified quotation.date last_modified quote.date
jsonp quotation.quote json quote.to_h
end
get '/(?<start_date>\d{4}-\d{2}-\d{2})\.\.(?<end_date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
last_modified quote.end_date
json quote.to_h
end end
not_found do not_found do

118
lib/web/views/index.erb Normal file
View File

@ -0,0 +1,118 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Frankfurter provides an open-source API for current and historical foreign exchange rates and currency conversion. The API tracks rates published daily by the European Central Bank.">
<title>Frankfurter</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css">
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.1/css/bootstrap.min.css">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:300,400">
<link rel="stylesheet" href="/stylesheets/application.css">
<link rel="shortcut icon" href="/images/frankfurter-icon.png">
</head>
<body>
<nav class="navbar navbar-expand">
<div class="container">
<ul class="navbar-nav mx-sm-auto">
<li class="nav-item">
<a class="nav-link" href="https://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html" target="_blank">Data sets</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://status.frankfurter.app/" target="_blank">Status</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/hakanensari/frankfurter" target="_blank">Source</a>
</li>
</ul>
</div>
</nav>
<section class="hero section">
<div class="container">
<h1 class="display-4">Frankfurter</h1>
<p class="lead">
Foreign exchange rates and currency conversion API
</p>
<img alt="" class="logo" src="/images/frankfurter.png">
</div>
</section>
<section class="introduction section">
<div class="container">
<p>
Frankfurter is a lightweight API for querying current and historical forex rates published by the European Central Bank.
</p>
<p>
Rates update around 4PM CET on every working day. Historical data goes back to early 1999.
</p>
</div>
</section>
<section class="usage section">
<div class="container">
<h5>Usage</h5>
<p>
Get current foreign exchange rates.
</p>
<pre><code class="http hljs small">GET /current <i>HTTP/1.1</i></code></pre>
<p>
Get historical rates for any day since 1999.
</p>
<pre><code class="http hljs small">GET /1999-12-31 <i>HTTP/1.1</i></code></pre>
<p>
Rates are quoted against the Euro by default. Quote against a different
currency by setting the <strong>from</strong> parameter in your request.
</p>
<pre><code class="http hljs small">GET /current?from=USD <i>HTTP/1.1</i></code></pre>
<p>
Request specific exchange rates by setting the <strong>to</strong> parameter.
</p>
<pre><code class="http hljs small">GET /current?to=USD,GBP <i>HTTP/1.1</i></code></pre>
<p>
Convert a specific value.
</p>
<pre><code class="http hljs small">GET /current?amount=1000&from=GBP&to=EUR <i>HTTP/1.1</i></code></pre>
</div>
</section>
<section class="best-practices section">
<div class="container">
<h5>Best Practices</h5>
<p>
The primary use case of Frankfurter is client side. If you are converting currencies in the browser, you will appreciate the unencumbered access currently not possible with similar commercial services.
</p>
<pre><code class="js hljs small">// Fetch and display GBP/USD
fetch('/current?from=GBP&to=USD')
.then(resp => resp.json())
.then((data) => { alert(`GBP/USD = ${data.rates.USD}`); });</code></pre>
<p>
If you are working with rates on the server side, download them off the European Central Bank to avoid unnecessary indirection.
</p>
<p>
If you require more currencies or granular data, you are better off going with a commercial service.
</p>
<p>
Cache results whenever possible.
</p>
</div>
</section>
<section class="best-practices section">
<div class="container">
<h5>Notes</h5>
<p>
This project started out as <strong>Fixer</strong> in 2013. I renamed it to <strong>Frankfurter</strong> after selling the <a href="https://fixer.io" target="_blank">original domain</a> in early 2018.
<p>
The API is open source and comes with no warranty.
</p>
</div>
</section>
<section class="footer section">
<div class="container">
<iframe src="https://ghbtns.com/github-btn.html?user=hakanensari&repo=frankfurter&type=star&count=true" frameborder="0" scrolling="0" width="120px" height="20px"></iframe>
</div>
</section>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script src="/javascripts/application.js"></script>
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=fixerio" id="_carbonads_js"></script>
</body>
</html>

View File

View File

@ -2,7 +2,7 @@
require_relative 'helper' require_relative 'helper'
require 'rack/test' require 'rack/test'
require 'api' require 'web/server'
describe 'the API' do describe 'the API' do
include Rack::Test::Methods include Rack::Test::Methods

View File

@ -1,35 +1,35 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'helper' require_relative 'helper'
require 'quotation' require 'quote'
describe Quotation do describe Quote do
describe 'by default' do describe 'by default' do
it 'quotes against the Euro' do it 'quotes against the Euro' do
quotation = Quotation.new quote = Quote.new
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates.keys.wont_include 'EUR' rates.keys.wont_include 'EUR'
end end
end end
describe 'when given a base' do describe 'when given a base' do
it 'quotes against that base' do it 'quotes against that base' do
quotation = Quotation.new(base: 'USD') quote = Quote.new(base: 'USD')
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates.keys.wont_include 'USD' rates.keys.wont_include 'USD'
end end
it 'sorts rates' do it 'sorts rates' do
quotation = Quotation.new(base: 'USD') quote = Quote.new(base: 'USD')
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates.keys.must_equal rates.keys.sort rates.keys.must_equal rates.keys.sort
end end
end end
describe 'when given symbols' do describe 'when given symbols' do
it 'quotes rates only for given symbols' do it 'quotes rates only for given symbols' do
quotation = Quotation.new(symbols: ['USD']) quote = Quote.new(symbols: ['USD'])
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates.keys.must_include 'USD' rates.keys.must_include 'USD'
rates.keys.wont_include 'GBP' rates.keys.wont_include 'GBP'
end end
@ -37,16 +37,16 @@ describe Quotation do
describe 'when given an amount' do describe 'when given an amount' do
it 'quotes for that amount' do it 'quotes for that amount' do
quotation = Quotation.new(amount: 100) quote = Quote.new(amount: 100)
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates['USD'].must_be :>, 10 rates['USD'].must_be :>, 10
end end
end end
describe 'when given an amount and symbols' do describe 'when given an amount and symbols' do
it 'quotes for that amount' do it 'quotes for that amount' do
quotation = Quotation.new(amount: 100, symbols: ['USD']) quote = Quote.new(amount: 100, symbols: ['USD'])
rates = quotation.quote[:rates] rates = quote.to_h[:rates]
rates['USD'].must_be :>, 10 rates['USD'].must_be :>, 10
end end
end end

View File

@ -1,40 +1,40 @@
# frozen_string_literal: true # frozen_string_literal: true
require_relative 'helper' require_relative '../helper'
require 'rack/test' require 'rack/test'
require 'api' require 'web/server'
describe 'the API' do describe 'the server' do
include Rack::Test::Methods include Rack::Test::Methods
let(:app) { Sinatra::Application } let(:app) { Sinatra::Application }
let(:json) { Oj.load(last_response.body) } let(:json) { Oj.load(last_response.body) }
let(:headers) { last_response.headers } let(:headers) { last_response.headers }
it 'describes itself' do it 'has a homepage' do
get '/' get '/'
last_response.must_be :ok? last_response.must_be :ok?
end end
it 'returns latest quotes' do it 'returns current quotes' do
get '/latest' get '/current'
last_response.must_be :ok? last_response.must_be :ok?
end end
it 'sets base currency' do it 'sets base currency' do
get '/latest' get '/current'
res = Oj.load(last_response.body) res = Oj.load(last_response.body)
get '/latest?base=USD' get '/current?from=USD'
json.wont_equal res json.wont_equal res
end end
it 'sets base amount' do it 'sets base amount' do
get '/latest?amount=10' get '/current?amount=10'
json['rates']['USD'].must_be :>, 10 json['rates']['USD'].must_be :>, 10
end end
it 'filters symbols' do it 'filters symbols' do
get '/latest?symbols=USD' get '/current?to=USD'
json['rates'].keys.must_equal %w[USD] json['rates'].keys.must_equal %w[USD]
end end
@ -50,21 +50,21 @@ describe 'the API' do
end end
it 'returns a cache control header' do it 'returns a cache control header' do
%w[/ /latest /2012-11-20].each do |path| %w[/ /current /2012-11-20].each do |path|
get path get path
headers['Cache-Control'].wont_be_nil headers['Cache-Control'].wont_be_nil
end end
end end
it 'returns a last modified header' do it 'returns a last modified header' do
%w[/latest /2012-11-20].each do |path| %w[/current /2012-11-20].each do |path|
get path get path
headers['Last-Modified'].wont_be_nil headers['Last-Modified'].wont_be_nil
end end
end end
it 'allows cross-origin requests' do it 'allows cross-origin requests' do
%w[/ /latest /2012-11-20].each do |path| %w[/ /current /2012-11-20].each do |path|
header 'Origin', '*' header 'Origin', '*'
get path get path
assert headers.key?('Access-Control-Allow-Methods') assert headers.key?('Access-Control-Allow-Methods')
@ -72,7 +72,7 @@ describe 'the API' do
end end
it 'responds to preflight requests' do it 'responds to preflight requests' do
%w[/ /latest /2012-11-20].each do |path| %w[/ /current /2012-11-20].each do |path|
header 'Origin', '*' header 'Origin', '*'
header 'Access-Control-Request-Method', 'GET' header 'Access-Control-Request-Method', 'GET'
header 'Access-Control-Request-Headers', 'Content-Type' header 'Access-Control-Request-Headers', 'Content-Type'
@ -82,7 +82,14 @@ describe 'the API' do
end end
it 'converts an amount' do it 'converts an amount' do
get '/latest?from=GBP&to=USD&amount=100' get '/current?from=GBP&to=USD&amount=100'
json['rates']['USD'].must_be :>, 100 json['rates']['USD'].must_be :>, 100
end end
it 'returns rates for a given period' do
get '/2010-01-01..2010-12-31'
json['start_date'].must_equal '2010-01-01'
json['end_date'].must_equal '2010-12-31'
json['rates'].wont_be empty
end
end end