package service import ( "context" "errors" "testing" "with_you/internal/model" "with_you/internal/repository" ) // stubGroupRepo 通过嵌入 repository.GroupRepository 接口(保持 nil), // 仅覆盖测试涉及的方法。未覆盖的方法被调用时会 panic——这能在测试中 // 提前暴露「Service 调用了非预期 repo 方法」的回归。 type stubGroupRepo struct { repository.GroupRepository // nil 嵌入,未覆盖方法 panic groupByID map[string]*model.Group groupByIDErr error memberByUser map[string]*model.GroupMember memberErr error transferCalls []transferCall transferErr error } type transferCall struct { GroupID, OldOwner, NewOwner string } func (s *stubGroupRepo) GetByID(id string) (*model.Group, error) { if s.groupByIDErr != nil { return nil, s.groupByIDErr } g, ok := s.groupByID[id] if !ok { return nil, errors.New("not found") } return g, nil } func (s *stubGroupRepo) GetMember(groupID, userID string) (*model.GroupMember, error) { if s.memberErr != nil { return nil, s.memberErr } m, ok := s.memberByUser[userID] if !ok { return nil, errors.New("not member") } return m, nil } func (s *stubGroupRepo) TransferOwner(groupID, oldOwnerID, newOwnerID string) error { s.transferCalls = append(s.transferCalls, transferCall{ GroupID: groupID, OldOwner: oldOwnerID, NewOwner: newOwnerID, }) // 模拟真实 DB 语义:事务提交后 owner_id 已变更,后续 GetByID 读到新值 if g, ok := s.groupByID[groupID]; ok { g.OwnerID = newOwnerID } return s.transferErr } // stubUserRepo 同样仅覆盖 GetByID。 type stubUserRepo struct { repository.UserRepository // nil 嵌入 byID map[string]*model.User } func (s *stubUserRepo) GetByID(id string) (*model.User, error) { u, ok := s.byID[id] if !ok { return nil, errors.New("not found") } return u, nil } // TestTransferOwner_Success 正常转让:TransferOwner 应被调用一次, // 参数为 (groupID, 当前 owner, 新 owner)。 func TestTransferOwner_Success(t *testing.T) { repo := &stubGroupRepo{ groupByID: map[string]*model.Group{ "g1": {ID: "g1", OwnerID: "old-owner", Name: "测试群"}, }, memberByUser: map[string]*model.GroupMember{ "old-owner": {UserID: "old-owner", GroupID: "g1", Role: model.GroupRoleOwner}, "new-owner": {UserID: "new-owner", GroupID: "g1", Role: model.GroupRoleMember}, }, } userRepo := &stubUserRepo{ byID: map[string]*model.User{ "new-owner": {ID: "new-owner", Username: "new", Nickname: "新群主"}, }, } svc := NewAdminGroupService(repo, userRepo) resp, err := svc.TransferOwner(context.Background(), "g1", "new-owner") if err != nil { t.Fatalf("TransferOwner failed: %v", err) } // 校验 TransferOwner 恰好被调用一次,参数正确 if len(repo.transferCalls) != 1 { t.Fatalf("expected 1 TransferOwner call, got %d", len(repo.transferCalls)) } tc := repo.transferCalls[0] if tc.GroupID != "g1" || tc.OldOwner != "old-owner" || tc.NewOwner != "new-owner" { t.Errorf("TransferOwner called with (%s,%s,%s), want (g1,old-owner,new-owner)", tc.GroupID, tc.OldOwner, tc.NewOwner) } // 响应应反映新群主 if resp == nil { t.Fatal("expected non-nil response") } if resp.OwnerID != "new-owner" { t.Errorf("response OwnerID = %s, want new-owner", resp.OwnerID) } } // TestTransferOwner_NewOwnerNotMember 新群主非成员时应返回错误, // 且 TransferOwner 不应被调用。 func TestTransferOwner_NewOwnerNotMember(t *testing.T) { repo := &stubGroupRepo{ groupByID: map[string]*model.Group{ "g1": {ID: "g1", OwnerID: "old-owner"}, }, memberByUser: map[string]*model.GroupMember{ "old-owner": {UserID: "old-owner", GroupID: "g1", Role: model.GroupRoleOwner}, // new-owner 不在成员表 }, } svc := NewAdminGroupService(repo, &stubUserRepo{}) _, err := svc.TransferOwner(context.Background(), "g1", "new-owner") if err == nil { t.Fatal("expected error when new owner is not a member") } if len(repo.transferCalls) != 0 { t.Errorf("TransferOwner should not be called, got %d calls", len(repo.transferCalls)) } } // TestTransferOwner_GroupNotFound 群不存在时应返回错误。 func TestTransferOwner_GroupNotFound(t *testing.T) { repo := &stubGroupRepo{ groupByID: map[string]*model.Group{}, // g1 不存在 } svc := NewAdminGroupService(repo, &stubUserRepo{}) _, err := svc.TransferOwner(context.Background(), "g1", "new-owner") if err == nil { t.Fatal("expected error when group does not exist") } if len(repo.transferCalls) != 0 { t.Errorf("TransferOwner should not be called, got %d calls", len(repo.transferCalls)) } } // TestTransferOwner_AlreadyOwner 新群主已是群主时应拒绝(无操作幂等)。 func TestTransferOwner_AlreadyOwner(t *testing.T) { repo := &stubGroupRepo{ groupByID: map[string]*model.Group{ "g1": {ID: "g1", OwnerID: "same-user"}, }, memberByUser: map[string]*model.GroupMember{ "same-user": {UserID: "same-user", GroupID: "g1", Role: model.GroupRoleOwner}, }, } svc := NewAdminGroupService(repo, &stubUserRepo{}) _, err := svc.TransferOwner(context.Background(), "g1", "same-user") if err == nil { t.Fatal("expected error when transferring to existing owner") } if len(repo.transferCalls) != 0 { t.Errorf("TransferOwner should not be called, got %d calls", len(repo.transferCalls)) } }