package store import ( "context" "testing" "time" ) func openMem(t *testing.T) *Store { t.Helper() s, err := Open(":memory:") if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { _ = s.Close() }) return s } func mkOrder(no, user string, amount int64, addr string, now time.Time) *Order { return &Order{ OrderNo: no, UserRef: user, SKU: "pro", ExpectAmount: amount, Address: addr, Status: StatusPending, CreatedAt: now, ExpiresAt: now.Add(15 * time.Minute), } } func TestOrderRoundtripAndMarkPaidIdempotent(t *testing.T) { s := openMem(t) ctx := context.Background() now := time.Unix(1_700_000_000, 0) if err := s.CreateOrder(ctx, mkOrder("PAY1", "u1", 5_000017, "TADDR", now)); err != nil { t.Fatalf("create: %v", err) } got, err := s.GetOrder(ctx, "PAY1") if err != nil { t.Fatalf("get: %v", err) } if got.UserRef != "u1" || got.ExpectAmount != 5_000017 || got.Status != StatusPending { t.Fatalf("roundtrip: %+v", got) } ok, err := s.MarkPaid(ctx, "PAY1", "tx-a") if err != nil || !ok { t.Fatalf("first MarkPaid ok=%v err=%v", ok, err) } ok2, err := s.MarkPaid(ctx, "PAY1", "tx-b") if err != nil || ok2 { t.Fatalf("second MarkPaid ok=%v err=%v (want false)", ok2, err) } got, _ = s.GetOrder(ctx, "PAY1") if got.Status != StatusPaid || got.TxID != "tx-a" { t.Fatalf("after paid: %s / %s", got.Status, got.TxID) } } func TestActiveOrderByUser(t *testing.T) { s := openMem(t) ctx := context.Background() now := time.Unix(1_700_000_000, 0) _ = s.CreateOrder(ctx, mkOrder("PAY1", "u1", 100, "T", now)) o, err := s.ActiveOrderByUser(ctx, "u1") if err != nil || o.OrderNo != "PAY1" { t.Fatalf("u1 active: %v %v", o, err) } if _, err := s.ActiveOrderByUser(ctx, "u2"); err != ErrNotFound { t.Fatalf("u2 want ErrNotFound, got %v", err) } _, _ = s.MarkPaid(ctx, "PAY1", "tx") if _, err := s.ActiveOrderByUser(ctx, "u1"); err != ErrNotFound { t.Fatalf("paid should not be active: %v", err) } } func TestAmountRecentlyUsed(t *testing.T) { s := openMem(t) ctx := context.Background() now := time.Unix(1_700_000_000, 0) _ = s.CreateOrder(ctx, mkOrder("PAY1", "u1", 5_000017, "T", now)) since := now.Add(-30 * time.Minute).Unix() if used, _ := s.AmountRecentlyUsed(ctx, 5_000017, since); !used { t.Fatal("5_000017 should be recently used") } if used, _ := s.AmountRecentlyUsed(ctx, 5_000018, since); used { t.Fatal("5_000018 not used") } if used, _ := s.AmountRecentlyUsed(ctx, 5_000017, now.Add(time.Minute).Unix()); used { t.Fatal("outside window should be false") } } func TestTxHandledAndOrphan(t *testing.T) { s := openMem(t) ctx := context.Background() now := time.Unix(1_700_000_000, 0) _ = s.CreateOrder(ctx, mkOrder("PAY1", "u1", 100, "T", now)) if h, _ := s.TxHandled(ctx, "tx-x"); h { t.Fatal("tx-x should be unhandled") } _, _ = s.MarkPaid(ctx, "PAY1", "tx-x") if h, _ := s.TxHandled(ctx, "tx-x"); !h { t.Fatal("matched tx should be handled") } if err := s.RecordOrphan(ctx, "tx-o", "T", 999, now.Unix(), now); err != nil { t.Fatalf("orphan: %v", err) } if h, _ := s.TxHandled(ctx, "tx-o"); !h { t.Fatal("orphan tx should be handled") } if err := s.RecordOrphan(ctx, "tx-o", "T", 999, now.Unix(), now); err != nil { t.Fatalf("orphan idempotent: %v", err) } } func TestMarkExpired(t *testing.T) { s := openMem(t) ctx := context.Background() base := time.Unix(1_700_000_000, 0) _ = s.CreateOrder(ctx, &Order{OrderNo: "old", UserRef: "u1", SKU: "x", ExpectAmount: 1, Address: "T", Status: StatusPending, CreatedAt: base, ExpiresAt: base.Add(time.Minute)}) _ = s.CreateOrder(ctx, &Order{OrderNo: "new", UserRef: "u2", SKU: "x", ExpectAmount: 2, Address: "T", Status: StatusPending, CreatedAt: base, ExpiresAt: base.Add(time.Hour)}) n, err := s.MarkExpired(ctx, base.Add(10*time.Minute)) if err != nil || n != 1 { t.Fatalf("MarkExpired n=%d err=%v", n, err) } o1, _ := s.GetOrder(ctx, "old") o2, _ := s.GetOrder(ctx, "new") if o1.Status != StatusExpired || o2.Status != StatusPending { t.Fatalf("old=%s new=%s", o1.Status, o2.Status) } }