Files
bank/exchange_test.go
zeekayandHanzo Dev 6099f82d56 bank: live-mode exchange via forexd + Playwright e2e for the sandbox loop
exchangeRate() sources the price by mode in one place: sandbox keeps
the deterministic reference tables; live deals through forexd at its
canonical GET /v1/{provider}/rate/{base}/{quote} (provider from
FOREX_PROVIDER, default kraken — our spot crypto+fiat backend) and
503s when FOREX_SERVICE_URL is unset, never falling back to sandbox
prices. Both exchange handlers now call it; the ledger mechanics are
unchanged.

Adds a real Playwright suite (app/dash/e2e) that boots bankd + the
dash and drives the loop end to end — membership ladder from live
/plans, demo login, testnet faucet, crypto send with txHash,
invalid-address rejection. 7 tests, green from a cold boot; one
worker since the sandbox is a shared ledger. pnpm test:e2e.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-21 13:09:54 -07:00

57 lines
1.8 KiB
Go

package bank
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
)
func TestExchangeRateSandbox(t *testing.T) {
t.Setenv("BANK_SANDBOX", "true")
// Sandbox prices off the local tables — identical to convertMinor.
wantMinor, wantRate := convertMinor(100_00, "USD", "EUR")
toMinor, rate, err := exchangeRate(context.Background(), "USD", "EUR", 100_00)
if err != nil {
t.Fatalf("exchangeRate returned error: %v", err)
}
if toMinor != wantMinor || rate != wantRate {
t.Errorf("exchangeRate = (%d, %f), want (%d, %f)", toMinor, rate, wantMinor, wantRate)
}
}
func TestExchangeRateLiveUnconfigured(t *testing.T) {
t.Setenv("BANK_SANDBOX", "false")
t.Setenv("FOREX_SERVICE_URL", "")
// No forexd, no price — never a silent fall back to the sandbox tables.
if _, _, err := exchangeRate(context.Background(), "USD", "EUR", 100_00); err == nil {
t.Error("exchangeRate = nil error with no FOREX_SERVICE_URL, want error")
}
}
func TestExchangeRateLive(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Canonical forexd contract: GET /v1/{provider}/rate/{base}/{quote}.
if r.URL.Path != "/v1/kraken/rate/USD/EUR" {
t.Errorf("forexd path = %q, want /v1/kraken/rate/USD/EUR", r.URL.Path)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"base_currency": "USD", "quote_currency": "EUR",
"rate": 0.90, "inverse_rate": 1.1111,
})
}))
defer srv.Close()
t.Setenv("BANK_SANDBOX", "false")
t.Setenv("FOREX_SERVICE_URL", srv.URL)
// $100.00 at 0.90 → €90.00, both 2 dp.
toMinor, rate, err := exchangeRate(context.Background(), "USD", "EUR", 100_00)
if err != nil {
t.Fatalf("exchangeRate returned error: %v", err)
}
if toMinor != 90_00 || rate != 0.90 {
t.Errorf("exchangeRate = (%d, %f), want (9000, 0.90)", toMinor, rate)
}
}