frankfurter/spec/currency_spec.rb

229 lines
7.2 KiB
Ruby
Raw Permalink Normal View History

2026-05-21 01:38:14 -06:00
# frozen_string_literal: true
require_relative "helper"
require "currency"
describe Currency do
before do
Rate.dataset.delete
Rate.multi_insert([
{ provider: "ECB", date: Date.today, base: "EUR", quote: "USD", rate: 1.1 },
{ provider: "ECB", date: Date.today, base: "EUR", quote: "GBP", rate: 0.85 },
{ provider: "BOC", date: Date.today, base: "CAD", quote: "USD", rate: 0.74 },
{ provider: "ECB", date: Date.today - 365, base: "EUR", quote: "SEK", rate: 11.0 },
])
db = Sequel::Model.db
db[:currencies].delete
db.run(<<~SQL)
INSERT OR REPLACE INTO currencies (iso_code, start_date, end_date)
SELECT iso_code, MIN(start_date), MAX(end_date)
FROM (
SELECT quote AS iso_code, MIN(date) AS start_date, MAX(date) AS end_date
FROM rates GROUP BY quote
UNION ALL
SELECT base AS iso_code, MIN(date) AS start_date, MAX(date) AS end_date
FROM rates GROUP BY base
)
GROUP BY iso_code
ORDER BY iso_code
SQL
db[:currency_coverages].delete
db.run(<<~SQL)
INSERT OR REPLACE INTO currency_coverages (provider_key, iso_code, start_date, end_date)
SELECT provider, iso_code, MIN(date), MAX(date)
FROM (
SELECT provider, quote AS iso_code, date FROM rates
UNION ALL
SELECT provider, base AS iso_code, date FROM rates
)
GROUP BY provider, iso_code
ORDER BY provider, iso_code
SQL
end
it "lists all currencies" do
codes = Currency.all.map(&:iso_code).sort
_(codes).must_include("USD")
_(codes).must_include("EUR")
_(codes).must_include("CAD")
end
it "merges date ranges across quote and base" do
usd = Currency.find("USD")
_(usd).wont_be_nil
_(usd.start_date.to_s).must_equal(Date.today.to_s)
_(usd.end_date.to_s).must_equal(Date.today.to_s)
end
it "includes base currencies" do
eur = Currency.find("EUR")
_(eur).wont_be_nil
end
it "filters active currencies" do
active_codes = Currency.active.map(&:iso_code)
_(active_codes).must_include("USD")
_(active_codes).wont_include("SEK")
end
it "returns nil for unknown currency" do
_(Currency.find("XYZ")).must_be_nil
end
it "formats to hash" do
usd = Currency.find("USD")
_(usd.to_h[:name]).must_equal("United States Dollar")
_(usd.to_h[:symbol]).must_equal("$")
_(usd.to_h[:iso_numeric]).must_equal("840")
end
it "includes providers" do
usd = Currency.find("USD")
_(usd.providers).must_include("ECB")
_(usd.providers).must_include("BOC")
end
it "is case insensitive" do
_(Currency.find("usd")).wont_be_nil
end
it "filters by providers" do
codes = Currency.with_providers(["ECB"]).map(&:iso_code)
_(codes).must_include("USD")
_(codes).must_include("EUR")
_(codes).wont_include("CAD")
end
it "excludes pegged currencies when filtering by providers" do
codes = Currency.with_providers(["ECB"]).map(&:iso_code)
_(codes).wont_include("BMD")
end
it "includes pegged currencies in the list" do
codes = Currency.all.map(&:iso_code)
_(codes).must_include("BMD")
_(codes).must_include("FKP")
end
it "derives pegged currency date range from anchor" do
bmd = Currency.find("BMD")
_(bmd).wont_be_nil
usd = Currency.find("USD")
_(bmd.end_date.to_s).must_equal(usd.end_date.to_s)
end
it "uses later of peg since and anchor start_date" do
bmd = Currency.find("BMD")
usd = Currency.find("USD")
_(bmd.start_date.to_s).must_be(:>=, usd.start_date.to_s)
end
it "extends start_date back to peg start when provider data is newer" do
# AED is pegged to USD since 1997-11-02. Insert provider data starting
# much later, but anchor (USD) data going back further.
db = Sequel::Model.db
Rate.multi_insert([
{ provider: "ECB", date: "1990-01-02", base: "EUR", quote: "USD", rate: 1.0 },
{ provider: "TCMB", date: Date.today, base: "USD", quote: "AED", rate: 3.6725 },
])
db[:currencies].insert_conflict(:replace).insert(iso_code: "USD", start_date: "1990-01-02", end_date: Date.today.to_s)
db[:currencies].insert_conflict(:replace).insert(iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "TCMB", iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
aed = Currency.find("AED")
_(aed.start_date.to_s).must_equal("1997-11-02")
_(aed.peg).wont_be_nil
_(aed.providers).must_include("TCMB")
end
it "extends start_date in currency list for pegged currencies with provider data" do
db = Sequel::Model.db
Rate.multi_insert([
{ provider: "ECB", date: "1990-01-02", base: "EUR", quote: "USD", rate: 1.0 },
{ provider: "TCMB", date: Date.today, base: "USD", quote: "AED", rate: 3.6725 },
])
db[:currencies].insert_conflict(:replace).insert(iso_code: "USD", start_date: "1990-01-02", end_date: Date.today.to_s)
db[:currencies].insert_conflict(:replace).insert(iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "TCMB", iso_code: "AED", start_date: Date.today.to_s, end_date: Date.today.to_s)
aed = Currency.all.find { |c| c.iso_code == "AED" }
_(aed.start_date.to_s).must_equal("1997-11-02")
end
it "extends end_date for pegged currency with stale provider data" do
db = Sequel::Model.db
# ANG is pegged to USD. Insert a stale ANG row (end_date in the past)
# but USD is current. The peg should extend ANG's end_date to match USD.
db[:currencies].insert_conflict(:replace).insert(iso_code: "ANG", start_date: "1999-01-04", end_date: "2025-03-28")
db[:currency_coverages].insert_conflict(:replace).insert(provider_key: "BDI", iso_code: "ANG", start_date: "1999-01-04", end_date: "2025-03-28")
usd = Currency.find("USD")
# find
ang = Currency.find("ANG")
_(ang.end_date.to_s).must_equal(usd.end_date.to_s)
# all (scope=all)
ang_all = Currency.all.find { |c| c.iso_code == "ANG" }
_(ang_all.end_date.to_s).must_equal(usd.end_date.to_s)
# active
ang_active = Currency.active.find { |c| c.iso_code == "ANG" }
_(ang_active.end_date.to_s).must_equal(usd.end_date.to_s)
end
it "formats pegged currency to hash" do
bmd = Currency.find("BMD")
_(bmd.to_h[:iso_code]).must_equal("BMD")
_(bmd.to_h[:name]).must_equal("Bermudian Dollar")
_(bmd.to_h[:start_date]).wont_be_nil
_(bmd.to_h[:end_date]).wont_be_nil
end
it "returns peg metadata for pegged currencies" do
h = Currency.find("BMD").to_h_with_providers
_(h[:peg][:base]).must_equal("USD")
_(h[:peg][:rate]).must_equal(1.0)
_(h[:peg][:authority]).must_equal("Bermuda Monetary Authority")
end
it "returns providers for pegged currencies" do
h = Currency.find("BMD").to_h_with_providers
_(h[:providers]).must_be_kind_of(Array)
end
it "returns providers for non-pegged currencies" do
h = Currency.find("USD").to_h_with_providers
_(h[:providers]).must_be_kind_of(Array)
end
it "does not return peg metadata for non-pegged currencies" do
h = Currency.find("USD").to_h_with_providers
_(h).wont_include(:peg)
end
end