mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
414 lines
11 KiB
Go
414 lines
11 KiB
Go
package workflow
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"log/slog"
|
|
"math/big"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/observe/log"
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/types/pointer"
|
|
"github.com/hanzo-fi/payments/internal/connectors"
|
|
"github.com/hanzo-fi/payments/internal/connectors/engine/activities"
|
|
"github.com/hanzo-fi/payments/ce/plugins/dummypay"
|
|
"github.com/hanzo-fi/payments/internal/connectors/plugins/registry"
|
|
"github.com/hanzo-fi/payments/pkg/domain/models"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/suite"
|
|
temporallog "go.temporal.io/sdk/log"
|
|
"go.temporal.io/sdk/testsuite"
|
|
temporalworkflow "go.temporal.io/sdk/workflow"
|
|
)
|
|
|
|
type UnitTestSuite struct {
|
|
suite.Suite
|
|
testsuite.WorkflowTestSuite
|
|
|
|
w *Workflow
|
|
a *activities.Activities
|
|
env *testsuite.TestWorkflowEnvironment
|
|
|
|
connectorID models.ConnectorID
|
|
accountID models.AccountID
|
|
paymentPayoutID models.PaymentID
|
|
paymentInitiationID models.PaymentInitiationID
|
|
paymentReversalID models.PaymentInitiationReversalID
|
|
|
|
connector models.Connector
|
|
configuredConnector models.Connector
|
|
bankAccount models.BankAccount
|
|
paymentServiceUser models.PaymentServiceUser
|
|
paymentPayout models.Payment
|
|
paymentWithAdjustmentAmount models.Payment
|
|
account models.Account
|
|
paymentInitiationPayout models.PaymentInitiation
|
|
paymentInitiationTransfer models.PaymentInitiation
|
|
paymentReversal models.PaymentInitiationReversal
|
|
|
|
pspAccount models.PSPAccount
|
|
pspPayment models.PSPPayment
|
|
pspPaymentReversed models.PSPPayment
|
|
pspBalance models.PSPBalance
|
|
pspOther models.PSPOther
|
|
}
|
|
|
|
func (s *UnitTestSuite) SetupTest() {
|
|
s.SetLogger(temporallog.NewStructuredLogger(slog.New(slog.NewTextHandler(io.Discard, nil))))
|
|
s.env = s.NewTestWorkflowEnvironment()
|
|
|
|
for _, def := range s.w.DefinitionSet() {
|
|
s.env.RegisterWorkflowWithOptions(def.Func, temporalworkflow.RegisterOptions{
|
|
Name: def.Name,
|
|
})
|
|
}
|
|
|
|
s.addData()
|
|
|
|
// EN-1093/H12: scheduleNextWorkflow now reads the polling period via a deterministic
|
|
// activity (the test env always takes the activity branch). Register it globally so any
|
|
// test that schedules is covered; .Maybe() leaves non-scheduling tests unaffected.
|
|
s.mockPollingPeriod(2 * time.Minute)
|
|
}
|
|
|
|
func (s *UnitTestSuite) AfterTest(suiteName, testName string) {
|
|
s.env.AssertExpectations(s.T())
|
|
}
|
|
|
|
// mockPollingPeriod registers the deterministic StorageConnectorsGetPollingPeriod activity
|
|
// (EN-1093/H12). Temporal's TestWorkflowEnvironment always reports the newest GetVersion, so
|
|
// scheduleNextWorkflow / create_payout / create_transfer always take the activity branch in
|
|
// tests. Registered with .Maybe() since not every test reaches the polling-period read.
|
|
func (s *UnitTestSuite) mockPollingPeriod(pollingPeriod time.Duration) {
|
|
s.env.OnActivity(activities.StorageConnectorsGetPollingPeriodActivity, mock.Anything, mock.Anything).Maybe().Return(
|
|
pollingPeriod,
|
|
nil,
|
|
)
|
|
}
|
|
|
|
func TestUnitTestSuite(t *testing.T) {
|
|
logger := logging.Testing()
|
|
w := Workflow{
|
|
connectors: connectors.NewManager(logger, true, time.Minute, time.Minute),
|
|
stackPublicURL: "http://localhost:8080",
|
|
stack: "test",
|
|
logger: logger,
|
|
}
|
|
a := activities.Activities{}
|
|
|
|
suite.Run(t, &UnitTestSuite{w: &w, a: &a})
|
|
}
|
|
|
|
func (s *UnitTestSuite) addData() {
|
|
now := s.env.Now().UTC()
|
|
|
|
s.connectorID = models.ConnectorID{
|
|
Reference: uuid.New(),
|
|
Provider: "test",
|
|
}
|
|
|
|
registry.RegisterPlugin("test", models.PluginTypePSP, func(_ models.ConnectorID, name string, logger logging.Logger, rm json.RawMessage) (models.Plugin, error) {
|
|
return dummypay.New(name, logger, rm)
|
|
}, []models.Capability{}, struct{}{}, 25)
|
|
dummyConf := json.RawMessage(`{"name":"somename","directory":"/tmp"}`)
|
|
|
|
connector := models.Connector{
|
|
ConnectorBase: models.ConnectorBase{
|
|
ID: s.connectorID,
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
Provider: "test",
|
|
},
|
|
ScheduledForDeletion: false,
|
|
Config: dummyConf,
|
|
}
|
|
s.configuredConnector = connector
|
|
_, _, err := s.w.connectors.Load(s.configuredConnector, true, true)
|
|
s.NoError(err)
|
|
|
|
s.accountID = models.AccountID{
|
|
Reference: "test",
|
|
ConnectorID: s.connectorID,
|
|
}
|
|
|
|
s.paymentPayoutID = models.PaymentID{
|
|
PaymentReference: models.PaymentReference{
|
|
Reference: "test",
|
|
Type: models.PAYMENT_TYPE_PAYOUT,
|
|
},
|
|
ConnectorID: s.connectorID,
|
|
}
|
|
|
|
s.paymentInitiationID = models.PaymentInitiationID{
|
|
Reference: "test",
|
|
ConnectorID: s.connectorID,
|
|
}
|
|
|
|
s.paymentReversalID = models.PaymentInitiationReversalID{
|
|
Reference: "test",
|
|
ConnectorID: s.connectorID,
|
|
}
|
|
|
|
s.bankAccount = models.BankAccount{
|
|
ID: uuid.New(),
|
|
CreatedAt: now,
|
|
Name: "test",
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
}
|
|
|
|
s.paymentServiceUser = models.PaymentServiceUser{
|
|
ID: uuid.New(),
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
ContactDetails: &models.ContactDetails{
|
|
Email: pointer.For("test"),
|
|
PhoneNumber: pointer.For("test"),
|
|
},
|
|
Address: &models.Address{
|
|
StreetName: pointer.For("test"),
|
|
StreetNumber: pointer.For("test"),
|
|
City: pointer.For("test"),
|
|
Region: pointer.For("test"),
|
|
PostalCode: pointer.For("test"),
|
|
Country: pointer.For("test"),
|
|
},
|
|
BankAccountIDs: []uuid.UUID{
|
|
s.bankAccount.ID,
|
|
},
|
|
Metadata: map[string]string{
|
|
"foo": "bar",
|
|
},
|
|
}
|
|
|
|
s.paymentPayout = models.Payment{
|
|
ID: s.paymentPayoutID,
|
|
ConnectorID: s.connectorID,
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Type: models.PAYMENT_TYPE_PAYOUT,
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Scheme: models.PAYMENT_SCHEME_A2A,
|
|
Status: models.PAYMENT_STATUS_SUCCEEDED,
|
|
SourceAccountID: &models.AccountID{
|
|
Reference: "test1",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
DestinationAccountID: &models.AccountID{
|
|
Reference: "test2",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Adjustments: []models.PaymentAdjustment{
|
|
{
|
|
ID: models.PaymentAdjustmentID{
|
|
PaymentID: s.paymentPayoutID,
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Status: models.PAYMENT_STATUS_SUCCEEDED,
|
|
},
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Status: models.PAYMENT_STATUS_SUCCEEDED,
|
|
Amount: big.NewInt(100),
|
|
Asset: pointer.For("USD/2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
},
|
|
},
|
|
}
|
|
|
|
s.paymentWithAdjustmentAmount = models.Payment{
|
|
ID: s.paymentPayoutID,
|
|
ConnectorID: s.connectorID,
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Type: models.PAYMENT_TYPE_PAYOUT,
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Scheme: models.PAYMENT_SCHEME_A2A,
|
|
Status: models.PAYMENT_STATUS_AMOUNT_ADJUSTMENT,
|
|
SourceAccountID: &models.AccountID{
|
|
Reference: "test1",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
DestinationAccountID: &models.AccountID{
|
|
Reference: "test2",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Adjustments: []models.PaymentAdjustment{
|
|
{
|
|
ID: models.PaymentAdjustmentID{
|
|
PaymentID: s.paymentPayoutID,
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Status: models.PAYMENT_STATUS_AMOUNT_ADJUSTMENT,
|
|
},
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Status: models.PAYMENT_STATUS_AMOUNT_ADJUSTMENT,
|
|
Amount: big.NewInt(100),
|
|
Asset: pointer.For("USD/2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
},
|
|
},
|
|
}
|
|
|
|
s.paymentInitiationPayout = models.PaymentInitiation{
|
|
ID: s.paymentInitiationID,
|
|
ConnectorID: s.connectorID,
|
|
Reference: "test",
|
|
CreatedAt: s.env.Now().UTC(),
|
|
ScheduledAt: s.env.Now().UTC(),
|
|
Description: "test_payout",
|
|
Type: models.PAYMENT_INITIATION_TYPE_PAYOUT,
|
|
SourceAccountID: &models.AccountID{
|
|
Reference: "test1",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
DestinationAccountID: &models.AccountID{
|
|
Reference: "test2",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
}
|
|
|
|
s.paymentInitiationTransfer = models.PaymentInitiation{
|
|
ID: s.paymentInitiationID,
|
|
ConnectorID: s.connectorID,
|
|
Reference: "test",
|
|
CreatedAt: s.env.Now().UTC(),
|
|
ScheduledAt: s.env.Now().UTC(),
|
|
Description: "test_payout",
|
|
Type: models.PAYMENT_INITIATION_TYPE_TRANSFER,
|
|
SourceAccountID: &models.AccountID{
|
|
Reference: "test1",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
DestinationAccountID: &models.AccountID{
|
|
Reference: "test2",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
}
|
|
|
|
s.paymentReversal = models.PaymentInitiationReversal{
|
|
ID: s.paymentReversalID,
|
|
ConnectorID: s.connectorID,
|
|
PaymentInitiationID: s.paymentInitiationID,
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Description: "test",
|
|
Amount: big.NewInt(50),
|
|
Asset: "USD/2",
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
}
|
|
|
|
s.connector = models.Connector{
|
|
ConnectorBase: models.ConnectorBase{
|
|
ID: s.connectorID,
|
|
Name: "test",
|
|
CreatedAt: now,
|
|
Provider: "test",
|
|
},
|
|
ScheduledForDeletion: false,
|
|
Config: []byte(`{"name": "test", "pollingPeriod": "2m", "pageSize": 25}`),
|
|
}
|
|
|
|
s.account = models.Account{
|
|
ID: models.AccountID{
|
|
Reference: "test1",
|
|
ConnectorID: s.connectorID,
|
|
},
|
|
ConnectorID: s.connectorID,
|
|
Reference: "test1",
|
|
CreatedAt: s.env.Now().UTC(),
|
|
Type: models.ACCOUNT_TYPE_INTERNAL,
|
|
Name: pointer.For("test1"),
|
|
DefaultAsset: pointer.For("USD/2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
}
|
|
|
|
s.pspAccount = models.PSPAccount{
|
|
Reference: "test",
|
|
CreatedAt: s.env.Now().UTC(),
|
|
Name: pointer.For("test"),
|
|
DefaultAsset: pointer.For("USD/2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
}
|
|
|
|
s.pspPayment = models.PSPPayment{
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Type: models.PAYMENT_TYPE_PAYOUT,
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Scheme: models.PAYMENT_SCHEME_A2A,
|
|
Status: models.PAYMENT_STATUS_SUCCEEDED,
|
|
SourceAccountReference: pointer.For("test1"),
|
|
DestinationAccountReference: pointer.For("test2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
}
|
|
|
|
s.pspPaymentReversed = models.PSPPayment{
|
|
Reference: "test",
|
|
CreatedAt: now,
|
|
Type: models.PAYMENT_TYPE_PAYOUT,
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
Scheme: models.PAYMENT_SCHEME_A2A,
|
|
Status: models.PAYMENT_STATUS_REFUNDED,
|
|
SourceAccountReference: pointer.For("test1"),
|
|
DestinationAccountReference: pointer.For("test2"),
|
|
Metadata: map[string]string{
|
|
"key": "value",
|
|
},
|
|
Raw: []byte(`{}`),
|
|
}
|
|
|
|
s.pspBalance = models.PSPBalance{
|
|
AccountReference: "test",
|
|
CreatedAt: now,
|
|
Amount: big.NewInt(100),
|
|
Asset: "USD/2",
|
|
}
|
|
|
|
s.pspOther = models.PSPOther{
|
|
ID: "test",
|
|
Other: []byte(`{}`),
|
|
}
|
|
}
|