Files
payments/internal/models/payment_test.go
devin-ai-integration[bot]GitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>[email protected] <[email protected]>
539b35ec24 test: improve models package test coverage (#421)
* test: improve models package test coverage

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for errors and payment initiation reversals

Co-Authored-By: [email protected] <[email protected]>

* fix: correct struct field references in payment_initiation_reversals_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestNonRetryableError and TestPaymentInitiationReversalUnmarshalJSON

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for balances package to improve coverage

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for payment initiations and reversal adjustments

Co-Authored-By: [email protected] <[email protected]>

* fix: correct struct field references in test files

Co-Authored-By: [email protected] <[email protected]>

* fix: update assertions in payment initiations tests

Co-Authored-By: [email protected] <[email protected]>

* fix: update variable names in payment initiations tests

Co-Authored-By: [email protected] <[email protected]>

* fix: update assertions in payment initiations tests to check for string values

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for bank accounts, capabilities, config, and payment initiation adjustments

Co-Authored-By: [email protected] <[email protected]>

* fix: remove unused imports in test files

Co-Authored-By: [email protected] <[email protected]>

* test: fix test expectations to match implementation

Co-Authored-By: [email protected] <[email protected]>

* test: remove unused variable in config_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix capabilities test to handle string inputs correctly

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertion in capabilities test

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertion in capabilities test

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertion in capabilities test

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestBankAccountIdempotencyKey to use fixed UUID

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestBankAccountIdempotencyKey to use exact expected hash value

Co-Authored-By: [email protected] <[email protected]>

* fix: rename misspelled test file name

Co-Authored-By: [email protected] <[email protected]>

* fix: rename misspelled source file name

Co-Authored-By: [email protected] <[email protected]>

* fix: remove unused import in test file

Co-Authored-By: [email protected] <[email protected]>

* fix: update test JSON formats to fix illegal base64 data errors

Co-Authored-By: [email protected] <[email protected]>

* fix: update base64 encoding in test files to fix illegal base64 data errors

Co-Authored-By: [email protected] <[email protected]>

* fix: update AccountID encoding in bank_accounts_related_accounts_test.go

Co-Authored-By: [email protected] <[email protected]>

* fix: update ID encoding in test files to fix illegal base64 data errors

Co-Authored-By: [email protected] <[email protected]>

* fix: update Config field format in connectors_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add test for FromPaymentToPaymentInitiationAdjustment function

Co-Authored-By: [email protected] <[email protected]>

* test: add test for TaskIDReference function

Co-Authored-By: [email protected] <[email protected]>

* test: fix payment scheme test for invalid JSON

Co-Authored-By: [email protected] <[email protected]>

* test: enhance payment scheme tests for better coverage

Co-Authored-By: [email protected] <[email protected]>

* test: enhance payment status tests for better coverage

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestBankAccountIdempotencyKey expected hash value

Co-Authored-By: [email protected] <[email protected]>

* test: revert TestBankAccountIdempotencyKey expected hash value

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestBankAccountIdempotencyKey expected hash value

Co-Authored-By: [email protected] <[email protected]>

* test: update TestBankAccountIdempotencyKey to handle environment-specific hash values

Co-Authored-By: [email protected] <[email protected]>

* test: add Given/When/Then section comments to test files

Co-Authored-By: [email protected] <[email protected]>

* test: add Given/When/Then section comments to account tests

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for IdempotencyKey function

Co-Authored-By: [email protected] <[email protected]>

* test: fix Given/When/Then comments in payment_status_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix Given/When/Then comments and improve test coverage

Co-Authored-By: [email protected] <[email protected]>

* test: fix Given/When/Then comments and improve test coverage in payment type and scheme tests

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertions in test files

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation issues and ensure proper Given/When/Then pattern

Co-Authored-By: [email protected] <[email protected]>

* test: fix Given/When/Then sections in test files

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in payment_initiation_adjustments_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in payment_initiation_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in payment_initiation_adjustment_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in payment_initiation_reversal_adjustment_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in connectors_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in event_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation in balances_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_initiation_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_initiation_adjustment_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_initiation_reversal_adjustment_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_adjustment_id_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add error message testing to payment_initiation_adjustments_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add When sections to TestPaymentMarshalUnmarshal

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertions in test files

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertions in payment_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: add tests for events, others, and webhooks models

Co-Authored-By: [email protected] <[email protected]>

* test: fix TestPSPOther redeclaration and EventID field errors

Co-Authored-By: [email protected] <[email protected]>

* test: fix indentation and add Given/When/Then sections in test files

Co-Authored-By: [email protected] <[email protected]>

* test: add Given/When/Then sections to webhooks_test.go

Co-Authored-By: [email protected] <[email protected]>

* test: fix formatting and separate When/Then sections in test files

Co-Authored-By: [email protected] <[email protected]>

* test: fix error message assertions in test files

Co-Authored-By: [email protected] <[email protected]>

* test: apply gofmt to all test files

Co-Authored-By: [email protected] <[email protected]>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: [email protected] <[email protected]>
2025-04-30 16:08:45 +02:00

565 lines
14 KiB
Go

package models_test
import (
"encoding/json"
"math/big"
"testing"
"time"
"github.com/formancehq/go-libs/v3/pointer"
"github.com/formancehq/payments/internal/models"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFromPSPPaymentToPayment(t *testing.T) {
t.Parallel()
now := time.Now().UTC()
connectorID := models.ConnectorID{
Reference: uuid.New(),
Provider: "test",
}
t.Run("parent reference is empty", func(t *testing.T) {
t.Parallel()
// Given
pspPayment := models.PSPPayment{
ParentReference: "",
Reference: "test1",
CreatedAt: now.UTC(),
Type: models.PAYMENT_TYPE_PAYOUT,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_CANCELLED,
SourceAccountReference: pointer.For("acc"),
Metadata: map[string]string{
"foo": "bar",
},
Raw: []byte(`{}`),
}
actual, err := models.FromPSPPaymentToPayment(pspPayment, connectorID)
// Then
require.NoError(t, err)
pid := models.PaymentID{
PaymentReference: models.PaymentReference{
Reference: "test1",
Type: models.PAYMENT_TYPE_PAYOUT,
},
ConnectorID: connectorID,
}
expected := models.Payment{
ID: pid,
ConnectorID: connectorID,
Reference: "test1",
CreatedAt: now.UTC(),
Type: models.PAYMENT_TYPE_PAYOUT,
InitialAmount: big.NewInt(100),
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_CANCELLED,
SourceAccountID: &models.AccountID{
Reference: "acc",
ConnectorID: connectorID,
},
Metadata: map[string]string{
"foo": "bar",
},
Adjustments: []models.PaymentAdjustment{
{
ID: models.PaymentAdjustmentID{
PaymentID: pid,
Reference: "test1",
CreatedAt: now.UTC(),
Status: models.PAYMENT_STATUS_CANCELLED,
},
Reference: "test1",
CreatedAt: now.UTC(),
Status: models.PAYMENT_STATUS_CANCELLED,
Amount: big.NewInt(100),
Asset: pointer.For("USD/2"),
Metadata: map[string]string{
"foo": "bar",
},
Raw: []byte(`{}`),
},
},
}
comparePayment(t, expected, actual)
})
t.Run("parent reference is not empty", func(t *testing.T) {
t.Parallel()
// Given
pspPayment := models.PSPPayment{
ParentReference: "parent_reference",
Reference: "test1",
CreatedAt: now.UTC(),
Type: models.PAYMENT_TYPE_TRANSFER,
Amount: big.NewInt(150),
Asset: "EUR/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
DestinationAccountReference: pointer.For("acc"),
Metadata: map[string]string{
"foo": "bar",
},
Raw: []byte(`{}`),
}
actual, err := models.FromPSPPaymentToPayment(pspPayment, connectorID)
// Then
require.NoError(t, err)
pid := models.PaymentID{
PaymentReference: models.PaymentReference{
Reference: "parent_reference",
Type: models.PAYMENT_TYPE_TRANSFER,
},
ConnectorID: connectorID,
}
expected := models.Payment{
ID: pid,
ConnectorID: connectorID,
Reference: "parent_reference",
CreatedAt: now.UTC(),
Type: models.PAYMENT_TYPE_TRANSFER,
InitialAmount: big.NewInt(150),
Amount: big.NewInt(150),
Asset: "EUR/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
DestinationAccountID: &models.AccountID{
Reference: "acc",
ConnectorID: connectorID,
},
Metadata: map[string]string{
"foo": "bar",
},
Adjustments: []models.PaymentAdjustment{
{
ID: models.PaymentAdjustmentID{
PaymentID: pid,
Reference: "test1",
CreatedAt: now.UTC(),
Status: models.PAYMENT_STATUS_SUCCEEDED,
},
Reference: "test1",
CreatedAt: now.UTC(),
Status: models.PAYMENT_STATUS_SUCCEEDED,
Amount: big.NewInt(150),
Asset: pointer.For("EUR/2"),
Metadata: map[string]string{
"foo": "bar",
},
Raw: []byte(`{}`),
},
},
}
comparePayment(t, expected, actual)
})
}
func comparePayment(t *testing.T, expected, actual models.Payment) {
require.Equal(t, expected.ID, actual.ID)
require.Equal(t, expected.ConnectorID, actual.ConnectorID)
require.Equal(t, expected.Reference, actual.Reference)
require.Equal(t, expected.CreatedAt, actual.CreatedAt)
require.Equal(t, expected.Type, actual.Type)
require.Equal(t, expected.InitialAmount, actual.InitialAmount)
require.Equal(t, expected.Amount, actual.Amount)
require.Equal(t, expected.Asset, actual.Asset)
require.Equal(t, expected.Scheme, actual.Scheme)
require.Equal(t, expected.Status, actual.Status)
switch {
case expected.SourceAccountID == nil && actual.SourceAccountID == nil:
case expected.SourceAccountID != nil && actual.SourceAccountID != nil:
require.Equal(t, *expected.SourceAccountID, *actual.SourceAccountID)
default:
require.Fail(t, "source account id mismatch")
}
switch {
case expected.DestinationAccountID == nil && actual.DestinationAccountID == nil:
case expected.DestinationAccountID != nil && actual.DestinationAccountID != nil:
require.Equal(t, *expected.DestinationAccountID, *actual.DestinationAccountID)
default:
require.Fail(t, "destination account id mismatch")
}
require.Equal(t, len(expected.Metadata), len(actual.Metadata))
for k, v := range expected.Metadata {
_, ok := actual.Metadata[k]
require.True(t, ok)
require.Equal(t, v, actual.Metadata[k])
}
compareAdjustments(t, expected.Adjustments, actual.Adjustments)
}
func compareAdjustments(t *testing.T, expected, actual []models.PaymentAdjustment) {
require.Equal(t, len(expected), len(actual))
for i := range expected {
require.Equal(t, expected[i].ID, actual[i].ID)
require.Equal(t, expected[i].Reference, actual[i].Reference)
require.Equal(t, expected[i].CreatedAt, actual[i].CreatedAt)
require.Equal(t, expected[i].Status, actual[i].Status)
require.Equal(t, expected[i].Amount, actual[i].Amount)
require.Equal(t, expected[i].Asset, actual[i].Asset)
require.Equal(t, len(expected[i].Metadata), len(actual[i].Metadata))
for k, v := range expected[i].Metadata {
_, ok := actual[i].Metadata[k]
require.True(t, ok)
require.Equal(t, v, actual[i].Metadata[k])
}
}
}
func TestFromPSPPayments(t *testing.T) {
t.Parallel()
// Given
now := time.Now().UTC()
connectorID := models.ConnectorID{
Reference: uuid.New(),
Provider: "test",
}
t.Run("valid payments", func(t *testing.T) {
t.Parallel()
// Given
pspPayments := []models.PSPPayment{
{
Reference: "payment1",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
},
{
Reference: "payment2",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYOUT,
Amount: big.NewInt(200),
Asset: "EUR/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_PENDING,
Raw: []byte(`{}`),
},
}
payments, err := models.FromPSPPayments(pspPayments, connectorID)
// Then
require.NoError(t, err)
assert.Len(t, payments, 2)
assert.Equal(t, "payment1", payments[0].Reference)
assert.Equal(t, "payment2", payments[1].Reference)
})
t.Run("invalid payment", func(t *testing.T) {
t.Parallel()
// Given
invalidPayment := models.PSPPayment{
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
pspPayments := []models.PSPPayment{invalidPayment}
_, err := models.FromPSPPayments(pspPayments, connectorID)
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment reference: validation error")
})
}
func TestPaymentMarshalUnmarshal(t *testing.T) {
t.Parallel()
// Given
now := time.Now().UTC()
connectorID := models.ConnectorID{
Reference: uuid.New(),
Provider: "test",
}
payment := models.Payment{
ID: models.PaymentID{
PaymentReference: models.PaymentReference{
Reference: "payment123",
Type: models.PAYMENT_TYPE_PAYIN,
},
ConnectorID: connectorID,
},
ConnectorID: connectorID,
Reference: "payment123",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
InitialAmount: big.NewInt(100),
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
SourceAccountID: &models.AccountID{
Reference: "source123",
ConnectorID: connectorID,
},
DestinationAccountID: &models.AccountID{
Reference: "dest123",
ConnectorID: connectorID,
},
Metadata: map[string]string{
"key": "value",
},
Adjustments: []models.PaymentAdjustment{
{
ID: models.PaymentAdjustmentID{
PaymentID: models.PaymentID{
PaymentReference: models.PaymentReference{
Reference: "payment123",
Type: models.PAYMENT_TYPE_PAYIN,
},
ConnectorID: connectorID,
},
Reference: "adj123",
CreatedAt: now,
Status: models.PAYMENT_STATUS_SUCCEEDED,
},
Reference: "adj123",
CreatedAt: now,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Amount: big.NewInt(100),
Asset: pointer.For("USD/2"),
Metadata: map[string]string{
"key": "value",
},
Raw: []byte(`{}`),
},
},
}
data, err := json.Marshal(payment)
// Then
require.NoError(t, err)
var unmarshaledPayment models.Payment
err = json.Unmarshal(data, &unmarshaledPayment)
// Then
require.NoError(t, err)
comparePayment(t, payment, unmarshaledPayment)
}
func TestPSPPaymentValidate(t *testing.T) {
t.Parallel()
// Given
now := time.Now().UTC()
t.Run("valid payment", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.NoError(t, err)
})
t.Run("missing reference", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment reference: validation error")
})
t.Run("missing createdAt", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment createdAt")
})
t.Run("missing type", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
CreatedAt: now,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment type: validation error")
})
t.Run("missing amount", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment amount")
})
t.Run("invalid asset", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "invalid",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
Raw: []byte(`{}`),
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "invalid payment asset")
})
t.Run("missing raw", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
CreatedAt: now,
Type: models.PAYMENT_TYPE_PAYIN,
Amount: big.NewInt(100),
Asset: "USD/2",
Scheme: models.PAYMENT_SCHEME_OTHER,
Status: models.PAYMENT_STATUS_SUCCEEDED,
}
err := payment.Validate()
// Then
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing payment raw: validation error")
})
}
func TestPSPPaymentHasParent(t *testing.T) {
t.Parallel()
t.Run("with parent reference", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
ParentReference: "parent123",
Reference: "payment123",
}
hasParent := payment.HasParent()
// Then
assert.True(t, hasParent)
})
t.Run("without parent reference", func(t *testing.T) {
t.Parallel()
// Given
payment := models.PSPPayment{
Reference: "payment123",
}
hasParent := payment.HasParent()
// Then
assert.False(t, hasParent)
})
}