feat(server): webhook 薄壳化——保留 X-Pangolin HMAC 协议,铸码走库单事务原语(#codes-lib)
This commit is contained in:
@@ -3,9 +3,11 @@ package codes_test
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
libcodes "github.com/wangjia/codes"
|
||||
"github.com/wangjia/pangolin/server/internal/codes"
|
||||
)
|
||||
|
||||
@@ -113,6 +115,50 @@ func TestRedeemCreatesSubscription(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebhookMintsIntoLibTables verifies Store.MintOne (webhook 铸码核心):
|
||||
// lands in the shared lib's new tables, entitlement is a duration payload,
|
||||
// and a duplicate code_hash leaves no orphan batch row (the old
|
||||
// two-statement webhook implementation leaked one batch per dupe delivery).
|
||||
func TestWebhookMintsIntoLibTables(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
db := openMigratedSQLite(t)
|
||||
store := codes.NewStore(db)
|
||||
|
||||
plain, err := codes.GenerateCode()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
canonical := mustCanonical(t, plain)
|
||||
ent, err := libcodesEnt("pro", 30)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := store.MintOne(ctx, codes.Hash(canonical), ent, codes.ChannelStore, "webhook", "n1"); err != nil {
|
||||
t.Fatalf("MintOne: %v", err)
|
||||
}
|
||||
var batches, ccount int
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM codes_batches`).Scan(&batches); err != nil || batches != 1 {
|
||||
t.Fatalf("batches = %d (err=%v)", batches, err)
|
||||
}
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM codes WHERE code_hash=?`, codes.Hash(canonical)).Scan(&ccount); err != nil || ccount != 1 {
|
||||
t.Fatalf("codes = %d (err=%v)", ccount, err)
|
||||
}
|
||||
|
||||
// 同 hash 再灌:ErrDuplicate 且 batch 数不变(无孤儿——旧实现的 I2 缺陷在此修复)。
|
||||
err = store.MintOne(ctx, codes.Hash(canonical), ent, codes.ChannelStore, "webhook", "n2")
|
||||
if !errors.Is(err, codes.ErrDuplicate) {
|
||||
t.Fatalf("err = %v, want ErrDuplicate", err)
|
||||
}
|
||||
if err := db.QueryRow(`SELECT COUNT(*) FROM codes_batches`).Scan(&batches); err != nil || batches != 1 {
|
||||
t.Fatalf("batches after dup = %d, want 1 (no orphan)", batches)
|
||||
}
|
||||
}
|
||||
|
||||
// libcodesEnt: 测试侧小工具,避免测试文件 import 库包名与宿主包名混淆。
|
||||
func libcodesEnt(plan string, days int) (libcodes.Entitlement, error) {
|
||||
return libcodes.NewDurationEntitlement(plan, days)
|
||||
}
|
||||
|
||||
func mustCanonical(t *testing.T, code string) string {
|
||||
t.Helper()
|
||||
c, err := codes.Canonicalize(code)
|
||||
|
||||
Reference in New Issue
Block a user