neq quotation definition + priced_at field
This commit is contained in:
@@ -48,6 +48,6 @@ require (
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/grpc v1.79.0 // indirect
|
||||
google.golang.org/grpc v1.79.1 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
)
|
||||
|
||||
@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA=
|
||||
google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -68,6 +68,7 @@ type Quote struct {
|
||||
BaseAmount *moneyv1.Money
|
||||
QuoteAmount *moneyv1.Money
|
||||
ExpiresAt time.Time
|
||||
PricedAt time.Time
|
||||
Provider string
|
||||
RateRef string
|
||||
Firm bool
|
||||
@@ -237,6 +238,10 @@ func fromProtoQuote(quote *oraclev1.Quote) *Quote {
|
||||
if quote == nil {
|
||||
return nil
|
||||
}
|
||||
pricedAt := time.Time{}
|
||||
if ts := quote.GetPricedAt(); ts != nil {
|
||||
pricedAt = ts.AsTime()
|
||||
}
|
||||
return &Quote{
|
||||
QuoteRef: quote.GetQuoteRef(),
|
||||
Pair: quote.Pair,
|
||||
@@ -245,6 +250,7 @@ func fromProtoQuote(quote *oraclev1.Quote) *Quote {
|
||||
BaseAmount: quote.BaseAmount,
|
||||
QuoteAmount: quote.QuoteAmount,
|
||||
ExpiresAt: time.UnixMilli(quote.GetExpiresAtUnixMs()),
|
||||
PricedAt: pricedAt,
|
||||
Provider: quote.GetProvider(),
|
||||
RateRef: quote.GetRateRef(),
|
||||
Firm: quote.GetFirm(),
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type stubOracle struct {
|
||||
@@ -75,6 +76,7 @@ func TestLatestRate(t *testing.T) {
|
||||
|
||||
func TestGetQuote(t *testing.T) {
|
||||
expiresAt := time.Date(2024, 2, 2, 12, 0, 0, 0, time.UTC)
|
||||
pricedAt := time.Date(2024, 2, 2, 11, 59, 0, 0, time.UTC)
|
||||
stub := &stubOracle{
|
||||
quoteResp: &oraclev1.GetQuoteResponse{
|
||||
Quote: &oraclev1.Quote{
|
||||
@@ -85,6 +87,7 @@ func TestGetQuote(t *testing.T) {
|
||||
BaseAmount: &moneyv1.Money{Amount: "100.00", Currency: "GBP"},
|
||||
QuoteAmount: &moneyv1.Money{Amount: "125.00", Currency: "USD"},
|
||||
ExpiresAtUnixMs: expiresAt.UnixMilli(),
|
||||
PricedAt: timestamppb.New(pricedAt),
|
||||
Provider: "Test",
|
||||
RateRef: "test-ref",
|
||||
Firm: true,
|
||||
@@ -113,4 +116,7 @@ func TestGetQuote(t *testing.T) {
|
||||
if resp.QuoteRef != "quote-123" || resp.Price != "1.2500" || !resp.ExpiresAt.Equal(expiresAt) {
|
||||
t.Fatalf("unexpected quote response: %+v", resp)
|
||||
}
|
||||
if !resp.PricedAt.Equal(pricedAt) {
|
||||
t.Fatalf("expected priced_at %s, got %s", pricedAt, resp.PricedAt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ require (
|
||||
github.com/tech/sendico/pkg v0.1.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
go.uber.org/zap v1.27.1
|
||||
google.golang.org/grpc v1.79.0
|
||||
google.golang.org/grpc v1.79.1
|
||||
google.golang.org/protobuf v1.36.11
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.79.0 h1:6/+EFlxsMyoSbHbBoEDx94n/Ycx/bi0IhJ5Qh7b7LaA=
|
||||
google.golang.org/grpc v1.79.0/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY=
|
||||
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -130,6 +130,10 @@ func (qc *quoteComputation) buildModelQuote(firm bool, expiryMillis int64, req *
|
||||
if qc.baseRounded == nil || qc.quoteRounded == nil || qc.priceRounded == nil {
|
||||
return nil, merrors.Internal("oracle: computation not executed")
|
||||
}
|
||||
pricedAtUnixMs := qc.rate.AsOfUnixMs
|
||||
if pricedAtUnixMs <= 0 {
|
||||
pricedAtUnixMs = time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
quote := &model.Quote{
|
||||
QuoteRef: uuid.NewString(),
|
||||
@@ -147,6 +151,7 @@ func (qc *quoteComputation) buildModelQuote(firm bool, expiryMillis int64, req *
|
||||
Amount: formatRat(qc.quoteRounded, qc.quoteScale),
|
||||
},
|
||||
AmountType: qc.amountType,
|
||||
PricedAtUnixMs: pricedAtUnixMs,
|
||||
RateRef: qc.rate.RateRef,
|
||||
Provider: qc.provider,
|
||||
PreferredProvider: req.GetPreferredProvider(),
|
||||
|
||||
@@ -111,6 +111,7 @@ func (currencyStoreStub) List(ctx context.Context, codes ...string) ([]*model.Cu
|
||||
func (currencyStoreStub) Upsert(ctx context.Context, currency *model.Currency) error { return nil }
|
||||
|
||||
func TestServiceGetQuoteFirm(t *testing.T) {
|
||||
pricedAt := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||
repo := &repositoryStub{}
|
||||
repo.pairs = &pairStoreStub{
|
||||
getFn: func(ctx context.Context, pair model.CurrencyPair) (*model.Pair, error) {
|
||||
@@ -129,7 +130,7 @@ func TestServiceGetQuoteFirm(t *testing.T) {
|
||||
Ask: "1.10",
|
||||
Bid: "1.08",
|
||||
RateRef: "rate#1",
|
||||
AsOfUnixMs: time.Now().UnixMilli(),
|
||||
AsOfUnixMs: pricedAt.UnixMilli(),
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
@@ -169,9 +170,15 @@ func TestServiceGetQuoteFirm(t *testing.T) {
|
||||
if resp.GetQuote().GetQuoteAmount().GetAmount() != "110.00" {
|
||||
t.Fatalf("unexpected quote amount: %s", resp.GetQuote().GetQuoteAmount().GetAmount())
|
||||
}
|
||||
if got := resp.GetQuote().GetPricedAt(); got == nil || !got.AsTime().Equal(pricedAt) {
|
||||
t.Fatalf("expected priced_at %s, got %v", pricedAt, got)
|
||||
}
|
||||
if savedQuote.QuoteRef == "" {
|
||||
t.Fatalf("expected quote persisted")
|
||||
}
|
||||
if savedQuote.PricedAtUnixMs != pricedAt.UnixMilli() {
|
||||
t.Fatalf("expected stored pricedAtUnixMs %d, got %d", pricedAt.UnixMilli(), savedQuote.PricedAtUnixMs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceGetQuoteRateNotFound(t *testing.T) {
|
||||
|
||||
@@ -2,12 +2,14 @@ package oracle
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/fx/storage/model"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
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/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
func buildResponseMeta(meta *oraclev1.RequestMeta) *oraclev1.ResponseMeta {
|
||||
@@ -36,6 +38,7 @@ func quoteModelToProto(q *model.Quote) *oraclev1.Quote {
|
||||
BaseAmount: moneyModelToProto(&q.BaseAmount),
|
||||
QuoteAmount: moneyModelToProto(&q.QuoteAmount),
|
||||
ExpiresAtUnixMs: q.ExpiresAtUnixMs,
|
||||
PricedAt: timestampFromUnixMillis(q.PricedAtUnixMs, q.CreatedAt),
|
||||
Provider: q.Provider,
|
||||
RateRef: q.RateRef,
|
||||
Firm: q.Firm,
|
||||
@@ -117,3 +120,13 @@ func decimalStringToProto(value string) *moneyv1.Decimal {
|
||||
}
|
||||
return &moneyv1.Decimal{Value: value}
|
||||
}
|
||||
|
||||
func timestampFromUnixMillis(ms int64, fallback time.Time) *timestamppb.Timestamp {
|
||||
if ms > 0 {
|
||||
return timestamppb.New(time.UnixMilli(ms).UTC())
|
||||
}
|
||||
if !fallback.IsZero() {
|
||||
return timestamppb.New(fallback.UTC())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ type Quote struct {
|
||||
QuoteAmount paymenttypes.Money `bson:"quoteAmount" json:"quoteAmount"`
|
||||
AmountType QuoteAmountType `bson:"amountType" json:"amountType"`
|
||||
ExpiresAtUnixMs int64 `bson:"expiresAtUnixMs" json:"expiresAtUnixMs"`
|
||||
PricedAtUnixMs int64 `bson:"pricedAtUnixMs,omitempty" json:"pricedAtUnixMs,omitempty"`
|
||||
ExpiresAt *time.Time `bson:"expiresAt,omitempty" json:"expiresAt,omitempty"`
|
||||
RateRef string `bson:"rateRef" json:"rateRef"`
|
||||
Provider string `bson:"provider" json:"provider"`
|
||||
|
||||
@@ -95,6 +95,9 @@ func (q *quotesStore) Issue(ctx context.Context, quote *model.Quote) error {
|
||||
expiry := time.UnixMilli(quote.ExpiresAtUnixMs)
|
||||
quote.ExpiresAt = &expiry
|
||||
}
|
||||
if quote.PricedAtUnixMs <= 0 {
|
||||
quote.PricedAtUnixMs = time.Now().UnixMilli()
|
||||
}
|
||||
|
||||
quote.Status = model.QuoteStatusIssued
|
||||
quote.ConsumedByLedgerTxnRef = ""
|
||||
|
||||
@@ -32,6 +32,9 @@ func TestQuotesStoreIssue(t *testing.T) {
|
||||
if inserted == nil || inserted.Status != model.QuoteStatusIssued {
|
||||
t.Fatalf("expected issued quote to be inserted")
|
||||
}
|
||||
if inserted.PricedAtUnixMs <= 0 {
|
||||
t.Fatalf("expected pricedAtUnixMs to be populated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuotesStoreIssueSetsExpiryDate(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user