package store import ( "context" "errors" "testing" "time" "github.com/tech/sendico/fx/storage" "github.com/tech/sendico/fx/storage/model" "github.com/tech/sendico/pkg/db/repository/builder" "github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/merrors" "go.uber.org/zap" ) func TestQuotesStoreIssue(t *testing.T) { ctx := context.Background() var inserted *model.Quote repo := &repoStub{ insertFn: func(_ context.Context, obj storable.Storable, _ builder.Query) error { inserted = cloneQuote(t, obj) return nil }, } store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: &txFactoryStub{}} quote := &model.Quote{QuoteRef: "q1"} if err := store.Issue(ctx, quote); err != nil { t.Fatalf("unexpected error: %v", err) } if inserted == nil || inserted.Status != model.QuoteStatusIssued { t.Fatalf("expected issued quote to be inserted") } } func TestQuotesStoreIssueSetsExpiryDate(t *testing.T) { ctx := context.Background() var inserted *model.Quote repo := &repoStub{ insertFn: func(_ context.Context, obj storable.Storable, _ builder.Query) error { inserted = cloneQuote(t, obj) return nil }, } store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: &txFactoryStub{}} expiry := time.Now().Add(2 * time.Minute).UnixMilli() quote := &model.Quote{ QuoteRef: "q1", ExpiresAtUnixMs: expiry, } if err := store.Issue(ctx, quote); err != nil { t.Fatalf("unexpected error: %v", err) } if inserted == nil || inserted.ExpiresAt == nil { t.Fatalf("expected expiry timestamp to be populated") } if inserted.ExpiresAt.UnixMilli() != expiry { t.Fatalf("expected expiry to equal %d, got %d", expiry, inserted.ExpiresAt.UnixMilli()) } } func TestQuotesStoreIssueInvalidInput(t *testing.T) { store := "esStore{logger: zap.NewNop(), repo: &repoStub{}, txFactory: &txFactoryStub{}} if err := store.Issue(context.Background(), nil); !errors.Is(err, merrors.ErrInvalidArg) { t.Fatalf("expected invalid argument error, got %v", err) } } func TestQuotesStoreConsumeSuccess(t *testing.T) { ctx := context.Background() now := time.Now() ledgerRef := "ledger-1" stored := &model.Quote{ QuoteRef: "q1", Firm: true, Status: model.QuoteStatusIssued, ExpiresAtUnixMs: now.Add(5 * time.Minute).UnixMilli(), } var updated *model.Quote repo := &repoStub{ findOneFn: func(_ context.Context, _ builder.Query, result storable.Storable) error { quote := result.(*model.Quote) *quote = *stored return nil }, updateFn: func(_ context.Context, obj storable.Storable) error { updated = cloneQuote(t, obj) return nil }, } factory := &txFactoryStub{} store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: factory} res, err := store.Consume(ctx, "q1", ledgerRef, now) if err != nil { t.Fatalf("unexpected error: %v", err) } if res == nil || res.Status != model.QuoteStatusConsumed { t.Fatalf("expected consumed quote") } if updated == nil || updated.ConsumedByLedgerTxnRef != ledgerRef { t.Fatalf("expected update with ledger ref") } } func TestQuotesStoreConsumeExpired(t *testing.T) { ctx := context.Background() stored := &model.Quote{ QuoteRef: "q1", Firm: true, Status: model.QuoteStatusIssued, ExpiresAtUnixMs: time.Now().Add(-time.Minute).UnixMilli(), } var updated *model.Quote repo := &repoStub{ findOneFn: func(_ context.Context, _ builder.Query, result storable.Storable) error { quote := result.(*model.Quote) *quote = *stored return nil }, updateFn: func(_ context.Context, obj storable.Storable) error { updated = cloneQuote(t, obj) return nil }, } factory := &txFactoryStub{} store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: factory} _, err := store.Consume(ctx, "q1", "ledger", time.Now()) if !errors.Is(err, storage.ErrQuoteExpired) { t.Fatalf("expected ErrQuoteExpired, got %v", err) } if updated == nil || updated.Status != model.QuoteStatusExpired { t.Fatalf("expected quote marked expired") } } func TestQuotesStoreExpireIssuedBefore(t *testing.T) { repo := &repoStub{ patchManyFn: func(context.Context, builder.Query, builder.Patch) (int, error) { return 3, nil }, } store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: &txFactoryStub{}} count, err := store.ExpireIssuedBefore(context.Background(), time.Now()) if err != nil { t.Fatalf("unexpected error: %v", err) } if count != 3 { t.Fatalf("expected 3 expired quotes, got %d", count) } } func TestQuotesStoreExpireZeroCutoff(t *testing.T) { store := "esStore{logger: zap.NewNop(), repo: &repoStub{}, txFactory: &txFactoryStub{}} if _, err := store.ExpireIssuedBefore(context.Background(), time.Time{}); !errors.Is(err, merrors.ErrInvalidArg) { t.Fatalf("expected invalid argument error") } } func TestQuotesStoreGetByRefNotFound(t *testing.T) { repo := &repoStub{ findOneFn: func(context.Context, builder.Query, storable.Storable) error { return merrors.ErrNoData }, } store := "esStore{logger: zap.NewNop(), repo: repo, txFactory: &txFactoryStub{}} if _, err := store.GetByRef(context.Background(), "missing"); !errors.Is(err, merrors.ErrNoData) { t.Fatalf("expected ErrNoData, got %v", err) } } func TestQuotesStoreGetByRefInvalid(t *testing.T) { store := "esStore{logger: zap.NewNop(), repo: &repoStub{}, txFactory: &txFactoryStub{}} if _, err := store.GetByRef(context.Background(), ""); !errors.Is(err, merrors.ErrInvalidArg) { t.Fatalf("expected invalid argument error") } }