Files
payments/test/e2e/api_bank_accounts_test.go

323 lines
11 KiB
Go

//go:build it
package test_suite
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
"github.com/hanzo-fi/go-libs/v5/pkg/testing/deferred"
"github.com/hanzo-fi/go-libs/v5/pkg/types/pointer"
internalEvents "github.com/hanzo-fi/payments/internal/events"
"github.com/hanzo-fi/payments/pkg/domain/models"
"github.com/hanzo-fi/payments/pkg/client/models/components"
"github.com/hanzo-fi/payments/pkg/events"
"github.com/google/uuid"
. "github.com/hanzo-fi/payments/pkg/testserver"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Context("Payments API Bank Accounts", Ordered, Serial, func() {
var (
db = UseTemplatedDatabase()
ctx = logging.TestingContext()
accountNumber = "123456789"
iban = "DE89370400440532013000"
v3CreateRequest *components.V3CreateBankAccountRequest
v2CreateRequest components.BankAccountRequest
app *deferred.Deferred[*Server]
)
app = NewTestServer(func() Configuration {
return Configuration{
Stack: stack,
NatsURL: natsServer.GetValue().ClientURL(),
PostgresConfiguration: db.GetValue().ConnectionOptions(),
TemporalNamespace: temporalServer.GetValue().DefaultNamespace(),
TemporalAddress: temporalServer.GetValue().Address(),
Output: GinkgoWriter,
SkipOutboxScheduleCreation: true,
}
})
AfterEach(func() {
flushRemainingWorkflows(ctx)
})
v3CreateRequest = &components.V3CreateBankAccountRequest{
Name: "foo",
AccountNumber: &accountNumber,
Iban: &iban,
Country: pointer.For("DE"),
}
v2CreateRequest = components.BankAccountRequest{
Name: "foo",
AccountNumber: &accountNumber,
Iban: &iban,
Country: "DE",
}
When("creating a new bank account with v3", func() {
var (
bankAccountID string
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V3.CreateBankAccount(ctx, v3CreateRequest)
Expect(err).To(BeNil())
bankAccountID = createResponse.GetV3CreateBankAccountResponse().Data
})
It("should be ok", func() {
id, err := uuid.Parse(bankAccountID)
Expect(err).To(BeNil())
getResponse, err := app.GetValue().SDK().Payments.V3.GetBankAccount(ctx, bankAccountID)
Expect(err).To(BeNil())
Expect(getResponse.GetV3GetBankAccountResponse().Data.ID).To(Equal(id.String()))
})
})
When("creating a new bank account with v2", func() {
var (
bankAccountID string
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V1.CreateBankAccount(ctx, v2CreateRequest)
Expect(err).To(BeNil())
bankAccountID = createResponse.GetBankAccountResponse().Data.ID
})
It("should be ok", func() {
id, err := uuid.Parse(bankAccountID)
Expect(err).To(BeNil())
getResponse, err := app.GetValue().SDK().Payments.V1.GetBankAccount(ctx, bankAccountID)
Expect(err).To(BeNil())
Expect(getResponse.GetBankAccountResponse().Data.ID).To(Equal(id.String()))
})
})
When("forwarding a bank account to a connector with v3", func() {
var (
connectorID string
id uuid.UUID
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V3.CreateBankAccount(ctx, v3CreateRequest)
Expect(err).To(BeNil())
id, err = uuid.Parse(createResponse.GetV3CreateBankAccountResponse().Data)
Expect(err).To(BeNil())
connectorID, err = installConnector(ctx, app.GetValue(), uuid.New(), 3)
Expect(err).To(BeNil())
})
AfterEach(func() {
uninstallConnector(ctx, app.GetValue(), connectorID)
})
It("should fail when connector ID is invalid", func() {
_, err := app.GetValue().SDK().Payments.V3.ForwardBankAccount(ctx, id.String(), &components.V3ForwardBankAccountRequest{
ConnectorID: "invalid",
})
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("400"))
})
It("should be ok when connector is installed", func() {
forwardResponse, err := app.GetValue().SDK().Payments.V3.ForwardBankAccount(ctx, id.String(), &components.V3ForwardBankAccountRequest{
ConnectorID: connectorID,
})
Expect(err).To(BeNil())
taskID, err := models.TaskIDFromString(forwardResponse.GetV3ForwardBankAccountResponse().Data.TaskID)
Expect(err).To(BeNil())
Expect(taskID.Reference).To(ContainSubstring(id.String()))
cID := models.MustConnectorIDFromString(connectorID)
Expect(taskID.Reference).To(ContainSubstring(cID.Reference.String()))
p := waitSavedBankAccountPayloadForConnector(ctx, app.GetValue(), id.String(), connectorID)
getResponse, err := app.GetValue().SDK().Payments.V3.GetBankAccount(ctx, id.String())
Expect(err).To(BeNil())
ba := getResponse.GetV3GetBankAccountResponse().Data
assertSavedPayloadMatchesBankAccount(p, &ba, v3CreateRequest.Name, connectorID)
})
})
When("forwarding a bank account to a connector with v2", func() {
var (
connectorID string
id uuid.UUID
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V1.CreateBankAccount(ctx, v2CreateRequest)
Expect(err).To(BeNil())
id, err = uuid.Parse(createResponse.GetBankAccountResponse().Data.ID)
Expect(err).To(BeNil())
connectorID, err = installConnector(ctx, app.GetValue(), uuid.New(), 2)
Expect(err).To(BeNil())
})
AfterEach(func() {
uninstallConnector(ctx, app.GetValue(), connectorID)
})
It("should fail when connector ID is invalid", func() {
_, err := app.GetValue().SDK().Payments.V1.ForwardBankAccount(ctx, id.String(), components.ForwardBankAccountRequest{
ConnectorID: "invalid",
})
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("400"))
})
It("should be ok", func() {
_, err := app.GetValue().SDK().Payments.V1.ForwardBankAccount(ctx, id.String(), components.ForwardBankAccountRequest{
ConnectorID: connectorID,
})
Expect(err).To(BeNil())
p := waitSavedBankAccountPayloadForConnector(ctx, app.GetValue(), id.String(), connectorID)
getResponse, err := app.GetValue().SDK().Payments.V1.GetBankAccount(ctx, id.String())
Expect(err).To(BeNil())
ba := getResponse.GetBankAccountResponse().Data
assertSavedPayloadMatchesBankAccount(p, &ba, v2CreateRequest.Name, connectorID)
})
})
When("updating bank account metadata with v3", func() {
var (
id uuid.UUID
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V3.CreateBankAccount(ctx, v3CreateRequest)
Expect(err).To(BeNil())
id, err = uuid.Parse(createResponse.GetV3CreateBankAccountResponse().Data)
Expect(err).To(BeNil())
})
It("should fail when metadata is invalid", func() {
_, err := app.GetValue().SDK().Payments.V3.UpdateBankAccountMetadata(ctx, id.String(), &components.V3UpdateBankAccountMetadataRequest{})
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("400"))
})
It("should be ok when metadata is valid", func() {
metadata := map[string]string{"key": "val"}
_, err := app.GetValue().SDK().Payments.V3.UpdateBankAccountMetadata(ctx, id.String(), &components.V3UpdateBankAccountMetadataRequest{
Metadata: metadata,
})
Expect(err).To(BeNil())
getResponse, err := app.GetValue().SDK().Payments.V3.GetBankAccount(ctx, id.String())
Expect(err).To(BeNil())
Expect(getResponse.GetV3GetBankAccountResponse().Data.ID).To(Equal(id.String()))
Expect(getResponse.GetV3GetBankAccountResponse().Data.Metadata).To(Equal(metadata))
})
})
When("updating bank account metadata with v2", func() {
var (
id uuid.UUID
)
BeforeEach(func() {
createResponse, err := app.GetValue().SDK().Payments.V1.CreateBankAccount(ctx, v2CreateRequest)
Expect(err).To(BeNil())
id, err = uuid.Parse(createResponse.GetBankAccountResponse().Data.ID)
Expect(err).To(BeNil())
})
It("should fail when metadata is invalid", func() {
_, err := app.GetValue().SDK().Payments.V1.UpdateBankAccountMetadata(ctx, id.String(), components.UpdateBankAccountMetadataRequest{})
Expect(err).NotTo(BeNil())
Expect(err.Error()).To(ContainSubstring("400"))
})
It("should be ok when metadata is valid", func() {
metadata := map[string]string{"key": "val"}
_, err := app.GetValue().SDK().Payments.V1.UpdateBankAccountMetadata(ctx, id.String(), components.UpdateBankAccountMetadataRequest{
Metadata: metadata,
})
Expect(err).To(BeNil())
getResponse, err := app.GetValue().SDK().Payments.V1.GetBankAccount(ctx, id.String())
Expect(err).To(BeNil())
Expect(getResponse.GetBankAccountResponse().Data.ID).To(Equal(id.String()))
Expect(getResponse.GetBankAccountResponse().Data.Metadata).To(Equal(metadata))
})
})
})
// test helpers shared across v2/v3 forwarding scenarios
func waitSavedBankAccountPayloadForConnector(ctx context.Context, s *Server, bankAccountID, connectorID string) internalEvents.BankAccountMessagePayload {
var ret internalEvents.BankAccountMessagePayload
Eventually(func(g Gomega) bool {
payloads, err := LoadOutboxPayloadsByType(ctx, s, events.EventTypeSavedBankAccount)
g.Expect(err).To(BeNil())
for _, raw := range payloads {
var tmp internalEvents.BankAccountMessagePayload
if json.Unmarshal(raw, &tmp) == nil && tmp.ID == bankAccountID {
for _, x := range tmp.RelatedAccounts {
if x.ConnectorID == connectorID {
ret = tmp
return true
}
}
}
}
return false
}).WithTimeout(5 * time.Second).Should(BeTrue())
return ret
}
// bankAccountLike abstracts both V1 and V3 bank account SDK models we need for assertions
// so we can share the same validation logic.
type bankAccountLike interface {
GetID() string
GetCreatedAt() time.Time
GetAccountNumber() *string
GetIban() *string
}
func assertSavedPayloadMatchesBankAccount(
p internalEvents.BankAccountMessagePayload,
ba bankAccountLike,
expectedName, connectorID string,
) {
Expect(p.ID).To(Equal(ba.GetID()))
Expect(p.Country).To(Equal("DE"))
Expect(p.Name).To(Equal(expectedName))
Expect(p.CreatedAt.Equal(ba.GetCreatedAt())).To(BeTrue())
Expect(ba.GetAccountNumber()).ToNot(BeNil())
an := *ba.GetAccountNumber()
Expect(len(an)).To(BeNumerically(">=", 5), "account number too short for masking")
Expect(p.AccountNumber).To(Equal(fmt.Sprintf("%s****%s", an[0:2], an[len(an)-3:])))
Expect(ba.GetIban()).ToNot(BeNil())
ib := *ba.GetIban()
Expect(len(ib)).To(BeNumerically(">=", 8), "IBAN too short for masking")
Expect(p.IBAN).To(Equal(fmt.Sprintf("%s**************%s", ib[0:4], ib[len(ib)-4:])))
var ra internalEvents.BankAccountRelatedAccountsPayload
raFound := false
for _, x := range p.RelatedAccounts {
if x.ConnectorID == connectorID {
ra = x
raFound = true
break
}
}
Expect(raFound).To(BeTrue(), "expected related account for connector %s", connectorID)
Expect(ra.Provider).To(Equal("dummypay"))
Expect(ra.ConnectorID).To(Equal(connectorID))
accID, err := models.AccountIDFromString(ra.AccountID)
Expect(err).To(BeNil())
Expect(accID.Reference).To(Equal(fmt.Sprintf("dummypay-%s", ba.GetID())))
Expect(accID.ConnectorID.String()).To(Equal(connectorID))
Expect(ra.CreatedAt.Equal(ba.GetCreatedAt())).To(BeTrue())
}