691 lines
22 KiB
Go
691 lines
22 KiB
Go
package services
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"quyun/v2/app/commands/testx"
|
|
"quyun/v2/app/errorx"
|
|
super_dto "quyun/v2/app/http/super/v1/dto"
|
|
"quyun/v2/app/requests"
|
|
"quyun/v2/database"
|
|
"quyun/v2/database/models"
|
|
"quyun/v2/pkg/consts"
|
|
|
|
"github.com/samber/lo"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.ipao.vip/atom/contracts"
|
|
"go.ipao.vip/gen/types"
|
|
"go.uber.org/dig"
|
|
)
|
|
|
|
type SuperTestSuiteInjectParams struct {
|
|
dig.In
|
|
|
|
DB *sql.DB
|
|
Initials []contracts.Initial `group:"initials"`
|
|
}
|
|
|
|
type SuperTestSuite struct {
|
|
suite.Suite
|
|
SuperTestSuiteInjectParams
|
|
}
|
|
|
|
func Test_Super(t *testing.T) {
|
|
providers := testx.Default().With(Provide)
|
|
|
|
testx.Serve(providers, t, func(p SuperTestSuiteInjectParams) {
|
|
suite.Run(t, &SuperTestSuite{SuperTestSuiteInjectParams: p})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_ListUsers() {
|
|
Convey("ListUsers", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameUser)
|
|
|
|
u1 := &models.User{Username: "user1", Nickname: "Alice"}
|
|
u2 := &models.User{Username: "user2", Nickname: "Bob"}
|
|
models.UserQuery.WithContext(ctx).Create(u1, u2)
|
|
|
|
Convey("should list users", func() {
|
|
filter := &super_dto.UserListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListUsers(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 2)
|
|
|
|
items := res.Items.([]super_dto.UserItem)
|
|
So(items[0].Username, ShouldEqual, "user2") // Desc order
|
|
})
|
|
|
|
Convey("should filter users", func() {
|
|
filter := &super_dto.UserListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
Username: lo.ToPtr("Alice"),
|
|
}
|
|
res, err := Super.ListUsers(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
items := res.Items.([]super_dto.UserItem)
|
|
So(items[0].Username, ShouldEqual, "user1")
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_LoginAndCheckToken() {
|
|
Convey("Login and CheckToken", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameUser)
|
|
|
|
admin := &models.User{
|
|
Username: "super_admin",
|
|
Password: "pass123",
|
|
Roles: types.Array[consts.Role]{consts.RoleSuperAdmin},
|
|
Status: consts.UserStatusVerified,
|
|
}
|
|
normal := &models.User{
|
|
Username: "normal_user",
|
|
Password: "pass123",
|
|
Status: consts.UserStatusVerified,
|
|
}
|
|
models.UserQuery.WithContext(ctx).Create(admin, normal)
|
|
|
|
Convey("should login as super admin", func() {
|
|
res, err := Super.Login(ctx, &super_dto.LoginForm{
|
|
Username: admin.Username,
|
|
Password: admin.Password,
|
|
})
|
|
So(err, ShouldBeNil)
|
|
So(res, ShouldNotBeNil)
|
|
So(res.Token, ShouldNotBeBlank)
|
|
So(res.User.ID, ShouldEqual, admin.ID)
|
|
})
|
|
|
|
Convey("should reject non-super admin", func() {
|
|
_, err := Super.Login(ctx, &super_dto.LoginForm{
|
|
Username: normal.Username,
|
|
Password: normal.Password,
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
})
|
|
|
|
Convey("should refresh token", func() {
|
|
loginRes, err := Super.Login(ctx, &super_dto.LoginForm{
|
|
Username: admin.Username,
|
|
Password: admin.Password,
|
|
})
|
|
So(err, ShouldBeNil)
|
|
|
|
token := "Bearer " + loginRes.Token
|
|
checkRes, err := Super.CheckToken(ctx, token)
|
|
So(err, ShouldBeNil)
|
|
So(checkRes, ShouldNotBeNil)
|
|
So(checkRes.Token, ShouldNotBeBlank)
|
|
So(checkRes.User.ID, ShouldEqual, admin.ID)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_CreateTenant() {
|
|
Convey("CreateTenant", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant)
|
|
|
|
u := &models.User{Username: "admin1"}
|
|
models.UserQuery.WithContext(ctx).Create(u)
|
|
|
|
Convey("should create tenant", func() {
|
|
form := &super_dto.TenantCreateForm{
|
|
Name: "Super Tenant",
|
|
Code: "st1",
|
|
AdminUserID: u.ID,
|
|
}
|
|
err := Super.CreateTenant(ctx, form)
|
|
So(err, ShouldBeNil)
|
|
|
|
t, _ := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.Code.Eq("st1")).First()
|
|
So(t, ShouldNotBeNil)
|
|
So(t.Name, ShouldEqual, "Super Tenant")
|
|
So(t.UserID, ShouldEqual, u.ID)
|
|
So(t.Status, ShouldEqual, consts.TenantStatusVerified)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_WithdrawalApproval() {
|
|
Convey("Withdrawal Approval", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameUser, models.TableNameTenantLedger)
|
|
|
|
u := &models.User{Username: "user_w", Balance: 1000} // Initial 10.00
|
|
admin := &models.User{Username: "admin_w"}
|
|
models.UserQuery.WithContext(ctx).Create(u, admin)
|
|
|
|
// Create Withdrawal Order (Pending)
|
|
o1 := &models.Order{
|
|
UserID: u.ID,
|
|
Type: consts.OrderTypeWithdrawal,
|
|
Status: consts.OrderStatusCreated,
|
|
AmountPaid: 500,
|
|
}
|
|
models.OrderQuery.WithContext(ctx).Create(o1)
|
|
|
|
Convey("should list withdrawals", func() {
|
|
filter := &super_dto.SuperOrderListFilter{Pagination: requests.Pagination{Page: 1, Limit: 10}}
|
|
res, err := Super.ListWithdrawals(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
})
|
|
|
|
Convey("should approve withdrawal", func() {
|
|
err := Super.ApproveWithdrawal(ctx, admin.ID, o1.ID)
|
|
So(err, ShouldBeNil)
|
|
|
|
oReload, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o1.ID)).First()
|
|
So(oReload.Status, ShouldEqual, consts.OrderStatusPaid)
|
|
})
|
|
|
|
Convey("should reject withdrawal and refund", func() {
|
|
// Another order
|
|
o2 := &models.Order{
|
|
UserID: u.ID,
|
|
Type: consts.OrderTypeWithdrawal,
|
|
Status: consts.OrderStatusCreated,
|
|
AmountPaid: 200,
|
|
}
|
|
models.OrderQuery.WithContext(ctx).Create(o2)
|
|
|
|
// Assuming user balance was deducted when o2 was created (logic in creator service)
|
|
// But here we set balance manually to 1000. Let's assume it was 1200 before.
|
|
// Current balance 1000. Refund 200 -> Expect 1200.
|
|
|
|
err := Super.RejectWithdrawal(ctx, admin.ID, o2.ID, "Invalid account")
|
|
So(err, ShouldBeNil)
|
|
|
|
oReload, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o2.ID)).First()
|
|
So(oReload.Status, ShouldEqual, consts.OrderStatusFailed)
|
|
|
|
uReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(u.ID)).First()
|
|
So(uReload.Balance, ShouldEqual, 1200)
|
|
|
|
// Check Ledger
|
|
l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o2.ID)).First()
|
|
So(l, ShouldNotBeNil)
|
|
So(l.Type, ShouldEqual, consts.TenantLedgerTypeAdjustment)
|
|
So(l.OperatorUserID, ShouldEqual, admin.ID)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_CommentGovernance() {
|
|
Convey("Comment Governance", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameComment, models.TableNameContent, models.TableNameTenant, models.TableNameUser)
|
|
|
|
owner := &models.User{Username: "owner_comment"}
|
|
commenter := &models.User{Username: "commenter"}
|
|
admin := &models.User{Username: "admin_comment"}
|
|
models.UserQuery.WithContext(ctx).Create(owner, commenter, admin)
|
|
|
|
tenant := &models.Tenant{UserID: owner.ID, Code: "t-comment", Name: "Comment Tenant", Status: consts.TenantStatusVerified}
|
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
|
|
|
content := &models.Content{
|
|
TenantID: tenant.ID,
|
|
UserID: owner.ID,
|
|
Title: "Comment Content",
|
|
Description: "Desc",
|
|
}
|
|
models.ContentQuery.WithContext(ctx).Create(content)
|
|
|
|
Convey("should list comments", func() {
|
|
comment := &models.Comment{
|
|
TenantID: tenant.ID,
|
|
UserID: commenter.ID,
|
|
ContentID: content.ID,
|
|
Content: "Nice work",
|
|
}
|
|
models.CommentQuery.WithContext(ctx).Create(comment)
|
|
|
|
filter := &super_dto.SuperCommentListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListComments(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
items := res.Items.([]super_dto.SuperCommentItem)
|
|
So(items[0].ContentTitle, ShouldEqual, "Comment Content")
|
|
So(items[0].Username, ShouldEqual, commenter.Username)
|
|
})
|
|
|
|
Convey("should delete comment", func() {
|
|
comment := &models.Comment{
|
|
TenantID: tenant.ID,
|
|
UserID: commenter.ID,
|
|
ContentID: content.ID,
|
|
Content: "Spam content",
|
|
}
|
|
models.CommentQuery.WithContext(ctx).Create(comment)
|
|
|
|
err := Super.DeleteComment(ctx, admin.ID, comment.ID, &super_dto.SuperCommentDeleteForm{Reason: "spam"})
|
|
So(err, ShouldBeNil)
|
|
|
|
deleted, err := models.CommentQuery.WithContext(ctx).Unscoped().Where(models.CommentQuery.ID.Eq(comment.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(deleted.DeletedAt.Valid, ShouldBeTrue)
|
|
|
|
filter := &super_dto.SuperCommentListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListComments(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 0)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_ContentReportGovernance() {
|
|
Convey("Content Report Governance", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameContentReport, models.TableNameContent, models.TableNameTenant, models.TableNameUser)
|
|
|
|
owner := &models.User{Username: "owner_report"}
|
|
reporter := &models.User{Username: "reporter"}
|
|
admin := &models.User{Username: "admin_report"}
|
|
models.UserQuery.WithContext(ctx).Create(owner, reporter, admin)
|
|
|
|
tenant := &models.Tenant{UserID: owner.ID, Code: "t-report", Name: "Report Tenant", Status: consts.TenantStatusVerified}
|
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
|
|
|
content := &models.Content{
|
|
TenantID: tenant.ID,
|
|
UserID: owner.ID,
|
|
Title: "Report Content",
|
|
Description: "Report Desc",
|
|
Status: consts.ContentStatusPublished,
|
|
}
|
|
models.ContentQuery.WithContext(ctx).Create(content)
|
|
|
|
Convey("should list reports", func() {
|
|
report := &models.ContentReport{
|
|
TenantID: tenant.ID,
|
|
ContentID: content.ID,
|
|
ReporterID: reporter.ID,
|
|
Reason: "spam",
|
|
Detail: "内容涉嫌违规",
|
|
Status: "pending",
|
|
}
|
|
models.ContentReportQuery.WithContext(ctx).Create(report)
|
|
|
|
filter := &super_dto.SuperContentReportListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListContentReports(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
items := res.Items.([]super_dto.SuperContentReportItem)
|
|
So(items[0].ContentTitle, ShouldEqual, "Report Content")
|
|
So(items[0].ReporterName, ShouldEqual, reporter.Username)
|
|
So(items[0].Status, ShouldEqual, "pending")
|
|
})
|
|
|
|
Convey("should process report and block content", func() {
|
|
report := &models.ContentReport{
|
|
TenantID: tenant.ID,
|
|
ContentID: content.ID,
|
|
ReporterID: reporter.ID,
|
|
Reason: "abuse",
|
|
Detail: "严重违规",
|
|
Status: "pending",
|
|
}
|
|
models.ContentReportQuery.WithContext(ctx).Create(report)
|
|
|
|
err := Super.ProcessContentReport(ctx, admin.ID, report.ID, &super_dto.SuperContentReportProcessForm{
|
|
Action: "approve",
|
|
ContentAction: "block",
|
|
Reason: "违规属实",
|
|
})
|
|
So(err, ShouldBeNil)
|
|
|
|
reloaded, err := models.ContentReportQuery.WithContext(ctx).Where(models.ContentReportQuery.ID.Eq(report.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.Status, ShouldEqual, "approved")
|
|
So(reloaded.HandledBy, ShouldEqual, admin.ID)
|
|
So(reloaded.HandledAction, ShouldEqual, "block")
|
|
So(reloaded.HandledReason, ShouldEqual, "违规属实")
|
|
So(reloaded.HandledAt.IsZero(), ShouldBeFalse)
|
|
|
|
contentReload, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(contentReload.Status, ShouldEqual, consts.ContentStatusBlocked)
|
|
})
|
|
|
|
Convey("should reject report without content action", func() {
|
|
report := &models.ContentReport{
|
|
TenantID: tenant.ID,
|
|
ContentID: content.ID,
|
|
ReporterID: reporter.ID,
|
|
Reason: "other",
|
|
Detail: "误报",
|
|
Status: "pending",
|
|
}
|
|
models.ContentReportQuery.WithContext(ctx).Create(report)
|
|
|
|
err := Super.ProcessContentReport(ctx, admin.ID, report.ID, &super_dto.SuperContentReportProcessForm{
|
|
Action: "reject",
|
|
Reason: "证据不足",
|
|
})
|
|
So(err, ShouldBeNil)
|
|
|
|
reloaded, err := models.ContentReportQuery.WithContext(ctx).Where(models.ContentReportQuery.ID.Eq(report.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.Status, ShouldEqual, "rejected")
|
|
So(reloaded.HandledBy, ShouldEqual, admin.ID)
|
|
So(reloaded.HandledAction, ShouldEqual, "ignore")
|
|
So(reloaded.HandledReason, ShouldEqual, "证据不足")
|
|
|
|
contentReload, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(contentReload.Status, ShouldEqual, consts.ContentStatusPublished)
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_FinanceAnomalies() {
|
|
Convey("Finance Anomalies", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameTenant, models.TableNameUser)
|
|
|
|
user := &models.User{Username: "finance_user", Balance: -100}
|
|
models.UserQuery.WithContext(ctx).Create(user)
|
|
|
|
tenant := &models.Tenant{UserID: user.ID, Code: "t-fin", Name: "Finance Tenant", Status: consts.TenantStatusVerified}
|
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
|
|
|
order := &models.Order{
|
|
TenantID: tenant.ID,
|
|
UserID: user.ID,
|
|
Type: consts.OrderTypeRecharge,
|
|
Status: consts.OrderStatusPaid,
|
|
AmountOriginal: 100,
|
|
AmountDiscount: 0,
|
|
AmountPaid: 100,
|
|
IdempotencyKey: "anomaly-paid",
|
|
}
|
|
models.OrderQuery.WithContext(ctx).Create(order)
|
|
|
|
Convey("should list balance anomalies", func() {
|
|
filter := &super_dto.SuperBalanceAnomalyFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListBalanceAnomalies(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
items := res.Items.([]super_dto.SuperBalanceAnomalyItem)
|
|
So(items[0].UserID, ShouldEqual, user.ID)
|
|
So(items[0].Issue, ShouldEqual, "negative_balance")
|
|
})
|
|
|
|
Convey("should list order anomalies", func() {
|
|
filter := &super_dto.SuperOrderAnomalyFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.ListOrderAnomalies(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 1)
|
|
items := res.Items.([]super_dto.SuperOrderAnomalyItem)
|
|
So(items[0].OrderID, ShouldEqual, order.ID)
|
|
So(items[0].Issue, ShouldEqual, "missing_paid_at")
|
|
})
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_TenantHealth() {
|
|
Convey("TenantHealth", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(
|
|
ctx,
|
|
s.DB,
|
|
models.TableNameUser,
|
|
models.TableNameTenant,
|
|
models.TableNameTenantUser,
|
|
models.TableNameContent,
|
|
models.TableNameOrder,
|
|
)
|
|
|
|
owner1 := &models.User{Username: "health_owner_1"}
|
|
owner2 := &models.User{Username: "health_owner_2"}
|
|
models.UserQuery.WithContext(ctx).Create(owner1, owner2)
|
|
|
|
tenant1 := &models.Tenant{
|
|
UserID: owner1.ID,
|
|
Name: "Health Tenant 1",
|
|
Code: "health1",
|
|
Status: consts.TenantStatusVerified,
|
|
}
|
|
tenant2 := &models.Tenant{
|
|
UserID: owner2.ID,
|
|
Name: "Health Tenant 2",
|
|
Code: "health2",
|
|
Status: consts.TenantStatusVerified,
|
|
}
|
|
models.TenantQuery.WithContext(ctx).Create(tenant1, tenant2)
|
|
|
|
models.TenantUserQuery.WithContext(ctx).Create(
|
|
&models.TenantUser{TenantID: tenant1.ID, UserID: owner1.ID},
|
|
&models.TenantUser{TenantID: tenant2.ID, UserID: owner2.ID},
|
|
)
|
|
|
|
models.ContentQuery.WithContext(ctx).Create(
|
|
&models.Content{
|
|
TenantID: tenant1.ID,
|
|
UserID: owner1.ID,
|
|
Title: "Content H1",
|
|
Status: consts.ContentStatusPublished,
|
|
},
|
|
&models.Content{
|
|
TenantID: tenant2.ID,
|
|
UserID: owner2.ID,
|
|
Title: "Content H2",
|
|
Status: consts.ContentStatusPublished,
|
|
},
|
|
)
|
|
|
|
now := time.Now()
|
|
models.OrderQuery.WithContext(ctx).Create(
|
|
&models.Order{
|
|
TenantID: tenant1.ID,
|
|
UserID: owner1.ID,
|
|
Type: consts.OrderTypeContentPurchase,
|
|
Status: consts.OrderStatusPaid,
|
|
AmountPaid: 1000,
|
|
PaidAt: now,
|
|
},
|
|
&models.Order{
|
|
TenantID: tenant2.ID,
|
|
UserID: owner2.ID,
|
|
Type: consts.OrderTypeContentPurchase,
|
|
Status: consts.OrderStatusPaid,
|
|
AmountPaid: 1000,
|
|
PaidAt: now,
|
|
},
|
|
&models.Order{
|
|
TenantID: tenant2.ID,
|
|
UserID: owner2.ID,
|
|
Type: consts.OrderTypeContentPurchase,
|
|
Status: consts.OrderStatusRefunded,
|
|
AmountPaid: 1000,
|
|
UpdatedAt: now,
|
|
},
|
|
)
|
|
|
|
filter := &super_dto.TenantListFilter{
|
|
Pagination: requests.Pagination{Page: 1, Limit: 10},
|
|
}
|
|
res, err := Super.TenantHealth(ctx, filter)
|
|
So(err, ShouldBeNil)
|
|
So(res.Total, ShouldEqual, 2)
|
|
|
|
items := res.Items.([]super_dto.TenantHealthItem)
|
|
itemMap := make(map[int64]super_dto.TenantHealthItem, len(items))
|
|
for _, item := range items {
|
|
itemMap[item.TenantID] = item
|
|
}
|
|
|
|
So(itemMap[tenant1.ID].PaidOrders, ShouldEqual, 1)
|
|
So(itemMap[tenant1.ID].RefundOrders, ShouldEqual, 0)
|
|
So(itemMap[tenant1.ID].HealthLevel, ShouldEqual, "healthy")
|
|
|
|
So(itemMap[tenant2.ID].PaidOrders, ShouldEqual, 1)
|
|
So(itemMap[tenant2.ID].RefundOrders, ShouldEqual, 1)
|
|
So(itemMap[tenant2.ID].HealthLevel, ShouldEqual, "risk")
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_ContentReview() {
|
|
Convey("ContentReview", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant, models.TableNameContent)
|
|
|
|
admin := &models.User{Username: "review_admin"}
|
|
owner := &models.User{Username: "review_owner"}
|
|
models.UserQuery.WithContext(ctx).Create(admin, owner)
|
|
|
|
tenant := &models.Tenant{
|
|
UserID: owner.ID,
|
|
Name: "Review Tenant",
|
|
Code: "review",
|
|
Status: consts.TenantStatusVerified,
|
|
}
|
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
|
|
|
content := &models.Content{
|
|
TenantID: tenant.ID,
|
|
UserID: owner.ID,
|
|
Title: "Review Content",
|
|
Status: consts.ContentStatusReviewing,
|
|
}
|
|
models.ContentQuery.WithContext(ctx).Create(content)
|
|
|
|
err := Super.ReviewContent(ctx, admin.ID, content.ID, &super_dto.SuperContentReviewForm{
|
|
Action: "approve",
|
|
})
|
|
So(err, ShouldBeNil)
|
|
|
|
reloaded, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content.ID)).First()
|
|
So(reloaded.Status, ShouldEqual, consts.ContentStatusPublished)
|
|
So(reloaded.PublishedAt.IsZero(), ShouldBeFalse)
|
|
|
|
content2 := &models.Content{
|
|
TenantID: tenant.ID,
|
|
UserID: owner.ID,
|
|
Title: "Review Content 2",
|
|
Status: consts.ContentStatusReviewing,
|
|
}
|
|
models.ContentQuery.WithContext(ctx).Create(content2)
|
|
|
|
err = Super.ReviewContent(ctx, admin.ID, content2.ID, &super_dto.SuperContentReviewForm{
|
|
Action: "reject",
|
|
Reason: "Policy violation",
|
|
})
|
|
So(err, ShouldBeNil)
|
|
|
|
reloaded2, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content2.ID)).First()
|
|
So(reloaded2.Status, ShouldEqual, consts.ContentStatusBlocked)
|
|
})
|
|
}
|
|
|
|
func (s *SuperTestSuite) Test_OrderGovernance() {
|
|
Convey("OrderGovernance", s.T(), func() {
|
|
ctx := s.T().Context()
|
|
database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameAuditLog)
|
|
|
|
newOrder := func() *models.Order {
|
|
o := &models.Order{
|
|
TenantID: 1,
|
|
UserID: 2,
|
|
Type: consts.OrderTypeContentPurchase,
|
|
Status: consts.OrderStatusPaid,
|
|
AmountPaid: 100,
|
|
}
|
|
So(models.OrderQuery.WithContext(ctx).Create(o), ShouldBeNil)
|
|
return o
|
|
}
|
|
|
|
operatorID := int64(9001)
|
|
|
|
Convey("should require reason when flagging", func() {
|
|
o := newOrder()
|
|
err := Super.FlagOrder(ctx, operatorID, o.ID, &super_dto.SuperOrderFlagForm{
|
|
IsFlagged: true,
|
|
})
|
|
So(err, ShouldNotBeNil)
|
|
|
|
var appErr *errorx.AppError
|
|
So(errors.As(err, &appErr), ShouldBeTrue)
|
|
So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code)
|
|
})
|
|
|
|
Convey("should flag and unflag order", func() {
|
|
o := newOrder()
|
|
reason := "支付回调异常"
|
|
|
|
So(Super.FlagOrder(ctx, operatorID, o.ID, &super_dto.SuperOrderFlagForm{
|
|
IsFlagged: true,
|
|
Reason: reason,
|
|
}), ShouldBeNil)
|
|
|
|
reloaded, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.IsFlagged, ShouldBeTrue)
|
|
So(reloaded.FlagReason, ShouldEqual, reason)
|
|
So(reloaded.FlaggedBy, ShouldEqual, operatorID)
|
|
So(reloaded.FlaggedAt.IsZero(), ShouldBeFalse)
|
|
|
|
So(Super.FlagOrder(ctx, operatorID, o.ID, &super_dto.SuperOrderFlagForm{
|
|
IsFlagged: false,
|
|
}), ShouldBeNil)
|
|
|
|
reloaded, err = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.IsFlagged, ShouldBeFalse)
|
|
So(reloaded.FlagReason, ShouldEqual, "")
|
|
So(reloaded.FlaggedBy, ShouldEqual, int64(0))
|
|
So(reloaded.FlaggedAt.IsZero(), ShouldBeTrue)
|
|
})
|
|
|
|
Convey("should reconcile and unreconcile order", func() {
|
|
o := newOrder()
|
|
note := "对账完成"
|
|
|
|
So(Super.ReconcileOrder(ctx, operatorID, o.ID, &super_dto.SuperOrderReconcileForm{
|
|
IsReconciled: true,
|
|
Note: note,
|
|
}), ShouldBeNil)
|
|
|
|
reloaded, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.IsReconciled, ShouldBeTrue)
|
|
So(reloaded.ReconcileNote, ShouldEqual, note)
|
|
So(reloaded.ReconciledBy, ShouldEqual, operatorID)
|
|
So(reloaded.ReconciledAt.IsZero(), ShouldBeFalse)
|
|
|
|
So(Super.ReconcileOrder(ctx, operatorID, o.ID, &super_dto.SuperOrderReconcileForm{
|
|
IsReconciled: false,
|
|
}), ShouldBeNil)
|
|
|
|
reloaded, err = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
|
|
So(err, ShouldBeNil)
|
|
So(reloaded.IsReconciled, ShouldBeFalse)
|
|
So(reloaded.ReconcileNote, ShouldEqual, "")
|
|
So(reloaded.ReconciledBy, ShouldEqual, int64(0))
|
|
So(reloaded.ReconciledAt.IsZero(), ShouldBeTrue)
|
|
})
|
|
})
|
|
}
|