feat: implement tenant-side creator audit feature and update related tests and documentation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -2,9 +2,11 @@ package services
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"quyun/v2/app/commands/testx"
|
||||
"quyun/v2/app/errorx"
|
||||
order_dto "quyun/v2/app/http/v1/dto"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/models"
|
||||
@@ -196,3 +198,137 @@ func (s *CouponTestSuite) Test_ListAvailable() {
|
||||
So(list[0].CouponID, ShouldEqual, cp.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CouponTestSuite) Test_Validate_DenyCrossTenantCoupon() {
|
||||
Convey("Validate should deny cross-tenant coupon", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(
|
||||
ctx,
|
||||
s.DB,
|
||||
models.TableNameCoupon,
|
||||
models.TableNameUserCoupon,
|
||||
models.TableNameUser,
|
||||
)
|
||||
|
||||
user := &models.User{Username: "coupon_cross_validate", Phone: "13800000011"}
|
||||
So(models.UserQuery.WithContext(ctx).Create(user), ShouldBeNil)
|
||||
|
||||
tenantA := int64(11)
|
||||
tenantB := int64(22)
|
||||
coupon := &models.Coupon{
|
||||
TenantID: tenantA,
|
||||
Title: "Tenant A Coupon",
|
||||
Type: consts.CouponTypeFixAmount,
|
||||
Value: 200,
|
||||
MinOrderAmount: 0,
|
||||
}
|
||||
So(models.CouponQuery.WithContext(ctx).Create(coupon), ShouldBeNil)
|
||||
|
||||
userCoupon := &models.UserCoupon{
|
||||
UserID: user.ID,
|
||||
CouponID: coupon.ID,
|
||||
Status: consts.UserCouponStatusUnused,
|
||||
}
|
||||
So(models.UserCouponQuery.WithContext(ctx).Create(userCoupon), ShouldBeNil)
|
||||
|
||||
_, err := Coupon.Validate(ctx, tenantB, user.ID, userCoupon.ID, 1000)
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var appErr *errorx.AppError
|
||||
So(errors.As(err, &appErr), ShouldBeTrue)
|
||||
So(appErr.Code, ShouldEqual, errorx.ErrForbidden.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CouponTestSuite) Test_MarkUsed_DenyCrossTenantCoupon() {
|
||||
Convey("MarkUsed should deny cross-tenant coupon", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(
|
||||
ctx,
|
||||
s.DB,
|
||||
models.TableNameCoupon,
|
||||
models.TableNameUserCoupon,
|
||||
models.TableNameOrder,
|
||||
models.TableNameUser,
|
||||
)
|
||||
|
||||
user := &models.User{Username: "coupon_cross_mark", Phone: "13800000012"}
|
||||
So(models.UserQuery.WithContext(ctx).Create(user), ShouldBeNil)
|
||||
|
||||
tenantA := int64(33)
|
||||
tenantB := int64(44)
|
||||
coupon := &models.Coupon{
|
||||
TenantID: tenantA,
|
||||
Title: "Tenant A Coupon",
|
||||
Type: consts.CouponTypeFixAmount,
|
||||
Value: 200,
|
||||
MinOrderAmount: 0,
|
||||
}
|
||||
So(models.CouponQuery.WithContext(ctx).Create(coupon), ShouldBeNil)
|
||||
|
||||
userCoupon := &models.UserCoupon{
|
||||
UserID: user.ID,
|
||||
CouponID: coupon.ID,
|
||||
Status: consts.UserCouponStatusUnused,
|
||||
}
|
||||
So(models.UserCouponQuery.WithContext(ctx).Create(userCoupon), ShouldBeNil)
|
||||
|
||||
order := &models.Order{
|
||||
TenantID: tenantA,
|
||||
UserID: user.ID,
|
||||
Type: consts.OrderTypeContentPurchase,
|
||||
Status: consts.OrderStatusCreated,
|
||||
}
|
||||
So(models.OrderQuery.WithContext(ctx).Create(order), ShouldBeNil)
|
||||
|
||||
err := models.Q.Transaction(func(tx *models.Query) error {
|
||||
return Coupon.MarkUsed(ctx, tx, tenantB, userCoupon.ID, order.ID)
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
var appErr *errorx.AppError
|
||||
So(errors.As(err, &appErr), ShouldBeTrue)
|
||||
So(appErr.Code, ShouldEqual, errorx.ErrForbidden.Code)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *CouponTestSuite) Test_Grant_DenyCrossTenantCoupon() {
|
||||
Convey("Grant should reject coupon from another tenant", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(
|
||||
ctx,
|
||||
s.DB,
|
||||
models.TableNameCoupon,
|
||||
models.TableNameUserCoupon,
|
||||
models.TableNameUser,
|
||||
)
|
||||
|
||||
user := &models.User{Username: "coupon_cross_grant", Phone: "13800000013"}
|
||||
So(models.UserQuery.WithContext(ctx).Create(user), ShouldBeNil)
|
||||
|
||||
tenantA := int64(55)
|
||||
tenantB := int64(66)
|
||||
coupon := &models.Coupon{
|
||||
TenantID: tenantA,
|
||||
Title: "Tenant A Coupon",
|
||||
Type: consts.CouponTypeFixAmount,
|
||||
Value: 200,
|
||||
MinOrderAmount: 0,
|
||||
}
|
||||
So(models.CouponQuery.WithContext(ctx).Create(coupon), ShouldBeNil)
|
||||
|
||||
granted, err := Coupon.Grant(ctx, tenantB, coupon.ID, []int64{user.ID})
|
||||
So(err, ShouldNotBeNil)
|
||||
So(granted, ShouldEqual, 0)
|
||||
|
||||
var appErr *errorx.AppError
|
||||
So(errors.As(err, &appErr), ShouldBeTrue)
|
||||
So(appErr.Code, ShouldEqual, errorx.ErrRecordNotFound.Code)
|
||||
|
||||
exists, err := models.UserCouponQuery.WithContext(ctx).
|
||||
Where(models.UserCouponQuery.UserID.Eq(user.ID), models.UserCouponQuery.CouponID.Eq(coupon.ID)).
|
||||
Exists()
|
||||
So(err, ShouldBeNil)
|
||||
So(exists, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user