Files
quyun-v2/backend/app/services/super_test.go

1410 lines
46 KiB
Go

package services
import (
"database/sql"
"encoding/json"
"errors"
"testing"
"time"
"quyun/v2/app/commands/testx"
"quyun/v2/app/errorx"
super_dto "quyun/v2/app/http/super/v1/dto"
v1_dto "quyun/v2/app/http/v1/dto"
"quyun/v2/app/requests"
"quyun/v2/database"
"quyun/v2/database/fields"
"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() {
startAt := time.Now()
form := &super_dto.TenantCreateForm{
Name: "Super Tenant",
Code: "st1",
AdminUserID: u.ID,
Duration: 7,
}
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)
So(t.ExpiredAt.After(startAt), ShouldBeTrue)
tu, _ := models.TenantUserQuery.WithContext(ctx).
Where(models.TenantUserQuery.TenantID.Eq(t.ID), models.TenantUserQuery.UserID.Eq(u.ID)).
First()
So(tu, ShouldNotBeNil)
So(tu.Status, ShouldEqual, consts.UserStatusVerified)
})
})
}
func (s *SuperTestSuite) Test_ListTenantsSortAggregates() {
Convey("ListTenants sort by aggregates", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant, models.TableNameTenantUser, models.TableNameOrder)
owner1 := &models.User{Username: "tenant_sort_owner_1"}
owner2 := &models.User{Username: "tenant_sort_owner_2"}
owner3 := &models.User{Username: "tenant_sort_owner_3"}
member := &models.User{Username: "tenant_sort_member"}
models.UserQuery.WithContext(ctx).Create(owner1, owner2, owner3, member)
tenant1 := &models.Tenant{UserID: owner1.ID, Code: "t-sort-1", Name: "Tenant Sort 1", Status: consts.TenantStatusVerified}
tenant2 := &models.Tenant{UserID: owner2.ID, Code: "t-sort-2", Name: "Tenant Sort 2", Status: consts.TenantStatusVerified}
tenant3 := &models.Tenant{UserID: owner3.ID, Code: "t-sort-3", Name: "Tenant Sort 3", Status: consts.TenantStatusVerified}
models.TenantQuery.WithContext(ctx).Create(tenant1, tenant2, tenant3)
models.TenantUserQuery.WithContext(ctx).Create(
&models.TenantUser{TenantID: tenant1.ID, UserID: owner1.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
&models.TenantUser{TenantID: tenant2.ID, UserID: owner2.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
&models.TenantUser{TenantID: tenant2.ID, UserID: member.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
&models.TenantUser{TenantID: tenant3.ID, UserID: owner3.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
&models.TenantUser{TenantID: tenant3.ID, UserID: owner1.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
&models.TenantUser{TenantID: tenant3.ID, UserID: member.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}},
)
models.OrderQuery.WithContext(ctx).Create(
&models.Order{
TenantID: tenant1.ID,
UserID: owner1.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountOriginal: 300,
AmountDiscount: 0,
AmountPaid: 300,
IdempotencyKey: "tenant-sort-income-1",
},
&models.Order{
TenantID: tenant2.ID,
UserID: owner2.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountOriginal: 100,
AmountDiscount: 0,
AmountPaid: 100,
IdempotencyKey: "tenant-sort-income-2",
},
&models.Order{
TenantID: tenant3.ID,
UserID: owner3.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountOriginal: 200,
AmountDiscount: 0,
AmountPaid: 200,
IdempotencyKey: "tenant-sort-income-3",
},
)
Convey("should sort by user_count asc", func() {
filter := &super_dto.TenantListFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Asc: lo.ToPtr("user_count"),
}
res, err := Super.ListTenants(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.TenantItem)
So(items[0].ID, ShouldEqual, tenant1.ID)
So(items[1].ID, ShouldEqual, tenant2.ID)
So(items[2].ID, ShouldEqual, tenant3.ID)
})
Convey("should sort by user_count desc", func() {
filter := &super_dto.TenantListFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Desc: lo.ToPtr("user_count"),
}
res, err := Super.ListTenants(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.TenantItem)
So(items[0].ID, ShouldEqual, tenant3.ID)
So(items[2].ID, ShouldEqual, tenant1.ID)
})
Convey("should sort by income_amount_paid_sum asc", func() {
filter := &super_dto.TenantListFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Asc: lo.ToPtr("income_amount_paid_sum"),
}
res, err := Super.ListTenants(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.TenantItem)
So(items[0].ID, ShouldEqual, tenant2.ID)
So(items[1].ID, ShouldEqual, tenant3.ID)
So(items[2].ID, ShouldEqual, tenant1.ID)
})
Convey("should sort by income_amount_paid_sum desc", func() {
filter := &super_dto.TenantListFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Desc: lo.ToPtr("income_amount_paid_sum"),
}
res, err := Super.ListTenants(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.TenantItem)
So(items[0].ID, ShouldEqual, tenant1.ID)
So(items[2].ID, ShouldEqual, tenant2.ID)
})
})
}
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_BatchProcessContentReports() {
Convey("BatchProcessContentReports", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameContentReport, models.TableNameContent, models.TableNameTenant, models.TableNameUser)
owner := &models.User{Username: "batch_report_owner"}
reporter := &models.User{Username: "batch_reporter"}
admin := &models.User{Username: "batch_report_admin"}
models.UserQuery.WithContext(ctx).Create(owner, reporter, admin)
tenant := &models.Tenant{UserID: owner.ID, Code: "t-report-batch", Name: "Report Batch Tenant", Status: consts.TenantStatusVerified}
models.TenantQuery.WithContext(ctx).Create(tenant)
contentA := &models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Batch Report A",
Status: consts.ContentStatusPublished,
}
contentB := &models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Batch Report B",
Status: consts.ContentStatusPublished,
}
models.ContentQuery.WithContext(ctx).Create(contentA, contentB)
Convey("should batch approve reports and unpublish contents", func() {
reportA := &models.ContentReport{
TenantID: tenant.ID,
ContentID: contentA.ID,
ReporterID: reporter.ID,
Reason: "spam",
Detail: "批量举报A",
Status: "pending",
}
reportB := &models.ContentReport{
TenantID: tenant.ID,
ContentID: contentB.ID,
ReporterID: reporter.ID,
Reason: "abuse",
Detail: "批量举报B",
Status: "pending",
}
models.ContentReportQuery.WithContext(ctx).Create(reportA, reportB)
err := Super.BatchProcessContentReports(ctx, admin.ID, &super_dto.SuperContentReportBatchProcessForm{
ReportIDs: []int64{reportA.ID, reportB.ID},
Action: "approve",
ContentAction: "unpublish",
Reason: "集中处理",
})
So(err, ShouldBeNil)
reloadedA, err := models.ContentReportQuery.WithContext(ctx).Where(models.ContentReportQuery.ID.Eq(reportA.ID)).First()
So(err, ShouldBeNil)
So(reloadedA.Status, ShouldEqual, "approved")
So(reloadedA.HandledBy, ShouldEqual, admin.ID)
So(reloadedA.HandledAction, ShouldEqual, "unpublish")
So(reloadedA.HandledReason, ShouldEqual, "集中处理")
So(reloadedA.HandledAt.IsZero(), ShouldBeFalse)
reloadedB, err := models.ContentReportQuery.WithContext(ctx).Where(models.ContentReportQuery.ID.Eq(reportB.ID)).First()
So(err, ShouldBeNil)
So(reloadedB.Status, ShouldEqual, "approved")
So(reloadedB.HandledAction, ShouldEqual, "unpublish")
contentReloadA, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(contentA.ID)).First()
So(err, ShouldBeNil)
So(contentReloadA.Status, ShouldEqual, consts.ContentStatusUnpublished)
contentReloadB, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(contentB.ID)).First()
So(err, ShouldBeNil)
So(contentReloadB.Status, ShouldEqual, consts.ContentStatusUnpublished)
})
Convey("should reject when report already handled", func() {
report := &models.ContentReport{
TenantID: tenant.ID,
ContentID: contentA.ID,
ReporterID: reporter.ID,
Reason: "other",
Detail: "已处理",
Status: "approved",
}
models.ContentReportQuery.WithContext(ctx).Create(report)
err := Super.BatchProcessContentReports(ctx, admin.ID, &super_dto.SuperContentReportBatchProcessForm{
ReportIDs: []int64{report.ID},
Action: "reject",
Reason: "重复提交",
})
So(err, ShouldNotBeNil)
var appErr *errorx.AppError
So(errors.As(err, &appErr), ShouldBeTrue)
So(appErr.Code, ShouldEqual, errorx.ErrStatusConflict.Code)
})
})
}
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_FinanceAnomalySorting() {
Convey("Finance Anomaly Sorting", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameTenant, models.TableNameUser)
now := time.Now().Truncate(time.Second)
userOld := &models.User{Username: "balance_old", Balance: -100, CreatedAt: now.Add(-2 * time.Hour)}
userNew := &models.User{Username: "balance_new", Balance: -200, CreatedAt: now.Add(-1 * time.Hour)}
models.UserQuery.WithContext(ctx).Create(userOld, userNew)
orderOwner := &models.User{Username: "order_anomaly_owner"}
models.UserQuery.WithContext(ctx).Create(orderOwner)
tenant := &models.Tenant{UserID: orderOwner.ID, Code: "t-anomaly-sort", Name: "Anomaly Sort Tenant", Status: consts.TenantStatusVerified}
models.TenantQuery.WithContext(ctx).Create(tenant)
order1 := &models.Order{
TenantID: tenant.ID,
UserID: orderOwner.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountOriginal: 100,
AmountDiscount: 0,
AmountPaid: 100,
IdempotencyKey: "anomaly-sort-1",
}
order2 := &models.Order{
TenantID: tenant.ID,
UserID: orderOwner.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountOriginal: 200,
AmountDiscount: 0,
AmountPaid: 200,
IdempotencyKey: "anomaly-sort-2",
}
models.OrderQuery.WithContext(ctx).Create(order1, order2)
Convey("should sort balance anomalies by created_at asc", func() {
filter := &super_dto.SuperBalanceAnomalyFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Asc: lo.ToPtr("created_at"),
}
res, err := Super.ListBalanceAnomalies(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.SuperBalanceAnomalyItem)
So(items[0].UserID, ShouldEqual, userOld.ID)
So(items[1].UserID, ShouldEqual, userNew.ID)
})
Convey("should sort balance anomalies by user_id desc", func() {
filter := &super_dto.SuperBalanceAnomalyFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Desc: lo.ToPtr("user_id"),
}
res, err := Super.ListBalanceAnomalies(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.SuperBalanceAnomalyItem)
So(items[0].UserID, ShouldEqual, userNew.ID)
})
Convey("should sort order anomalies by order_id asc", func() {
filter := &super_dto.SuperOrderAnomalyFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Asc: lo.ToPtr("order_id"),
}
res, err := Super.ListOrderAnomalies(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.SuperOrderAnomalyItem)
So(items[0].OrderID, ShouldEqual, order1.ID)
So(items[1].OrderID, ShouldEqual, order2.ID)
})
Convey("should sort order anomalies by order_id desc", func() {
filter := &super_dto.SuperOrderAnomalyFilter{
Pagination: requests.Pagination{Page: 1, Limit: 10},
Desc: lo.ToPtr("order_id"),
}
res, err := Super.ListOrderAnomalies(ctx, filter)
So(err, ShouldBeNil)
items := res.Items.([]super_dto.SuperOrderAnomalyItem)
So(items[0].OrderID, ShouldEqual, order2.ID)
})
})
}
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().Truncate(time.Second)
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_HealthOverview() {
Convey("HealthOverview", s.T(), func() {
ctx := s.T().Context()
database.Truncate(
ctx,
s.DB,
models.TableNameUser,
models.TableNameTenant,
models.TableNameTenantUser,
models.TableNameContent,
models.TableNameOrder,
models.TableNameMediaAsset,
)
owner := &models.User{Username: "health_overview_owner"}
models.UserQuery.WithContext(ctx).Create(owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Health Overview",
Code: "health_overview",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{
TenantID: tenant.ID,
UserID: owner.ID,
})
models.ContentQuery.WithContext(ctx).Create(&models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Health Content",
Status: consts.ContentStatusPublished,
})
now := time.Now()
models.OrderQuery.WithContext(ctx).Create(
&models.Order{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusPaid,
AmountPaid: 1000,
PaidAt: now,
CreatedAt: now,
},
&models.Order{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.OrderTypeContentPurchase,
Status: consts.OrderStatusFailed,
AmountPaid: 1000,
UpdatedAt: now,
CreatedAt: now,
},
)
models.MediaAssetQuery.WithContext(ctx).Create(
&models.MediaAsset{
TenantID: tenant.ID,
UserID: owner.ID,
ObjectKey: "failed.mp4",
Type: consts.MediaAssetTypeVideo,
Status: consts.MediaAssetStatusFailed,
CreatedAt: now,
UpdatedAt: now,
},
&models.MediaAsset{
TenantID: tenant.ID,
UserID: owner.ID,
ObjectKey: "ready.mp4",
Type: consts.MediaAssetTypeVideo,
Status: consts.MediaAssetStatusReady,
CreatedAt: now,
UpdatedAt: now,
},
&models.MediaAsset{
TenantID: tenant.ID,
UserID: owner.ID,
ObjectKey: "processing.mp4",
Type: consts.MediaAssetTypeVideo,
Status: consts.MediaAssetStatusProcessing,
CreatedAt: now.Add(-48 * time.Hour),
UpdatedAt: now.Add(-48 * time.Hour),
},
)
startAt := now.AddDate(0, 0, -7).Format(time.RFC3339)
endAt := now.Add(time.Second).Format(time.RFC3339)
filter := &super_dto.SuperHealthOverviewFilter{
TenantID: &tenant.ID,
StartAt: &startAt,
EndAt: &endAt,
UploadStuckHours: lo.ToPtr(int64(24)),
}
res, err := Super.HealthOverview(ctx, filter)
So(err, ShouldBeNil)
So(res.TenantTotal, ShouldEqual, 1)
So(res.OrderTotal, ShouldEqual, 2)
So(res.OrderFailed, ShouldEqual, 1)
So(res.UploadTotal, ShouldEqual, 3)
So(res.UploadFailed, ShouldEqual, 1)
So(res.UploadProcessingStuck, ShouldEqual, 1)
})
}
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_BatchUpdateContentStatus() {
Convey("BatchUpdateContentStatus", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant, models.TableNameContent)
admin := &models.User{Username: "batch_admin"}
owner := &models.User{Username: "batch_owner"}
models.UserQuery.WithContext(ctx).Create(admin, owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Batch Tenant",
Code: "batch",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
content1 := &models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Batch Content 1",
Status: consts.ContentStatusPublished,
}
content2 := &models.Content{
TenantID: tenant.ID,
UserID: owner.ID,
Title: "Batch Content 2",
Status: consts.ContentStatusUnpublished,
}
models.ContentQuery.WithContext(ctx).Create(content1, content2)
err := Super.BatchUpdateContentStatus(ctx, admin.ID, &super_dto.SuperContentBatchStatusForm{
ContentIDs: []int64{content1.ID, content2.ID},
Status: consts.ContentStatusBlocked,
Reason: "违规处理",
})
So(err, ShouldBeNil)
reloaded1, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content1.ID)).First()
reloaded2, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content2.ID)).First()
So(reloaded1.Status, ShouldEqual, consts.ContentStatusBlocked)
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)
})
})
}
func (s *SuperTestSuite) Test_PayoutAccountReview() {
Convey("PayoutAccountReview", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNamePayoutAccount, models.TableNameUser, models.TableNameTenant)
admin := &models.User{Username: "payout_admin"}
owner := &models.User{Username: "payout_owner"}
models.UserQuery.WithContext(ctx).Create(admin, owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Payout Tenant",
Code: "payout",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
account := &models.PayoutAccount{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.PayoutAccountTypeBank,
Name: "Bank",
Account: "123",
Realname: "Owner",
Status: consts.PayoutAccountStatusPending,
}
models.PayoutAccountQuery.WithContext(ctx).Create(account)
Convey("should approve payout account", func() {
err := Super.ReviewPayoutAccount(ctx, admin.ID, account.ID, &super_dto.SuperPayoutAccountReviewForm{
Action: "approve",
})
So(err, ShouldBeNil)
reloaded, _ := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.ID.Eq(account.ID)).First()
So(reloaded.Status, ShouldEqual, consts.PayoutAccountStatusApproved)
So(reloaded.ReviewedBy, ShouldEqual, admin.ID)
So(reloaded.ReviewedAt.IsZero(), ShouldBeFalse)
})
Convey("should require reason when rejecting", func() {
account2 := &models.PayoutAccount{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.PayoutAccountTypeAlipay,
Name: "Alipay",
Account: "user@example.com",
Realname: "Owner",
Status: consts.PayoutAccountStatusPending,
}
models.PayoutAccountQuery.WithContext(ctx).Create(account2)
err := Super.ReviewPayoutAccount(ctx, admin.ID, account2.ID, &super_dto.SuperPayoutAccountReviewForm{
Action: "reject",
})
So(err, ShouldNotBeNil)
var appErr *errorx.AppError
So(errors.As(err, &appErr), ShouldBeTrue)
So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code)
})
})
}
func (s *SuperTestSuite) Test_UpdateUserProfile() {
Convey("UpdateUserProfile", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameUser)
meta := types.JSON([]byte(`{"real_name":"Old Name","id_card":"ENC:1111"}`))
u := &models.User{
Username: "profile_user",
Nickname: "Old",
Avatar: "http://old-avatar",
Gender: consts.GenderSecret,
Bio: "old bio",
Metas: meta,
}
models.UserQuery.WithContext(ctx).Create(u)
Convey("should update profile fields and real-name meta", func() {
form := &super_dto.SuperUserProfileUpdateForm{
Nickname: lo.ToPtr("New Nick"),
Avatar: lo.ToPtr("http://new-avatar"),
Gender: lo.ToPtr(consts.GenderMale),
Bio: lo.ToPtr("new bio"),
IsRealNameVerified: lo.ToPtr(true),
RealName: lo.ToPtr("New Name"),
IDCard: lo.ToPtr("123456789012345678"),
}
err := Super.UpdateUserProfile(ctx, 1001, u.ID, form)
So(err, ShouldBeNil)
updated, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(u.ID)).First()
So(err, ShouldBeNil)
So(updated.Nickname, ShouldEqual, "New Nick")
So(updated.Avatar, ShouldEqual, "http://new-avatar")
So(updated.Gender, ShouldEqual, consts.GenderMale)
So(updated.Bio, ShouldEqual, "new bio")
So(updated.IsRealNameVerified, ShouldBeTrue)
So(updated.VerifiedAt.IsZero(), ShouldBeFalse)
metaMap := make(map[string]interface{})
So(json.Unmarshal(updated.Metas, &metaMap), ShouldBeNil)
So(metaMap["real_name"], ShouldEqual, "New Name")
So(metaMap["id_card"], ShouldEqual, "ENC:123456789012345678")
})
})
}
func (s *SuperTestSuite) Test_UpdateCreatorSettings() {
Convey("UpdateCreatorSettings", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameTenant, models.TableNameUser)
owner := &models.User{Username: "settings_owner"}
models.UserQuery.WithContext(ctx).Create(owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Old Tenant",
Code: "creator-settings",
Status: consts.TenantStatusVerified,
Config: types.NewJSONType(fields.TenantConfig{}),
}
models.TenantQuery.WithContext(ctx).Create(tenant)
form := &v1_dto.Settings{
Name: "New Tenant",
Bio: "new bio",
Avatar: "http://avatar",
Cover: "http://cover",
Description: "new description",
}
err := Super.UpdateCreatorSettings(ctx, 2001, tenant.ID, form)
So(err, ShouldBeNil)
updated, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenant.ID)).First()
So(err, ShouldBeNil)
So(updated.Name, ShouldEqual, "New Tenant")
cfg := updated.Config.Data()
So(cfg.Bio, ShouldEqual, "new bio")
So(cfg.Avatar, ShouldEqual, "http://avatar")
So(cfg.Cover, ShouldEqual, "http://cover")
So(cfg.Description, ShouldEqual, "new description")
})
}
func (s *SuperTestSuite) Test_PayoutAccountCreateUpdate() {
Convey("PayoutAccountCreateUpdate", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNamePayoutAccount, models.TableNameUser, models.TableNameTenant)
admin := &models.User{Username: "payout_admin_2"}
owner := &models.User{Username: "payout_owner_2"}
models.UserQuery.WithContext(ctx).Create(admin, owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Payout Tenant 2",
Code: "payout-2",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
Convey("should create payout account", func() {
err := Super.CreatePayoutAccount(ctx, admin.ID, tenant.ID, &super_dto.SuperPayoutAccountCreateForm{
UserID: owner.ID,
Type: "bank",
Name: "Bank",
Account: "123",
Realname: "Owner",
})
So(err, ShouldBeNil)
account, err := models.PayoutAccountQuery.WithContext(ctx).
Where(models.PayoutAccountQuery.TenantID.Eq(tenant.ID), models.PayoutAccountQuery.UserID.Eq(owner.ID)).
First()
So(err, ShouldBeNil)
So(account.Status, ShouldEqual, consts.PayoutAccountStatusPending)
})
Convey("should reset status when updating approved account", func() {
account := &models.PayoutAccount{
TenantID: tenant.ID,
UserID: owner.ID,
Type: consts.PayoutAccountTypeBank,
Name: "Bank",
Account: "111",
Realname: "Owner",
Status: consts.PayoutAccountStatusApproved,
ReviewedBy: admin.ID,
ReviewReason: "ok",
ReviewedAt: time.Now(),
}
models.PayoutAccountQuery.WithContext(ctx).Create(account)
err := Super.UpdatePayoutAccount(ctx, admin.ID, account.ID, &super_dto.SuperPayoutAccountUpdateForm{
Account: lo.ToPtr("222"),
})
So(err, ShouldBeNil)
updated, err := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.ID.Eq(account.ID)).First()
So(err, ShouldBeNil)
So(updated.Account, ShouldEqual, "222")
So(updated.Status, ShouldEqual, consts.PayoutAccountStatusPending)
So(updated.ReviewedBy, ShouldEqual, int64(0))
So(updated.ReviewReason, ShouldEqual, "")
})
})
}
func (s *SuperTestSuite) Test_UpdateNotificationTemplate() {
Convey("UpdateNotificationTemplate", s.T(), func() {
ctx := s.T().Context()
database.Truncate(ctx, s.DB, models.TableNameNotificationTemplate, models.TableNameTenant, models.TableNameUser)
owner := &models.User{Username: "tmpl_owner"}
models.UserQuery.WithContext(ctx).Create(owner)
tenant := &models.Tenant{
UserID: owner.ID,
Name: "Template Tenant",
Code: "tmpl",
Status: consts.TenantStatusVerified,
}
models.TenantQuery.WithContext(ctx).Create(tenant)
tmpl := &models.NotificationTemplate{
TenantID: tenant.ID,
Name: "Old Template",
Type: consts.NotificationTypeSystem,
Title: "Old Title",
Content: "Old Content",
IsActive: true,
}
models.NotificationTemplateQuery.WithContext(ctx).Create(tmpl)
item, err := Super.UpdateNotificationTemplate(ctx, 3001, tmpl.ID, &super_dto.SuperNotificationTemplateUpdateForm{
Name: lo.ToPtr("New Template"),
Title: lo.ToPtr("New Title"),
Content: lo.ToPtr("New Content"),
IsActive: lo.ToPtr(false),
})
So(err, ShouldBeNil)
So(item.Name, ShouldEqual, "New Template")
So(item.IsActive, ShouldBeFalse)
updated, err := models.NotificationTemplateQuery.WithContext(ctx).Where(models.NotificationTemplateQuery.ID.Eq(tmpl.ID)).First()
So(err, ShouldBeNil)
So(updated.Title, ShouldEqual, "New Title")
So(updated.Content, ShouldEqual, "New Content")
So(updated.IsActive, ShouldBeFalse)
})
}