Files
payments/internal/storage/payment_initiations_test.go

1257 lines
42 KiB
Go

package storage
import (
"context"
"math/big"
"testing"
"github.com/formancehq/go-libs/v3/bun/bunpaginate"
"github.com/formancehq/go-libs/v3/logging"
"github.com/formancehq/go-libs/v3/pointer"
"github.com/formancehq/go-libs/v3/query"
"github.com/formancehq/go-libs/v3/time"
"github.com/formancehq/payments/internal/models"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
piID1 = models.PaymentInitiationID{
Reference: "test1",
ConnectorID: defaultConnector.ID,
}
piID2 = models.PaymentInitiationID{
Reference: "test2",
ConnectorID: defaultConnector.ID,
}
piID3 = models.PaymentInitiationID{
Reference: "test3",
ConnectorID: defaultConnector.ID,
}
)
func defaultPaymentInitiations() []models.PaymentInitiation {
defaultAccounts := defaultAccounts()
return []models.PaymentInitiation{
{
ID: piID1,
ConnectorID: defaultConnector.ID,
Reference: "test1",
CreatedAt: now.Add(-60 * time.Minute).UTC().Time,
ScheduledAt: now.Add(-60 * time.Minute).UTC().Time,
Description: "test1",
Type: models.PAYMENT_INITIATION_TYPE_PAYOUT,
DestinationAccountID: &defaultAccounts[0].ID,
Amount: big.NewInt(100),
Asset: "EUR/2",
},
{
ID: piID2,
ConnectorID: defaultConnector.ID,
Reference: "test2",
CreatedAt: now.Add(-30 * time.Minute).UTC().Time,
ScheduledAt: now.Add(-20 * time.Minute).UTC().Time,
Description: "test2",
Type: models.PAYMENT_INITIATION_TYPE_TRANSFER,
SourceAccountID: &defaultAccounts[0].ID,
DestinationAccountID: &defaultAccounts[1].ID,
Amount: big.NewInt(150),
Asset: "USD/2",
Metadata: map[string]string{
"foo": "bar",
},
},
{
ID: piID3,
ConnectorID: defaultConnector.ID,
Reference: "test3",
CreatedAt: now.Add(-55 * time.Minute).UTC().Time,
ScheduledAt: now.Add(-40 * time.Minute).UTC().Time,
Description: "test3",
Type: models.PAYMENT_INITIATION_TYPE_PAYOUT,
DestinationAccountID: &defaultAccounts[1].ID,
Amount: big.NewInt(200),
Asset: "EUR/2",
Metadata: map[string]string{
"foo2": "bar2",
},
},
}
}
func upsertPaymentInitiations(t *testing.T, ctx context.Context, storage Storage, paymentInitiations []models.PaymentInitiation) {
for _, pi := range paymentInitiations {
err := storage.PaymentInitiationsInsert(ctx, pi)
require.NoError(t, err)
}
}
func TestPaymentInitiationsInsert(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
t.Run("upsert with unknown connector", func(t *testing.T) {
connector := models.ConnectorID{
Reference: uuid.New(),
Provider: "unknown",
}
p := defaultPaymentInitiations()[0]
p.ID.ConnectorID = connector
p.ConnectorID = connector
err := store.PaymentInitiationsInsert(ctx, p)
require.Error(t, err)
})
t.Run("attempt insert with same id", func(t *testing.T) {
defaultAccounts := defaultAccounts()
pi := models.PaymentInitiation{
ID: piID1,
ConnectorID: defaultConnector.ID,
Reference: "test_changed",
CreatedAt: now.Add(-30 * time.Minute).UTC().Time,
ScheduledAt: now.Add(-20 * time.Minute).UTC().Time,
Description: "test_changed",
Type: models.PAYMENT_INITIATION_TYPE_PAYOUT,
DestinationAccountID: &defaultAccounts[0].ID,
Amount: big.NewInt(100),
Asset: "DKK/2",
}
err := store.PaymentInitiationsInsert(ctx, pi)
require.Error(t, err)
assert.ErrorIs(t, err, ErrDuplicateKeyValue)
actual, err := store.PaymentInitiationsGet(ctx, piID1)
require.NoError(t, err)
comparePaymentInitiations(t, defaultPaymentInitiations()[0], *actual)
})
}
func TestPaymentInitiationsUpdateMetadata(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
t.Run("update metadata of unknown payment initiation", func(t *testing.T) {
require.Error(t, store.PaymentInitiationsUpdateMetadata(ctx, models.PaymentInitiationID{
Reference: "unknown",
ConnectorID: defaultConnector.ID,
}, map[string]string{}))
})
t.Run("update existing metadata", func(t *testing.T) {
metadata := map[string]string{
"foo": "changed",
}
require.NoError(t, store.PaymentInitiationsUpdateMetadata(ctx, piID2, metadata))
actual, err := store.PaymentInitiationsGet(ctx, piID2)
require.NoError(t, err)
require.Equal(t, len(metadata), len(actual.Metadata))
for k, v := range metadata {
_, ok := actual.Metadata[k]
require.True(t, ok)
require.Equal(t, v, actual.Metadata[k])
}
})
t.Run("add new metadata", func(t *testing.T) {
metadata := map[string]string{
"key2": "value2",
"key3": "value3",
}
require.NoError(t, store.PaymentInitiationsUpdateMetadata(ctx, piID1, metadata))
actual, err := store.PaymentInitiationsGet(ctx, piID1)
require.NoError(t, err)
require.Equal(t, len(metadata), len(actual.Metadata))
for k, v := range metadata {
_, ok := actual.Metadata[k]
require.True(t, ok)
require.Equal(t, v, actual.Metadata[k])
}
})
}
func TestPaymentInitiationsGet(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
t.Run("get unknown payment initiation", func(t *testing.T) {
_, err := store.PaymentInitiationsGet(ctx, models.PaymentInitiationID{
Reference: "unknown",
ConnectorID: defaultConnector.ID,
})
require.Error(t, err)
require.ErrorIs(t, err, ErrNotFound)
})
t.Run("get existing payment initiation", func(t *testing.T) {
for _, pi := range defaultPaymentInitiations() {
actual, err := store.PaymentInitiationsGet(ctx, pi.ID)
require.NoError(t, err)
comparePaymentInitiations(t, pi, *actual)
}
})
}
func TestPaymentInitiationsDelete(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
t.Run("delete unknown payment initiation", func(t *testing.T) {
require.NoError(t, store.PaymentInitiationsDelete(ctx, models.PaymentInitiationID{
Reference: "unknown",
ConnectorID: defaultConnector.ID,
}))
})
t.Run("delete existing payment initiation", func(t *testing.T) {
for _, pi := range defaultPaymentInitiations() {
require.NoError(t, store.PaymentInitiationsDelete(ctx, pi.ID))
_, err := store.PaymentInitiationsGet(ctx, pi.ID)
require.Error(t, err)
require.ErrorIs(t, err, ErrNotFound)
}
})
}
func TestPaymentInitiationsDeleteFromConnectorID(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
t.Run("delete from unknown connector", func(t *testing.T) {
require.NoError(t, store.PaymentInitiationsDeleteFromConnectorID(ctx, models.ConnectorID{
Reference: uuid.New(),
Provider: "unknown",
}))
for _, pi := range defaultPaymentInitiations() {
actual, err := store.PaymentInitiationsGet(ctx, pi.ID)
require.NoError(t, err)
comparePaymentInitiations(t, pi, *actual)
}
})
t.Run("delete from existing connector", func(t *testing.T) {
require.NoError(t, store.PaymentInitiationsDeleteFromConnectorID(ctx, defaultConnector.ID))
for _, pi := range defaultPaymentInitiations() {
_, err := store.PaymentInitiationsGet(ctx, pi.ID)
require.Error(t, err)
require.ErrorIs(t, err, ErrNotFound)
}
})
}
func TestPaymentInitiationsList(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationAdjustments(t, ctx, store, defaultPaymentInitiationAdjustments())
t.Run("wrong query builder operator when listing by reference", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Lt("reference", "test1")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.Error(t, err)
require.Nil(t, cursor)
assert.True(t, errors.Is(err, ErrValidation))
assert.Regexp(t, "reference", err.Error())
})
t.Run("list payment intitiations by reference", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("reference", "test1")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[0])
})
t.Run("list payment initiations by unknown reference", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("reference", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment intitiations by id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("id", defaultPaymentInitiations()[0].ID.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[0])
})
t.Run("list payment initiations by unknown id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("id", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by connector_id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("connector_id", defaultConnector.ID.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 3)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[1])
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[2])
})
t.Run("list payment initiations by unknown connector_id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("connector_id", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by type", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("type", "PAYOUT")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 2)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[0])
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[1])
})
t.Run("list payment initiations by type 2", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("type", "TRANSFER")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
t.Run("list payment initiations by unknown type", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("type", "UNKNOWN")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by status multiple adjustments", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("status", models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[0])
})
t.Run("list payment initiations by status single adjustment", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("status", models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
t.Run("list payment initiations by unknown status", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("status", "UNKNOWN")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by asset", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("asset", "EUR/2")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 2)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[0])
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[1])
})
t.Run("list payment initiations by asset 2", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("asset", "USD/2")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
t.Run("list payment initiations by unknown asset", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("asset", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by source account id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("source_account_id", defaultAccounts()[0].ID.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
t.Run("list payment initiations by unknown source account id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("source_account_id", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by destination account id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("destination_account_id", defaultAccounts()[1].ID.String())),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 2)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[1])
})
t.Run("list payment initiations by unknown destination account id", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("destination_account_id", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by amount", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("amount", 200)),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[0])
})
t.Run("list payment initiations by unknown amount", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("amount", 0)),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("wrong query builder operator when listing by metadata", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Lt("metadata[foo]", "bar")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.Error(t, err)
require.Nil(t, cursor)
})
t.Run("list payment initiations by metadata", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("metadata[foo]", "bar")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
t.Run("list payment initiations by unknown metadata", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("metadata[foo]", "unknown")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list payment initiations by unknown metadata 2", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("metadata[unknown]", "bar")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("unknown query builder key when listing", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("unknown", "test1")),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.Error(t, err)
require.Nil(t, cursor)
})
t.Run("list payment initiations test cursor", func(t *testing.T) {
q := NewListPaymentInitiationsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationQuery{}).
WithPageSize(1),
)
cursor, err := store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.Empty(t, cursor.Next)
comparePaymentInitiations(t, defaultPaymentInitiations()[0], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Previous, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiations(t, defaultPaymentInitiations()[2], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Previous, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationsList(ctx, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiations(t, defaultPaymentInitiations()[1], cursor.Data[0])
})
}
func upsertPaymentInitiationRelatedPayments(t *testing.T, ctx context.Context, storage Storage) {
payments := defaultPayments()
require.NoError(t, storage.PaymentInitiationRelatedPaymentsUpsert(ctx, piID1, payments[0].ID, now.Add(-10*time.Minute).UTC().Time))
require.NoError(t, storage.PaymentInitiationRelatedPaymentsUpsert(ctx, piID1, payments[1].ID, now.Add(-5*time.Minute).UTC().Time))
require.NoError(t, storage.PaymentInitiationRelatedPaymentsUpsert(ctx, piID1, payments[2].ID, now.Add(-7*time.Minute).UTC().Time))
require.NoError(t, storage.PaymentInitiationRelatedPaymentsUpsert(ctx, piID2, payments[0].ID, now.Add(-7*time.Minute).UTC().Time))
}
func TestPaymentInitiationsRelatedPaymentUpsert(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationRelatedPayments(t, ctx, store)
t.Run("same id insert", func(t *testing.T) {
payments := defaultPayments()
require.NoError(t, store.PaymentInitiationRelatedPaymentsUpsert(ctx, piID1, payments[0].ID, now.Add(-10*time.Minute).UTC().Time))
cursor, err := store.PaymentInitiationRelatedPaymentsList(
ctx,
piID1,
NewListPaymentInitiationRelatedPaymentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationRelatedPaymentsQuery{}).
WithPageSize(15),
),
)
require.NoError(t, err)
require.Len(t, cursor.Data, 3)
require.False(t, cursor.HasMore)
comparePayments(t, payments[1], cursor.Data[0])
comparePayments(t, payments[2], cursor.Data[1])
comparePayments(t, payments[0], cursor.Data[2])
})
t.Run("unknown payment initiation", func(t *testing.T) {
payments := defaultPayments()
require.Error(t, store.PaymentInitiationRelatedPaymentsUpsert(
ctx,
models.PaymentInitiationID{},
payments[0].ID, now.Add(-10*time.Minute).UTC().Time),
)
})
}
func TestPaymentInitiationIDsFromPaymentID(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationRelatedPayments(t, ctx, store)
t.Run("unknown payment id", func(t *testing.T) {
ids, err := store.PaymentInitiationIDsListFromPaymentID(ctx, models.PaymentID{})
require.NoError(t, err)
require.Len(t, ids, 0)
})
t.Run("known payment id", func(t *testing.T) {
ids, err := store.PaymentInitiationIDsListFromPaymentID(ctx, defaultPayments()[0].ID)
require.NoError(t, err)
require.Len(t, ids, 2)
require.Contains(t, ids, piID1)
require.Contains(t, ids, piID2)
})
}
func TestPaymentInitiationRelatedPaymentsList(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationRelatedPayments(t, ctx, store)
t.Run("list related payments by unknown payment initiation", func(t *testing.T) {
cursor, err := store.PaymentInitiationRelatedPaymentsList(
ctx,
models.PaymentInitiationID{},
NewListPaymentInitiationRelatedPaymentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationRelatedPaymentsQuery{}).
WithPageSize(15),
),
)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("list related payments by payment initiation", func(t *testing.T) {
q := NewListPaymentInitiationRelatedPaymentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationRelatedPaymentsQuery{}).
WithPageSize(1),
)
payments := defaultPayments()
cursor, err := store.PaymentInitiationRelatedPaymentsList(ctx, piID1, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePayments(t, payments[1], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationRelatedPaymentsList(ctx, piID1, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePayments(t, payments[2], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationRelatedPaymentsList(ctx, piID1, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.Empty(t, cursor.Next)
comparePayments(t, payments[0], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Previous, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationRelatedPaymentsList(ctx, piID1, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePayments(t, payments[2], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Previous, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationRelatedPaymentsList(ctx, piID1, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePayments(t, payments[1], cursor.Data[0])
})
}
var (
piAdjID1 = models.PaymentInitiationAdjustmentID{
PaymentInitiationID: defaultPaymentInitiations()[0].ID,
CreatedAt: now.Add(-10 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_WAITING_FOR_VALIDATION,
}
piAdjID2 = models.PaymentInitiationAdjustmentID{
PaymentInitiationID: defaultPaymentInitiations()[0].ID,
CreatedAt: now.Add(-5 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED,
}
piAdjID3 = models.PaymentInitiationAdjustmentID{
PaymentInitiationID: defaultPaymentInitiations()[1].ID,
CreatedAt: now.Add(-7 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
}
)
func defaultPaymentInitiationAdjustments() []models.PaymentInitiationAdjustment {
return []models.PaymentInitiationAdjustment{
{
ID: piAdjID1,
CreatedAt: now.Add(-10 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_WAITING_FOR_VALIDATION,
Amount: big.NewInt(100),
Asset: pointer.For("EUR/2"),
Metadata: map[string]string{
"foo": "bar",
},
},
{
ID: piAdjID2,
CreatedAt: now.Add(-5 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED,
Error: errors.New("test"),
Amount: big.NewInt(200),
Asset: pointer.For("USD/2"),
Metadata: map[string]string{
"foo2": "bar2",
},
},
{
ID: piAdjID3,
CreatedAt: now.Add(-7 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
Amount: big.NewInt(300),
Asset: pointer.For("DKK/2"),
Metadata: map[string]string{
"foo3": "bar3",
},
},
}
}
func upsertPaymentInitiationAdjustments(t *testing.T, ctx context.Context, storage Storage, adjustments []models.PaymentInitiationAdjustment) {
for _, adj := range adjustments {
require.NoError(t, storage.PaymentInitiationAdjustmentsUpsert(ctx, adj))
}
}
func TestPaymentInitiationAdjustmentsUpsert(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationAdjustments(t, ctx, store, defaultPaymentInitiationAdjustments())
t.Run("upsert with unknown payment initiation", func(t *testing.T) {
p := models.PaymentInitiationAdjustment{
ID: models.PaymentInitiationAdjustmentID{},
CreatedAt: now.Add(-10 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_WAITING_FOR_VALIDATION,
Metadata: map[string]string{
"foo": "bar",
},
}
require.Error(t, store.PaymentInitiationAdjustmentsUpsert(ctx, p))
})
t.Run("upsert with same id", func(t *testing.T) {
p := models.PaymentInitiationAdjustment{
ID: piAdjID1,
CreatedAt: now.Add(-30 * time.Minute).UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSED,
Metadata: map[string]string{
"foo": "changed",
},
}
require.NoError(t, store.PaymentInitiationAdjustmentsUpsert(ctx, p))
for _, pa := range defaultPaymentInitiationAdjustments() {
actual, err := store.PaymentInitiationAdjustmentsGet(ctx, pa.ID)
require.NoError(t, err)
comparePaymentInitiationAdjustments(t, pa, *actual)
}
})
}
func TestPaymentInitiationAdjustmentsUpsertIfStatusEqual(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationAdjustments(t, ctx, store, defaultPaymentInitiationAdjustments())
t.Run("upsert with status not equal", func(t *testing.T) {
p := models.PaymentInitiationAdjustment{
ID: models.PaymentInitiationAdjustmentID{
PaymentInitiationID: defaultPaymentInitiations()[1].ID,
CreatedAt: now.UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
},
CreatedAt: now.UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
Metadata: map[string]string{
"foo": "bar",
},
}
inserted, err := store.PaymentInitiationAdjustmentsUpsertIfPredicate(
ctx,
p,
func(previous models.PaymentInitiationAdjustment) bool {
return previous.Status == models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_WAITING_FOR_VALIDATION
},
)
require.NoError(t, err)
require.False(t, inserted)
})
t.Run("upsert with status equal", func(t *testing.T) {
p := models.PaymentInitiationAdjustment{
ID: models.PaymentInitiationAdjustmentID{
PaymentInitiationID: defaultPaymentInitiations()[0].ID,
CreatedAt: now.UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
},
CreatedAt: now.UTC().Time,
Status: models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_PROCESSING,
Metadata: map[string]string{
"foo": "bar",
},
}
inserted, err := store.PaymentInitiationAdjustmentsUpsertIfPredicate(
ctx,
p,
func(previous models.PaymentInitiationAdjustment) bool {
return previous.Status == models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED
},
)
require.NoError(t, err)
require.True(t, inserted)
})
}
func TestPaymentInitiationAdjustmentsGet(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationAdjustments(t, ctx, store, defaultPaymentInitiationAdjustments())
t.Run("get unknown payment initiation adjustment", func(t *testing.T) {
_, err := store.PaymentInitiationAdjustmentsGet(ctx, models.PaymentInitiationAdjustmentID{})
require.Error(t, err)
require.ErrorIs(t, err, ErrNotFound)
})
t.Run("get existing payment initiation adjustment", func(t *testing.T) {
for _, pa := range defaultPaymentInitiationAdjustments() {
actual, err := store.PaymentInitiationAdjustmentsGet(ctx, pa.ID)
require.NoError(t, err)
comparePaymentInitiationAdjustments(t, pa, *actual)
}
})
}
func TestPaymentInitiationAdjustmentsList(t *testing.T) {
t.Parallel()
ctx := logging.TestingContext()
store := newStore(t)
defer store.Close()
upsertConnector(t, ctx, store, defaultConnector)
upsertAccounts(t, ctx, store, defaultAccounts())
upsertPayments(t, ctx, store, defaultPayments())
upsertPaymentInitiations(t, ctx, store, defaultPaymentInitiations())
upsertPaymentInitiationAdjustments(t, ctx, store, defaultPaymentInitiationAdjustments())
t.Run("list payment initiation adjustments by unknown payment initiation", func(t *testing.T) {
cursor, err := store.PaymentInitiationAdjustmentsList(
ctx,
models.PaymentInitiationID{},
NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15),
),
)
require.NoError(t, err)
require.Len(t, cursor.Data, 0)
require.False(t, cursor.HasMore)
})
t.Run("wrong query builder operator when listing by status", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Lt("status", models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED)),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.Error(t, err)
require.Nil(t, cursor)
})
t.Run("list payment initiation adjustments by status", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("status", models.PAYMENT_INITIATION_ADJUSTMENT_STATUS_FAILED)),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiationAdjustments(t, defaultPaymentInitiationAdjustments()[1], cursor.Data[0])
})
t.Run("wrong query builder operator when listing by metadata", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Lt("metadata[foo]", "bar")),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.Error(t, err)
require.Nil(t, cursor)
})
t.Run("list payment initiation adjustments by metadata", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("metadata[foo]", "bar")),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
comparePaymentInitiationAdjustments(t, defaultPaymentInitiationAdjustments()[0], cursor.Data[0])
})
t.Run("unknown query builder key when listing", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(15).
WithQueryBuilder(query.Match("unknown", "test1")),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.Error(t, err)
require.Nil(t, cursor)
})
t.Run("list payment initiation adjustments by payment initiation", func(t *testing.T) {
q := NewListPaymentInitiationAdjustmentsQuery(
bunpaginate.NewPaginatedQueryOptions(PaymentInitiationAdjustmentsQuery{}).
WithPageSize(1),
)
cursor, err := store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiationAdjustments(t, defaultPaymentInitiationAdjustments()[1], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Next, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.False(t, cursor.HasMore)
require.NotEmpty(t, cursor.Previous)
require.Empty(t, cursor.Next)
comparePaymentInitiationAdjustments(t, defaultPaymentInitiationAdjustments()[0], cursor.Data[0])
err = bunpaginate.UnmarshalCursor(cursor.Previous, &q)
require.NoError(t, err)
cursor, err = store.PaymentInitiationAdjustmentsList(ctx, defaultPaymentInitiationAdjustments()[0].ID.PaymentInitiationID, q)
require.NoError(t, err)
require.Len(t, cursor.Data, 1)
require.True(t, cursor.HasMore)
require.Empty(t, cursor.Previous)
require.NotEmpty(t, cursor.Next)
comparePaymentInitiationAdjustments(t, defaultPaymentInitiationAdjustments()[1], cursor.Data[0])
})
}
func comparePaymentInitiations(t *testing.T, expected, actual models.PaymentInitiation) {
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.ScheduledAt, actual.ScheduledAt)
require.Equal(t, expected.Description, actual.Description)
require.Equal(t, expected.Type, actual.Type)
switch {
case expected.SourceAccountID != nil && actual.SourceAccountID != nil:
require.Equal(t, *expected.SourceAccountID, *actual.SourceAccountID)
case expected.SourceAccountID == nil && actual.SourceAccountID == nil:
default:
t.Fatalf("expected.SourceAccountID != actual.SourceAccountID")
}
require.Equal(t, expected.DestinationAccountID, actual.DestinationAccountID)
require.Equal(t, expected.Amount, actual.Amount)
require.Equal(t, expected.Asset, actual.Asset)
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])
}
}
func comparePaymentInitiationAdjustments(t *testing.T, expected, actual models.PaymentInitiationAdjustment) {
require.Equal(t, expected.ID, actual.ID)
require.Equal(t, expected.CreatedAt, actual.CreatedAt)
require.Equal(t, expected.Status, actual.Status)
switch {
case expected.Error != nil && actual.Error != nil:
require.Equal(t, expected.Error.Error(), actual.Error.Error())
case expected.Error == nil && actual.Error == nil:
default:
t.Fatalf("expected.Error != actual.Error")
}
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])
}
}