package services import ( "context" "database/sql" "testing" "quyun/v2/app/commands/testx" user_dto "quyun/v2/app/http/v1/dto" "quyun/v2/database" "quyun/v2/database/models" "quyun/v2/pkg/consts" . "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 UserTestSuiteInjectParams struct { dig.In DB *sql.DB Initials []contracts.Initial `group:"initials"` } type UserTestSuite struct { suite.Suite UserTestSuiteInjectParams } func Test_User(t *testing.T) { providers := testx.Default().With(Provide) testx.Serve(providers, t, func(p UserTestSuiteInjectParams) { suite.Run(t, &UserTestSuite{UserTestSuiteInjectParams: p}) }) } func (s *UserTestSuite) Test_LoginWithOTP() { Convey("LoginWithOTP", s.T(), func() { ctx := s.T().Context() database.Truncate(ctx, s.DB, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser) tenant := &models.Tenant{ UserID: 1000, Name: "Tenant A", Code: "tenant_a", Status: consts.TenantStatusVerified, } models.TenantQuery.WithContext(ctx).Create(tenant) Convey("should create user and login success without tenant", func() { phone := "13800138000" resp, err := User.LoginWithOTP(ctx, 0, phone, "1234") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.Token, ShouldNotBeEmpty) So(resp.User.Phone, ShouldEqual, phone) So(resp.User.Nickname, ShouldStartWith, "User_") }) Convey("should allow login when not tenant member", func() { phone := "13800138001" resp, err := User.LoginWithOTP(ctx, tenant.ID, phone, "1234") So(err, ShouldBeNil) So(resp, ShouldNotBeNil) So(resp.Token, ShouldNotBeEmpty) So(resp.User.Phone, ShouldEqual, phone) }) Convey("should login existing tenant member", func() { phone := "13800138002" resp, err := User.LoginWithOTP(ctx, 0, phone, "1234") So(err, ShouldBeNil) models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{ TenantID: tenant.ID, UserID: resp.User.ID, Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember}, Status: consts.UserStatusVerified, }) resp2, err := User.LoginWithOTP(ctx, tenant.ID, phone, "1234") So(err, ShouldBeNil) So(resp2.User.Phone, ShouldEqual, phone) }) Convey("should fail with incorrect OTP", func() { resp, err := User.LoginWithOTP(ctx, 0, "13800138003", "000000") So(err, ShouldNotBeNil) So(resp, ShouldBeNil) }) }) } func (s *UserTestSuite) Test_Me() { Convey("Me", s.T(), func() { ctx := s.T().Context() tenantID := int64(0) database.Truncate(ctx, s.DB, models.TableNameUser) // Create user phone := "13800138003" resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234") userID := resp.User.ID Convey("should return user profile", func() { // Mock context with user ID ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) user, err := User.Me(ctx, userID) So(err, ShouldBeNil) So(user.ID, ShouldEqual, resp.User.ID) So(user.Phone, ShouldEqual, phone) }) Convey("should fail without auth", func() { // No user ID in context user, err := User.Me(ctx, 0) So(err, ShouldNotBeNil) So(user, ShouldBeNil) }) }) } func (s *UserTestSuite) Test_Update() { Convey("Update", s.T(), func() { ctx := s.T().Context() tenantID := int64(0) database.Truncate(ctx, s.DB, models.TableNameUser) phone := "13800138004" resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234") userID := resp.User.ID ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) Convey("should update nickname and bio", func() { form := &user_dto.UserUpdate{ Nickname: "NewName", Bio: "New Bio", Gender: consts.GenderMale, } err := User.Update(ctx, userID, form) So(err, ShouldBeNil) // Verify u, _ := User.Me(ctx, userID) So(u.Nickname, ShouldEqual, "NewName") So(u.Bio, ShouldEqual, "New Bio") So(u.Gender, ShouldEqual, consts.GenderMale) }) }) } func (s *UserTestSuite) Test_RealName() { Convey("RealName", s.T(), func() { ctx := s.T().Context() tenantID := int64(0) database.Truncate(ctx, s.DB, models.TableNameUser) phone := "13800138005" resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234") userID := resp.User.ID ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) Convey("should update realname status", func() { form := &user_dto.RealNameForm{ Realname: "张三", IDCard: "123456789012345678", } err := User.RealName(ctx, userID, form) So(err, ShouldBeNil) // Verify u, _ := User.Me(ctx, userID) So(u.IsRealNameVerified, ShouldBeTrue) }) }) } func (s *UserTestSuite) Test_GetNotifications() { Convey("GetNotifications", s.T(), func() { ctx := s.T().Context() tenantID := int64(0) database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameNotification) phone := "13800138006" resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234") userID := resp.User.ID ctx = context.WithValue(ctx, consts.CtxKeyUser, userID) // Mock notifications _ = models.Q.Notification.WithContext(ctx).Create(&models.Notification{ TenantID: tenantID, UserID: userID, Type: "system", Title: "Welcome", Content: "Hello World", IsRead: false, }) Convey("should return notifications", func() { list, err := User.GetNotifications(ctx, tenantID, userID, "all") So(err, ShouldBeNil) So(len(list), ShouldEqual, 1) So(list[0].Title, ShouldEqual, "Welcome") So(list[0].Type, ShouldEqual, "system") }) }) }