Files

187 lines
5.9 KiB
Go

package bitstamp
import (
"errors"
"github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
"github.com/hanzo-fi/payments/ee/plugins/bitstamp/client"
"github.com/hanzo-fi/payments/ee/plugins/bitstamp/mappers"
"github.com/hanzo-fi/payments/pkg/domain/plugins"
"github.com/hanzo-fi/payments/pkg/domain/models"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)
var _ = Describe("Bitstamp Plugin Accounts", func() {
var (
ctrl *gomock.Controller
m *client.MockClient
plg *Plugin
)
BeforeEach(func() {
ctrl = gomock.NewController(GinkgoT())
m = client.NewMockClient(ctrl)
plg = &Plugin{
Plugin: plugins.NewBasePlugin(),
client: m,
logger: logging.NewDefaultLogger(GinkgoWriter, true, false, false),
}
// Enrichment caches are refreshed on every FetchNextAccounts cycle;
// default each install-time call to a non-empty but cheap response.
// Per-test cases can override with explicit expectations.
m.EXPECT().GetMarkets(gomock.Any()).Return(nil, nil).AnyTimes()
m.EXPECT().GetMyMarkets(gomock.Any()).Return(nil, nil).AnyTimes()
m.EXPECT().GetTradingFees(gomock.Any()).Return(nil, nil).AnyTimes()
m.EXPECT().GetWithdrawalFees(gomock.Any()).Return(nil, nil).AnyTimes()
// currenciesIndex re-fetches full Currency objects on each cycle.
m.EXPECT().GetCurrencies(gomock.Any()).Return([]client.Currency{
{Currency: "btc", Decimals: 8},
{Currency: "usd", Decimals: 2},
{Currency: "eur", Decimals: 2},
{Currency: "eth", Decimals: 18},
}, nil).AnyTimes()
})
AfterEach(func() {
ctrl.Finish()
})
Context("fetching next accounts", func() {
It("should return an error - get balances error", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
nil,
errors.New("test error"),
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).ToNot(BeNil())
// Orchestrator wraps client errors with a single line of context.
Expect(err.Error()).To(ContainSubstring("test error"))
Expect(err.Error()).To(ContainSubstring("fetch accounts"))
Expect(resp).To(Equal(models.FetchNextAccountsResponse{}))
})
It("should fetch accounts successfully", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
[]client.AccountBalance{
{Currency: "btc", Total: "1.50000000", Available: "1.00000000", Reserved: "0.50000000"},
{Currency: "usd", Total: "5000.00", Available: "4500.00", Reserved: "500.00"},
{Currency: "eth", Total: "10.000000000000000000", Available: "10.000000000000000000", Reserved: "0"},
},
nil,
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).To(BeNil())
Expect(resp.Accounts).To(HaveLen(3))
Expect(resp.HasMore).To(BeFalse())
// Verify BTC account
Expect(resp.Accounts[0].Reference).To(Equal("BTC"))
Expect(resp.Accounts[0].CreatedAt).To(Equal(mappers.BitstampGenesis))
Expect(resp.Accounts[0].Name).NotTo(BeNil())
Expect(*resp.Accounts[0].Name).To(Equal("BTC"))
Expect(*resp.Accounts[0].DefaultAsset).To(Equal("BTC/8"))
// Verify USD account
Expect(resp.Accounts[1].Reference).To(Equal("USD"))
Expect(*resp.Accounts[1].DefaultAsset).To(Equal("USD/2"))
// Verify ETH account
Expect(resp.Accounts[2].Reference).To(Equal("ETH"))
Expect(*resp.Accounts[2].DefaultAsset).To(Equal("ETH/18"))
})
It("should skip zero-balance currencies", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
[]client.AccountBalance{
{Currency: "btc", Total: "1.00000000", Available: "1.00000000", Reserved: "0"},
{Currency: "usd", Total: "0", Available: "0", Reserved: "0"},
},
nil,
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).To(BeNil())
Expect(resp.Accounts).To(HaveLen(1))
Expect(resp.Accounts[0].Reference).To(Equal("BTC"))
})
It("should keep unsupported currencies without a default asset", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
[]client.AccountBalance{
{Currency: "unknown_coin", Total: "100", Available: "100", Reserved: "0"},
{Currency: "btc", Total: "1.00000000", Available: "1.00000000", Reserved: "0"},
},
nil,
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).To(BeNil())
Expect(resp.Accounts).To(HaveLen(2))
Expect(resp.Accounts[0].Reference).To(Equal("UNKNOWN_COIN"))
Expect(resp.Accounts[0].DefaultAsset).To(BeNil())
Expect(resp.Accounts[1].Reference).To(Equal("BTC"))
})
It("should handle empty response", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
[]client.AccountBalance{},
nil,
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).To(BeNil())
Expect(resp.Accounts).To(HaveLen(0))
Expect(resp.HasMore).To(BeFalse())
})
It("should return empty accounts when all currencies were already seen", func(ctx SpecContext) {
req := models.FetchNextAccountsRequest{
State: []byte(`{"accountCurrenciesImportedAt":{"BTC":"2025-01-01 00:00:00 +0000 UTC","USD":"2025-01-01 00:00:00 +0000 UTC"}}`),
PageSize: 10,
}
m.EXPECT().GetAccountBalances(gomock.Any()).Return(
[]client.AccountBalance{
{Currency: "btc", Total: "1.50000000", Available: "1.00000000", Reserved: "0.50000000"},
{Currency: "usd", Total: "5000.00", Available: "4500.00", Reserved: "500.00"},
},
nil,
)
resp, err := plg.FetchNextAccounts(ctx, req)
Expect(err).To(BeNil())
Expect(resp.Accounts).To(BeEmpty())
Expect(resp.HasMore).To(BeFalse())
})
})
})