Files
zeekay 7d727874d5 chore: rebrand Formance Payments -> Hanzo Payments (hanzo-fi)
- go.mod module path + all self-imports: github.com/formancehq/payments -> github.com/hanzo-fi/payments
  (1590 .go files / 3152 refs; 20 modules: root, pkg/client, pkg/domain, 18 ce/plugins).
  External dep kept: formancehq/go-libs.
- plugins: each ce/plugins/* consumed a *published* pkg/domain; added local
  'replace .../pkg/domain => ../../../pkg/domain' (consistent with root) so every
  standalone plugin module builds in-monorepo. compile-plugin.sh MOD var rebranded too.
- branding: README, LICENSE (dual MIT+EE; upstream copyright retained + Hanzo added),
  goreleaser (release images -> registry.hanzo.ai/hanzo-fi, OCI labels, homepage),
  proto go_package, renovate, speakeasy gen.yaml, connector-template *.gotpl.
- CI: removed Numary/Formance workflows + composite action; added canonical hanzo.yml +
  cicd.yml (hanzoai/ci, gated HANZO_CI_ENABLED) + secretless ci.yml (go build/vet).
- kept as data/compat keys: wire capability enums ALLOW_FORMANCE_*, generated SDK type
  client.Formance, domain field FormanceRedirectURL, OTEL_SERVICE_NAME, upstream base image.
- verified: go build ./... (default + -tags ee) + all 20 nested modules green.
2026-07-05 13:08:55 -07:00

193 lines
6.3 KiB
Go

package mappers
import (
"encoding/json"
"testing"
"github.com/hanzo-fi/payments/ee/plugins/bitstamp/client"
"github.com/hanzo-fi/payments/pkg/domain/models"
)
func TestSplitCurrencyPair(t *testing.T) {
t.Parallel()
cases := []struct {
in string
base string
quote string
err bool
}{
{"btcusd", "BTC", "USD", false},
{"btcusdc", "BTC", "USDC", false},
{"BTC/USD", "BTC", "USD", false},
{"", "", "", true},
{"toolongtoknow", "", "", true},
}
for _, tc := range cases {
tc := tc
t.Run(tc.in, func(t *testing.T) {
t.Parallel()
b, q, err := SplitCurrencyPair(tc.in)
if (err != nil) != tc.err {
t.Fatalf("err: %v wantErr=%v", err, tc.err)
}
if !tc.err && (b != tc.base || q != tc.quote) {
t.Errorf("got (%s, %s), want (%s, %s)", b, q, tc.base, tc.quote)
}
})
}
}
// TestAccountOrderAccountReference verifies that AccountOrderDataEventToPSPOrder
// always returns an order and routes accountReference to SourceAccountReference
// when it is the spender, or DestinationAccountReference otherwise.
func TestAccountOrderAccountReference(t *testing.T) {
t.Parallel()
currencies := map[string]int{"BTC": 8, "USD": 2}
makeEvent := func(orderType int) client.AccountOrderDataEvent {
return client.AccountOrderDataEvent{
Event: "order_created",
EventID: "a1b2c3d4-e5f6-a1b2-c3d4-e5f6a1b2c3d4",
Data: client.AccountOrderDataItem{
IDStr: "1000",
OrderType: orderType,
Amount: json.Number("0.001"),
AmountAtCreate: "0.001",
AmountTraded: "0",
AmountStr: "0.001",
PriceStr: "50000",
Microtimestamp: "1779709892000000",
},
}
}
// BUY spends quote (USD): accountReference=USD → srcAccount=&USD, dstAccount=nil.
order, err := AccountOrderDataEventToPSPOrder(currencies, "USD", "BTC/USD", makeEvent(0))
if err != nil {
t.Fatalf("BUY/USD: unexpected error: %v", err)
}
if order == nil {
t.Fatal("BUY/USD: expected order, got nil")
}
if order.SourceAccountReference == nil || *order.SourceAccountReference != "USD" {
t.Errorf("BUY/USD: SourceAccountReference = %v, want &USD", order.SourceAccountReference)
}
if order.DestinationAccountReference != nil {
t.Errorf("BUY/USD: DestinationAccountReference = %v, want nil", order.DestinationAccountReference)
}
// BUY spends quote (USD): accountReference=BTC (the destination) → srcAccount=nil, dstAccount=&BTC.
order, err = AccountOrderDataEventToPSPOrder(currencies, "BTC", "BTC/USD", makeEvent(0))
if err != nil {
t.Fatalf("BUY/BTC: unexpected error: %v", err)
}
if order == nil {
t.Fatal("BUY/BTC: expected order, got nil")
}
if order.SourceAccountReference != nil {
t.Errorf("BUY/BTC: SourceAccountReference = %v, want nil", order.SourceAccountReference)
}
if order.DestinationAccountReference == nil || *order.DestinationAccountReference != "BTC" {
t.Errorf("BUY/BTC: DestinationAccountReference = %v, want &BTC", order.DestinationAccountReference)
}
// SELL spends base (BTC): accountReference=BTC → srcAccount=&BTC, dstAccount=nil.
order, err = AccountOrderDataEventToPSPOrder(currencies, "BTC", "BTC/USD", makeEvent(1))
if err != nil {
t.Fatalf("SELL/BTC: unexpected error: %v", err)
}
if order == nil {
t.Fatal("SELL/BTC: expected order, got nil")
}
if order.SourceAccountReference == nil || *order.SourceAccountReference != "BTC" {
t.Errorf("SELL/BTC: SourceAccountReference = %v, want &BTC", order.SourceAccountReference)
}
if order.DestinationAccountReference != nil {
t.Errorf("SELL/BTC: DestinationAccountReference = %v, want nil", order.DestinationAccountReference)
}
// SELL spends base (BTC): accountReference=USD (the destination) → srcAccount=nil, dstAccount=&USD.
order, err = AccountOrderDataEventToPSPOrder(currencies, "USD", "BTC/USD", makeEvent(1))
if err != nil {
t.Fatalf("SELL/USD: unexpected error: %v", err)
}
if order == nil {
t.Fatal("SELL/USD: expected order, got nil")
}
if order.SourceAccountReference != nil {
t.Errorf("SELL/USD: SourceAccountReference = %v, want nil", order.SourceAccountReference)
}
if order.DestinationAccountReference == nil || *order.DestinationAccountReference != "USD" {
t.Errorf("SELL/USD: DestinationAccountReference = %v, want &USD", order.DestinationAccountReference)
}
}
func TestResolveOrderAccounts(t *testing.T) {
t.Parallel()
ptrEq := func(p *string, want string) bool { return p != nil && *p == want }
cases := []struct {
name string
direction models.OrderDirection
base, quote, ref string
wantSrcAccount string // "" means expect nil
wantDstAccount string // "" means expect nil
wantSrcCurrency string
wantDstCurrency string
}{
{
name: "BUY ref=quote (spender)",
direction: models.ORDER_DIRECTION_BUY, base: "BTC", quote: "USD", ref: "USD",
wantSrcAccount: "USD", wantSrcCurrency: "USD", wantDstCurrency: "BTC",
},
{
name: "BUY ref=base (receiver)",
direction: models.ORDER_DIRECTION_BUY, base: "BTC", quote: "USD", ref: "BTC",
wantDstAccount: "BTC", wantSrcCurrency: "USD", wantDstCurrency: "BTC",
},
{
name: "SELL ref=base (spender)",
direction: models.ORDER_DIRECTION_SELL, base: "BTC", quote: "USD", ref: "BTC",
wantSrcAccount: "BTC", wantSrcCurrency: "BTC", wantDstCurrency: "USD",
},
{
name: "SELL ref=quote (receiver)",
direction: models.ORDER_DIRECTION_SELL, base: "BTC", quote: "USD", ref: "USD",
wantDstAccount: "USD", wantSrcCurrency: "BTC", wantDstCurrency: "USD",
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
srcAccount, dstAccount, srcCurrency, dstCurrency := ResolveOrderAccounts(tc.direction, tc.base, tc.quote, tc.ref)
if tc.wantSrcAccount != "" {
if !ptrEq(srcAccount, tc.wantSrcAccount) {
t.Errorf("srcAccount = %v, want &%s", srcAccount, tc.wantSrcAccount)
}
} else if srcAccount != nil {
t.Errorf("srcAccount = &%s, want nil", *srcAccount)
}
if tc.wantDstAccount != "" {
if !ptrEq(dstAccount, tc.wantDstAccount) {
t.Errorf("dstAccount = %v, want &%s", dstAccount, tc.wantDstAccount)
}
} else if dstAccount != nil {
t.Errorf("dstAccount = &%s, want nil", *dstAccount)
}
if srcCurrency != tc.wantSrcCurrency {
t.Errorf("srcCurrency = %q, want %q", srcCurrency, tc.wantSrcCurrency)
}
if dstCurrency != tc.wantDstCurrency {
t.Errorf("dstCurrency = %q, want %q", dstCurrency, tc.wantDstCurrency)
}
})
}
}