# frozen_string_literal: true require_relative "helper" require "blender" describe Blender do let(:date) { Date.parse("2024-01-15") } it "averages rates across providers" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "ECB" }, { date: date, base: "EUR", quote: "GBP", rate: 0.84, provider: "ECB" }, { date: date, base: "EUR", quote: "USD", rate: 1.10, provider: "BOC" }, { date: date, base: "EUR", quote: "GBP", rate: 0.86, provider: "BOC" }, ] result = Blender.new(rates, base: "EUR").blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:rate]).must_be_close_to((1.08 + 1.10) / 2.0) end it "handles mixed bases within a provider" do rates = [ { date: date, base: "USD", quote: "JPY", rate: 150.0, provider: "FRED" }, { date: date, base: "USD", quote: "CHF", rate: 0.88, provider: "FRED" }, { date: date, base: "EUR", quote: "USD", rate: 1.10, provider: "FRED" }, ] result = Blender.new(rates, base: "USD").blend jpy = result.find { |r| r[:quote] == "JPY" } eur = result.find { |r| r[:quote] == "EUR" } _(jpy[:rate]).must_be_close_to(150.0) _(eur[:rate]).must_be_close_to(1.0 / 1.10) end it "blends rates consistently across providers with different bases" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "ECB" }, { date: date, base: "USD", quote: "CAD", rate: 1.37, provider: "BOC" }, { date: date, base: "EUR", quote: "CAD", rate: 1.48, provider: "BOC" }, ] result = Blender.new(rates, base: "EUR").blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:rate]).must_be_close_to(1.08, 0.05) end it "blends rates equally within the grace period" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "ECB" }, { date: date - 1, base: "EUR", quote: "USD", rate: 1.10, provider: "BOC" }, ] result = Blender.new(rates, base: "EUR").blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:rate]).must_be_close_to((1.08 + 1.10) / 2.0) _(usd[:date]).must_equal(date) end it "discounts stale rates beyond the grace period" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "ECB" }, { date: date - 7, base: "EUR", quote: "USD", rate: 1.20, provider: "BOC" }, ] result = Blender.new(rates, base: "EUR").blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:rate]).must_be_close_to(1.08, 0.02) _(usd[:date]).must_equal(date) end it "excludes outliers from the blend" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "A" }, { date: date, base: "EUR", quote: "USD", rate: 1.09, provider: "B" }, { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "C" }, { date: date, base: "EUR", quote: "USD", rate: 9.99, provider: "D" }, ] blender = Blender.new(rates, base: "EUR") result = blender.blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:rate]).must_be_close_to(1.083, 0.01) end it "exposes contributing providers and marks outliers as excluded" do rates = [ { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "A" }, { date: date, base: "EUR", quote: "USD", rate: 1.09, provider: "B" }, { date: date, base: "EUR", quote: "USD", rate: 1.08, provider: "C" }, { date: date, base: "EUR", quote: "USD", rate: 9.99, provider: "D" }, ] result = Blender.new(rates, base: "EUR").blend usd = result.find { |r| r[:quote] == "USD" } _(usd[:providers]).must_equal([ { key: "A", rate: 1.08 }, { key: "B", rate: 1.09 }, { key: "C", rate: 1.08 }, { key: "D", rate: 9.99, excluded: true }, ]) end end