package client import ( "context" "testing" "time" fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" "google.golang.org/grpc" ) type stubOracle struct { latestResp *oraclev1.LatestRateResponse latestErr error quoteResp *oraclev1.GetQuoteResponse quoteErr error lastLatest *oraclev1.LatestRateRequest lastQuote *oraclev1.GetQuoteRequest } func (s *stubOracle) LatestRate(ctx context.Context, in *oraclev1.LatestRateRequest, _ ...grpc.CallOption) (*oraclev1.LatestRateResponse, error) { s.lastLatest = in return s.latestResp, s.latestErr } func (s *stubOracle) GetQuote(ctx context.Context, in *oraclev1.GetQuoteRequest, _ ...grpc.CallOption) (*oraclev1.GetQuoteResponse, error) { s.lastQuote = in return s.quoteResp, s.quoteErr } func TestLatestRate(t *testing.T) { expectedTime := time.Date(2024, 1, 1, 15, 0, 0, 0, time.UTC) stub := &stubOracle{ latestResp: &oraclev1.LatestRateResponse{ Rate: &oraclev1.RateSnapshot{ Pair: &fxv1.CurrencyPair{Base: "USD", Quote: "EUR"}, Mid: &moneyv1.Decimal{Value: "1.1000"}, Bid: &moneyv1.Decimal{Value: "1.0995"}, Ask: &moneyv1.Decimal{Value: "1.1005"}, SpreadBps: &moneyv1.Decimal{Value: "5"}, Provider: "ECB", RateRef: "ECB-20240101", AsofUnixMs: expectedTime.UnixMilli(), }, }, } client := NewWithClient(Config{}, stub) resp, err := client.LatestRate(context.Background(), LatestRateParams{ Meta: RequestMeta{ TenantRef: "tenant", OrganizationRef: "org", }, Pair: &fxv1.CurrencyPair{Base: "USD", Quote: "EUR"}, Provider: "ECB", }) if err != nil { t.Fatalf("LatestRate returned error: %v", err) } if stub.lastLatest.GetProvider() != "ECB" { t.Fatalf("expected provider to propagate, got %s", stub.lastLatest.GetProvider()) } if resp.Provider != "ECB" || resp.RateRef != "ECB-20240101" { t.Fatalf("unexpected response: %+v", resp) } if !resp.AsOf.Equal(expectedTime) { t.Fatalf("expected as-of %s, got %s", expectedTime, resp.AsOf) } } func TestGetQuote(t *testing.T) { expiresAt := time.Date(2024, 2, 2, 12, 0, 0, 0, time.UTC) stub := &stubOracle{ quoteResp: &oraclev1.GetQuoteResponse{ Quote: &oraclev1.Quote{ QuoteRef: "quote-123", Pair: &fxv1.CurrencyPair{Base: "GBP", Quote: "USD"}, Side: fxv1.Side_BUY_BASE_SELL_QUOTE, Price: &moneyv1.Decimal{Value: "1.2500"}, BaseAmount: &moneyv1.Money{Amount: "100.00", Currency: "GBP"}, QuoteAmount: &moneyv1.Money{Amount: "125.00", Currency: "USD"}, ExpiresAtUnixMs: expiresAt.UnixMilli(), Provider: "Test", RateRef: "test-ref", Firm: true, }, }, } client := NewWithClient(Config{}, stub) resp, err := client.GetQuote(context.Background(), GetQuoteParams{ Pair: &fxv1.CurrencyPair{Base: "GBP", Quote: "USD"}, Side: fxv1.Side_BUY_BASE_SELL_QUOTE, BaseAmount: &moneyv1.Money{Amount: "100.00", Currency: "GBP"}, Firm: true, TTL: 2 * time.Second, }) if err != nil { t.Fatalf("GetQuote returned error: %v", err) } if stub.lastQuote.GetFirm() != true { t.Fatalf("expected firm flag to propagate") } if stub.lastQuote.GetTtlMs() == 0 { t.Fatalf("expected ttl to be populated") } if resp.QuoteRef != "quote-123" || resp.Price != "1.2500" || !resp.ExpiresAt.Equal(expiresAt) { t.Fatalf("unexpected quote response: %+v", resp) } }