Files
zhuangang/topics/backend/postgresql/examples/order-state/order_test.go
T

120 lines
2.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package orderstate
import (
"context"
"errors"
"fmt"
"os"
"sync"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
// 需要 PostgreSQL:TEST_DATABASE_URL=postgres://user:pass@localhost:5432/db?sslmode=disable go test ./...
func setup(t *testing.T) *pgxpool.Pool {
t.Helper()
url := os.Getenv("TEST_DATABASE_URL")
if url == "" {
t.Skip("未设置 TEST_DATABASE_URL,跳过集成测试")
}
ctx := context.Background()
schema := fmt.Sprintf("t_order_%d", time.Now().UnixNano())
admin, err := pgxpool.New(ctx, url)
if err != nil {
t.Fatal(err)
}
if _, err := admin.Exec(ctx, "CREATE SCHEMA "+schema); err != nil {
t.Fatal(err)
}
cfg, _ := pgxpool.ParseConfig(url)
cfg.ConnConfig.RuntimeParams["search_path"] = schema // 每次测试用独立的 schema
db, err := pgxpool.NewWithConfig(ctx, cfg)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
db.Close()
_, _ = admin.Exec(ctx, "DROP SCHEMA "+schema+" CASCADE")
admin.Close()
})
if _, err := db.Exec(ctx, Schema); err != nil {
t.Fatal(err)
}
return db
}
func newOrder(t *testing.T, db *pgxpool.Pool) Order {
t.Helper()
var id int64
if err := db.QueryRow(context.Background(), "INSERT INTO orders DEFAULT VALUES RETURNING id").Scan(&id); err != nil {
t.Fatal(err)
}
o, err := Get(context.Background(), db, id)
if err != nil {
t.Fatal(err)
}
return o
}
func TestAllowed(t *testing.T) {
if !allowed("pending", "paid") || allowed("pending", "shipped") || allowed("completed", "paid") {
t.Fatal("状态流转表不对")
}
}
func TestTransitionAndAudit(t *testing.T) {
db := setup(t)
ctx := context.Background()
o := newOrder(t, db)
o, err := Transition(ctx, db, o, "paid", "payment-callback", "")
if err != nil {
t.Fatal(err)
}
if _, err := Transition(ctx, db, o, "completed", "admin", ""); !errors.Is(err, ErrIllegal) {
t.Fatalf("paid -> completed 应该不允许,得到 %v", err)
}
var n int
_ = db.QueryRow(ctx, "SELECT count(*) FROM order_events WHERE order_id = $1", o.ID).Scan(&n)
if n != 1 {
t.Fatalf("审计日志条数 = %d", n)
}
}
func TestConcurrentCancelAndPay(t *testing.T) {
db := setup(t)
ctx := context.Background()
o := newOrder(t, db) // 两个操作读到的是同一个版本
var wg sync.WaitGroup
errs := make([]error, 2)
for i, to := range []string{"paid", "cancelled"} {
wg.Add(1)
go func() {
defer wg.Done()
_, errs[i] = Transition(ctx, db, o, to, "test", "")
}()
}
wg.Wait()
ok, conflict := 0, 0
for _, err := range errs {
switch {
case err == nil:
ok++
case errors.Is(err, ErrConflict):
conflict++
default:
t.Fatal(err)
}
}
if ok != 1 || conflict != 1 {
t.Fatalf("应该一个成功一个冲突,得到 ok=%d conflict=%d", ok, conflict)
}
cur, _ := Get(ctx, db, o.ID)
var n int
_ = db.QueryRow(ctx, "SELECT count(*) FROM order_events WHERE order_id = $1", o.ID).Scan(&n)
if cur.Version != 2 || n != 1 {
t.Fatalf("版本 %d,审计 %d 条", cur.Version, n)
}
}