gpt
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package mysqldao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/global"
|
||||
"git.lzcwy.cn/zhengchiliu/zt-closet-backend/internal/mysqlmodel"
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"go.opentelemetry.io/otel/trace/noop"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func newQuotaTestDAO(t *testing.T) (*QuotaGrantDao, sqlmock.Sqlmock) {
|
||||
t.Helper()
|
||||
conn, mock, err := sqlmock.New()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { conn.Close() })
|
||||
db, err := gorm.Open(mysql.New(mysql.Config{Conn: conn, SkipInitializeWithVersion: true}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
oldTracer := global.Tracer
|
||||
global.Tracer = noop.NewTracerProvider().Tracer("quota-test")
|
||||
t.Cleanup(func() { global.Tracer = oldTracer })
|
||||
return &QuotaGrantDao{db: db}, mock
|
||||
}
|
||||
|
||||
func TestMemberQuotaFirstPurchaseAndRenewal(t *testing.T) {
|
||||
// 每次请求都是新订单;已用完、过期和退款软删除的历史凭证仍代表已领取。
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
prior bool
|
||||
used, deleted int64
|
||||
want bool
|
||||
}{
|
||||
{name: "first_purchase", want: true},
|
||||
{name: "renewal_new_order", prior: true},
|
||||
{name: "exhausted_quota", prior: true, used: 100},
|
||||
{name: "refunded_purchase", prior: true, deleted: 123},
|
||||
{name: "compensation_retry", prior: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dao, mock := newQuotaTestDAO(t)
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectQuery("SELECT .* FROM `users` .*FOR UPDATE").WithArgs(7, 0, 1).
|
||||
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(7))
|
||||
rows := sqlmock.NewRows([]string{"id", "user_id", "quota_type", "source", "amount", "used", "deleted_at", "ref_id"})
|
||||
if tc.prior {
|
||||
rows.AddRow(1, 7, 2, 1, 100, tc.used, tc.deleted, "OLD-ORDER")
|
||||
}
|
||||
// 精确匹配确认按用户/商品/额度类型过滤,且没有 deleted_at/有效期/剩余量过滤。
|
||||
query := "SELECT quota_grants.* FROM `quota_grants` JOIN orders ON orders.order_no = quota_grants.ref_id AND orders.user_id = quota_grants.user_id WHERE quota_grants.user_id = ? AND orders.goods_id = ? AND quota_grants.quota_type = ? AND quota_grants.source = ? LIMIT ? FOR UPDATE"
|
||||
mock.ExpectQuery(regexp.QuoteMeta(query)).WithArgs(7, 9, 2, 1, 1).WillReturnRows(rows)
|
||||
if !tc.prior {
|
||||
mock.ExpectExec("INSERT INTO `quota_grants`").WillReturnResult(sqlmock.NewResult(2, 1))
|
||||
mock.ExpectExec("INSERT INTO `quota_flow_logs`").WillReturnResult(sqlmock.NewResult(3, 1))
|
||||
}
|
||||
mock.ExpectCommit()
|
||||
grant := &mysqlmodel.QuotaGrant{UserID: 7, QuotaType: 2, Amount: 100, Source: 1, RefID: "NEW-ORDER"}
|
||||
flow := &mysqlmodel.QuotaFlowLog{QuotaFlowUUID: "flow", UserID: 7, OrderNo: "NEW-ORDER", QuotaType: 2}
|
||||
created, err := dao.MemberQuotaGrantCreateWithFlow(context.Background(), 9, grant, flow)
|
||||
if err != nil || created != tc.want {
|
||||
t.Fatalf("created=%v err=%v", created, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberQuotaFailureRollsBack(t *testing.T) {
|
||||
for _, stage := range []string{"user_lock", "history_read", "grant_insert", "flow_insert"} {
|
||||
t.Run(stage, func(t *testing.T) {
|
||||
dao, mock := newQuotaTestDAO(t)
|
||||
failure := errors.New("database unavailable")
|
||||
mock.ExpectBegin()
|
||||
lock := mock.ExpectQuery("SELECT .* FROM `users` .*FOR UPDATE")
|
||||
if stage == "user_lock" {
|
||||
lock.WillReturnError(failure)
|
||||
} else {
|
||||
lock.WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(7))
|
||||
history := mock.ExpectQuery("SELECT quota_grants.* .*FOR UPDATE")
|
||||
if stage == "history_read" {
|
||||
history.WillReturnError(failure)
|
||||
} else {
|
||||
history.WillReturnRows(sqlmock.NewRows([]string{"id"}))
|
||||
insert := mock.ExpectExec("INSERT INTO `quota_grants`")
|
||||
if stage == "grant_insert" {
|
||||
insert.WillReturnError(failure)
|
||||
} else {
|
||||
insert.WillReturnResult(sqlmock.NewResult(2, 1))
|
||||
mock.ExpectExec("INSERT INTO `quota_flow_logs`").WillReturnError(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
mock.ExpectRollback()
|
||||
created, err := dao.MemberQuotaGrantCreateWithFlow(context.Background(), 9,
|
||||
&mysqlmodel.QuotaGrant{UserID: 7, QuotaType: 2, Amount: 100, Source: 1, RefID: "NEW"},
|
||||
&mysqlmodel.QuotaFlowLog{QuotaFlowUUID: "flow"})
|
||||
if created || !errors.Is(err, failure) {
|
||||
t.Fatalf("created=%v err=%v", created, err)
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddonQuotaStillGrantedPerOrder(t *testing.T) {
|
||||
dao, mock := newQuotaTestDAO(t)
|
||||
for _, order := range []string{"ADDON-1", "ADDON-2"} {
|
||||
mock.ExpectBegin()
|
||||
mock.ExpectExec("INSERT INTO `quota_grants`").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectExec("INSERT INTO `quota_flow_logs`").WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
mock.ExpectCommit()
|
||||
created, err := dao.QuotaGrantCreateWithFlow(context.Background(),
|
||||
&mysqlmodel.QuotaGrant{UserID: 7, QuotaType: 2, Source: 2, Amount: 100, RefID: order},
|
||||
&mysqlmodel.QuotaFlowLog{QuotaFlowUUID: order, OrderNo: order})
|
||||
if err != nil || !created {
|
||||
t.Fatalf("order=%s created=%v err=%v", order, created, err)
|
||||
}
|
||||
}
|
||||
if err := mock.ExpectationsWereMet(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user