diff --git a/components/ledger/internal/storage/ledgerstore/bucket.go b/components/ledger/internal/storage/ledgerstore/bucket.go index 703e93fd87..09fdab81cb 100644 --- a/components/ledger/internal/storage/ledgerstore/bucket.go +++ b/components/ledger/internal/storage/ledgerstore/bucket.go @@ -41,6 +41,9 @@ var addIndexOnReference string //go:embed migrations/7-add-ik-unique-index.sql var addIKUniqueIndex string +//go:embed migrations/8-ik-ledger-unique-index.sql +var updateIKUniqueIndex string + type Bucket struct { name string db *bun.DB @@ -200,6 +203,13 @@ func registerMigrations(migrator *migrations.Migrator, name string) { return err }, }, + migrations.Migration{ + Name: "Update unique index on IK", + UpWithContext: func(ctx context.Context, tx bun.Tx) error { + _, err := tx.ExecContext(ctx, updateIKUniqueIndex) + return err + }, + }, ) } diff --git a/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql b/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql new file mode 100644 index 0000000000..1093bf9c01 --- /dev/null +++ b/components/ledger/internal/storage/ledgerstore/migrations/8-ik-ledger-unique-index.sql @@ -0,0 +1,3 @@ +drop index logs_idempotency_key; + +create unique index logs_idempotency_key on logs (ledger, idempotency_key); \ No newline at end of file diff --git a/tests/integration/suite/ledger-create-transaction-ik.go b/tests/integration/suite/ledger-create-transaction-ik.go new file mode 100644 index 0000000000..ba626bb329 --- /dev/null +++ b/tests/integration/suite/ledger-create-transaction-ik.go @@ -0,0 +1,86 @@ +package suite + +import ( + "math/big" + "net/http" + "time" + + "github.com/formancehq/stack/tests/integration/internal/modules" + + "github.com/formancehq/formance-sdk-go/v2/pkg/models/operations" + "github.com/formancehq/formance-sdk-go/v2/pkg/models/shared" + "github.com/formancehq/stack/libs/go-libs/pointer" + . "github.com/formancehq/stack/tests/integration/internal" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = WithModules([]*Module{modules.Search, modules.Ledger}, func() { + BeforeEach(func() { + createLedgerResponse, err := Client().Ledger.V2CreateLedger(TestContext(), operations.V2CreateLedgerRequest{ + Ledger: "default", + }) + Expect(err).To(BeNil()) + Expect(createLedgerResponse.StatusCode).To(Equal(http.StatusNoContent)) + + createLedgerResponse, err = Client().Ledger.V2CreateLedger(TestContext(), operations.V2CreateLedgerRequest{ + Ledger: "test", + }) + Expect(err).To(BeNil()) + Expect(createLedgerResponse.StatusCode).To(Equal(http.StatusNoContent)) + }) + When("creating a transaction on a ledger", func() { + var ( + timestamp = time.Now().Add(-1 * time.Minute).Round(time.Second).UTC() + timestamp2 = time.Now().Round(time.Second).UTC() + ) + It("should fail", func() { + // Create a transaction + response, err := Client().Ledger.V2CreateTransaction( + TestContext(), + operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("foo"), + V2PostTransaction: shared.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []shared.V2Posting{ + { + Amount: big.NewInt(100), + Asset: "USD", + Source: "world", + Destination: "alice", + }, + }, + Timestamp: ×tamp, + Reference: pointer.For("foo"), + }, + Ledger: "default", + }, + ) + Expect(err).ToNot(HaveOccurred()) + Expect(response.StatusCode).To(Equal(200)) + + response, err = Client().Ledger.V2CreateTransaction( + TestContext(), + operations.V2CreateTransactionRequest{ + IdempotencyKey: pointer.For("foo"), + V2PostTransaction: shared.V2PostTransaction{ + Metadata: map[string]string{}, + Postings: []shared.V2Posting{ + { + Amount: big.NewInt(100), + Asset: "USD", + Source: "world", + Destination: "alice", + }, + }, + Timestamp: ×tamp2, + Reference: pointer.For("foo2"), + }, + Ledger: "test", + }, + ) + Expect(err).ToNot(HaveOccurred()) + Expect(response.StatusCode).To(Equal(200)) + }) + }) +})