package services import ( "errors" "time" "quyun/v2/app/errorx" tenant_dto "quyun/v2/app/http/v1/dto" "quyun/v2/app/requests" "quyun/v2/database" "quyun/v2/database/models" "quyun/v2/pkg/consts" . "github.com/smartystreets/goconvey/convey" "go.ipao.vip/gen/types" ) func (s *TenantTestSuite) Test_ApplyJoin() { Convey("ApplyJoin", s.T(), func() { ctx := s.T().Context() setup := func() (*models.Tenant, *models.User, *models.User) { database.Truncate(ctx, s.DB, models.TableNameTenantJoinRequest, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_apply", Phone: "13900001001"} user := &models.User{Username: "user_apply", Phone: "13900001002"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(user) tenant := &models.Tenant{ Name: "Tenant Apply", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) return tenant, owner, user } Convey("should create pending join request", func() { tenant, _, user := setup() err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{Reason: "想加入"}) So(err, ShouldBeNil) req, err := models.TenantJoinRequestQuery.WithContext(ctx). Where(models.TenantJoinRequestQuery.TenantID.Eq(tenant.ID), models.TenantJoinRequestQuery.UserID.Eq(user.ID)). First() So(err, ShouldBeNil) So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusPending)) }) Convey("should reject duplicate apply", func() { tenant, _, user := setup() _ = Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{}) err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{}) So(err, ShouldNotBeNil) var appErr *errorx.AppError So(errors.As(err, &appErr), ShouldBeTrue) So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code) }) Convey("should reject when already member", func() { tenant, _, user := setup() _ = models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{ TenantID: tenant.ID, UserID: user.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, Status: consts.UserStatusVerified, }) err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{}) So(err, ShouldNotBeNil) var appErr *errorx.AppError So(errors.As(err, &appErr), ShouldBeTrue) So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code) }) }) } func (s *TenantTestSuite) Test_CancelJoin() { Convey("CancelJoin", s.T(), func() { ctx := s.T().Context() setup := func() (*models.Tenant, *models.User, *models.User, *models.TenantJoinRequest) { database.Truncate(ctx, s.DB, models.TableNameTenantJoinRequest, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_cancel", Phone: "13900001003"} user := &models.User{Username: "user_cancel", Phone: "13900001004"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(user) tenant := &models.Tenant{ Name: "Tenant Cancel", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) req := &models.TenantJoinRequest{ TenantID: tenant.ID, UserID: user.ID, Status: string(consts.TenantJoinRequestStatusPending), Reason: "测试取消", } _ = models.TenantJoinRequestQuery.WithContext(ctx).Create(req) return tenant, owner, user, req } Convey("should cancel pending request", func() { tenant, _, user, _ := setup() err := Tenant.CancelJoin(ctx, tenant.ID, user.ID) So(err, ShouldBeNil) exists, err := models.TenantJoinRequestQuery.WithContext(ctx). Where(models.TenantJoinRequestQuery.TenantID.Eq(tenant.ID), models.TenantJoinRequestQuery.UserID.Eq(user.ID)). Exists() So(err, ShouldBeNil) So(exists, ShouldBeFalse) }) Convey("should return not found when request missing", func() { tenant, _, user, _ := setup() _ = Tenant.CancelJoin(ctx, tenant.ID, user.ID) err := Tenant.CancelJoin(ctx, tenant.ID, user.ID) So(err, ShouldNotBeNil) var appErr *errorx.AppError So(errors.As(err, &appErr), ShouldBeTrue) So(appErr.Code, ShouldEqual, errorx.ErrRecordNotFound.Code) }) }) } func (s *TenantTestSuite) Test_ReviewJoin() { Convey("ReviewJoin", s.T(), func() { ctx := s.T().Context() setup := func() (*models.Tenant, *models.User, *models.User, *models.TenantJoinRequest) { database.Truncate(ctx, s.DB, models.TableNameTenantJoinRequest, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_review", Phone: "13900001005"} user := &models.User{Username: "user_review", Phone: "13900001006"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(user) tenant := &models.Tenant{ Name: "Tenant Review", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) req := &models.TenantJoinRequest{ TenantID: tenant.ID, UserID: user.ID, Status: string(consts.TenantJoinRequestStatusPending), Reason: "测试审核", } _ = models.TenantJoinRequestQuery.WithContext(ctx).Create(req) return tenant, owner, user, req } Convey("should approve join request", func() { tenant, owner, user, req := setup() err := Tenant.ReviewJoin(ctx, tenant.ID, owner.ID, req.ID, &tenant_dto.TenantJoinReviewForm{ Action: "approve", Reason: "通过", }) So(err, ShouldBeNil) req, err = models.TenantJoinRequestQuery.WithContext(ctx).Where(models.TenantJoinRequestQuery.ID.Eq(req.ID)).First() So(err, ShouldBeNil) So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusApproved)) exists, err := models.TenantUserQuery.WithContext(ctx). Where(models.TenantUserQuery.TenantID.Eq(tenant.ID), models.TenantUserQuery.UserID.Eq(user.ID)). Exists() So(err, ShouldBeNil) So(exists, ShouldBeTrue) }) Convey("should reject join request", func() { tenant, owner, _, req := setup() err := Tenant.ReviewJoin(ctx, tenant.ID, owner.ID, req.ID, &tenant_dto.TenantJoinReviewForm{ Action: "reject", Reason: "不符合条件", }) So(err, ShouldBeNil) req, err = models.TenantJoinRequestQuery.WithContext(ctx).Where(models.TenantJoinRequestQuery.ID.Eq(req.ID)).First() So(err, ShouldBeNil) So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusRejected)) }) }) } func (s *TenantTestSuite) Test_CreateInvite() { Convey("CreateInvite", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantInvite, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_invite", Phone: "13900001007"} _ = models.UserQuery.WithContext(ctx).Create(owner) tenant := &models.Tenant{ Name: "Tenant Invite", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) expiresAt := time.Now().Add(24 * time.Hour).Format(time.RFC3339) item, err := Tenant.CreateInvite(ctx, tenant.ID, owner.ID, &tenant_dto.TenantInviteCreateForm{ MaxUses: 2, ExpiresAt: &expiresAt, Remark: "测试邀请", }) So(err, ShouldBeNil) So(item.Code, ShouldNotBeBlank) invite, err := models.TenantInviteQuery.WithContext(ctx).Where(models.TenantInviteQuery.ID.Eq(item.ID)).First() So(err, ShouldBeNil) So(invite.MaxUses, ShouldEqual, 2) So(invite.Status, ShouldEqual, string(consts.TenantInviteStatusActive)) }) } func (s *TenantTestSuite) Test_AcceptInvite() { Convey("AcceptInvite", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantInvite, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_accept", Phone: "13900001008"} user := &models.User{Username: "user_accept", Phone: "13900001009"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(user) tenant := &models.Tenant{ Name: "Tenant Accept", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) invite := &models.TenantInvite{ TenantID: tenant.ID, UserID: owner.ID, Code: "invite_accept", Status: string(consts.TenantInviteStatusActive), MaxUses: 1, UsedCount: 0, ExpiresAt: time.Now().Add(24 * time.Hour), Remark: "测试使用", } _ = models.TenantInviteQuery.WithContext(ctx).Create(invite) err := Tenant.AcceptInvite(ctx, tenant.ID, user.ID, &tenant_dto.TenantInviteAcceptForm{Code: "invite_accept"}) So(err, ShouldBeNil) exists, err := models.TenantUserQuery.WithContext(ctx). Where(models.TenantUserQuery.TenantID.Eq(tenant.ID), models.TenantUserQuery.UserID.Eq(user.ID)). Exists() So(err, ShouldBeNil) So(exists, ShouldBeTrue) invite, err = models.TenantInviteQuery.WithContext(ctx).Where(models.TenantInviteQuery.ID.Eq(invite.ID)).First() So(err, ShouldBeNil) So(invite.UsedCount, ShouldEqual, 1) }) } func (s *TenantTestSuite) Test_ListMembersAndRemove() { Convey("ListMembers and RemoveMember", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_member", Phone: "13900002001"} member := &models.User{Username: "member_user", Phone: "13900002002"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(member) tenant := &models.Tenant{ Name: "Tenant Member", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) link := &models.TenantUser{ TenantID: tenant.ID, UserID: member.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, Status: consts.UserStatusVerified, } _ = models.TenantUserQuery.WithContext(ctx).Create(link) res, err := Tenant.ListMembers(ctx, tenant.ID, owner.ID, &tenant_dto.TenantMemberListFilter{ Pagination: requests.Pagination{Page: 1, Limit: 10}, }) So(err, ShouldBeNil) So(res.Total, ShouldEqual, 1) items := res.Items.([]tenant_dto.TenantMemberItem) So(items[0].User.ID, ShouldEqual, member.ID) err = Tenant.RemoveMember(ctx, tenant.ID, owner.ID, items[0].ID) So(err, ShouldBeNil) exists, err := models.TenantUserQuery.WithContext(ctx). Where(models.TenantUserQuery.ID.Eq(items[0].ID)). Exists() So(err, ShouldBeNil) So(exists, ShouldBeFalse) }) } func (s *TenantTestSuite) Test_ListInvitesAndDisable() { Convey("ListInvites and DisableInvite", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantInvite, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_invite", Phone: "13900002003"} _ = models.UserQuery.WithContext(ctx).Create(owner) tenant := &models.Tenant{ Name: "Tenant Invite", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) invite := &models.TenantInvite{ TenantID: tenant.ID, UserID: owner.ID, Code: "invite_list", Status: string(consts.TenantInviteStatusActive), MaxUses: 2, UsedCount: 0, ExpiresAt: time.Now().Add(24 * time.Hour), Remark: "测试邀请", } _ = models.TenantInviteQuery.WithContext(ctx).Create(invite) res, err := Tenant.ListInvites(ctx, tenant.ID, owner.ID, &tenant_dto.TenantInviteListFilter{ Pagination: requests.Pagination{Page: 1, Limit: 10}, }) So(err, ShouldBeNil) So(res.Total, ShouldEqual, 1) err = Tenant.DisableInvite(ctx, tenant.ID, owner.ID, invite.ID) So(err, ShouldBeNil) updated, err := models.TenantInviteQuery.WithContext(ctx). Where(models.TenantInviteQuery.ID.Eq(invite.ID)). First() So(err, ShouldBeNil) So(updated.Status, ShouldEqual, string(consts.TenantInviteStatusDisabled)) }) } func (s *TenantTestSuite) Test_ListJoinRequests() { Convey("ListJoinRequests", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantJoinRequest, models.TableNameTenant, models.TableNameUser, ) owner := &models.User{Username: "owner_request", Phone: "13900002004"} user := &models.User{Username: "request_user", Phone: "13900002005"} _ = models.UserQuery.WithContext(ctx).Create(owner) _ = models.UserQuery.WithContext(ctx).Create(user) tenant := &models.Tenant{ Name: "Tenant Request", UserID: owner.ID, Status: consts.TenantStatusVerified, } _ = models.TenantQuery.WithContext(ctx).Create(tenant) req := &models.TenantJoinRequest{ TenantID: tenant.ID, UserID: user.ID, Status: string(consts.TenantJoinRequestStatusPending), Reason: "申请加入", } _ = models.TenantJoinRequestQuery.WithContext(ctx).Create(req) status := consts.TenantJoinRequestStatusPending res, err := Tenant.ListJoinRequests(ctx, tenant.ID, owner.ID, &tenant_dto.TenantJoinRequestListFilter{ Pagination: requests.Pagination{Page: 1, Limit: 10}, Status: &status, }) So(err, ShouldBeNil) So(res.Total, ShouldEqual, 1) }) }