package services import ( "database/sql" "testing" "time" "quyun/v2/app/commands/testx" 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_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) }) }