Files
backend/internal/service/admin_group_service_test.go
lan d9aa4b46c3
All checks were successful
Build Backend / build (push) Successful in 4m55s
Build Backend / build-docker (push) Successful in 10m34s
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00

180 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}
}