feat: persist group business state in tables

This commit is contained in:
richjohnrambo
2026-08-17 09:50:31 +08:00
parent d8d1639d5b
commit ddf0f7a55e
10 changed files with 2065 additions and 130 deletions
+151 -6
View File
@@ -138,6 +138,142 @@ type testSafetyStore struct {
targetType string
}
type testGroupBusinessStore struct {
settings *types.GroupSettings
member *types.GroupMember
joinRequests []types.GroupJoinRequest
announcement *types.GroupAnnouncement
mutes []types.GroupMute
invitations []types.GroupInvitation
updatedSettings types.GroupSettingsPatch
updatedMembers []types.GroupMemberPatch
}
func (s *testGroupBusinessStore) CreateSettings(_ types.TenantID, settings *types.GroupSettings) (*types.GroupSettings, error) {
s.settings = settings
return settings, nil
}
func (s *testGroupBusinessStore) GetSettings(_ types.TenantID, topic string) (*types.GroupSettings, error) {
if s.settings != nil {
return s.settings, nil
}
return &types.GroupSettings{Topic: topic, OwnerUserID: types.Uid(42), Name: "Alpha", GroupStatus: types.GroupStatusActive, MemberUploadFiles: true, MemberPrivateChat: true, MemberInvite: true, MaxAdminCount: groupMaxAdminCount, MemberCount: 3, Version: 1}, nil
}
func (s *testGroupBusinessStore) UpdateSettings(_ types.TenantID, topic string, patch types.GroupSettingsPatch) (*types.GroupSettings, error) {
s.updatedSettings = patch
settings, _ := s.GetSettings(0, topic)
if patch.Name != nil {
settings.Name = *patch.Name
}
if patch.Description != nil {
settings.Description = *patch.Description
}
if patch.JoinApproval != nil {
settings.JoinApproval = *patch.JoinApproval
}
if patch.OwnerUserID != nil {
settings.OwnerUserID = *patch.OwnerUserID
}
settings.Version++
s.settings = settings
return settings, nil
}
func (s *testGroupBusinessStore) UpsertMember(_ types.TenantID, member *types.GroupMember) (*types.GroupMember, error) {
s.member = member
return member, nil
}
func (s *testGroupBusinessStore) GetMember(_ types.TenantID, topic string, uid types.Uid) (*types.GroupMember, error) {
return &types.GroupMember{Topic: topic, UserID: uid, Role: types.GroupMemberRoleMember, MemberStatus: types.GroupMemberStatusActive, Version: 1}, nil
}
func (s *testGroupBusinessStore) ListMembers(_ types.TenantID, _ string, _ types.GroupMemberListOptions) ([]types.GroupMember, string, error) {
return nil, "", nil
}
func (s *testGroupBusinessStore) UpdateMember(_ types.TenantID, topic string, uid types.Uid, patch types.GroupMemberPatch) (*types.GroupMember, error) {
s.updatedMembers = append(s.updatedMembers, patch)
return &types.GroupMember{Topic: topic, UserID: uid, Role: defaultStringPtr(patch.Role, types.GroupMemberRoleMember), MemberStatus: types.GroupMemberStatusActive, Version: 2}, nil
}
func (s *testGroupBusinessStore) CreateInvitation(_ types.TenantID, inv *types.GroupInvitation) (*types.GroupInvitation, error) {
s.invitations = append(s.invitations, *inv)
return inv, nil
}
func (s *testGroupBusinessStore) CreateJoinRequest(_ types.TenantID, req *types.GroupJoinRequest) (*types.GroupJoinRequest, error) {
now := time.Now().UTC()
req.CreatedAt = now
req.UpdatedAt = now
s.joinRequests = append(s.joinRequests, *req)
return req, nil
}
func (s *testGroupBusinessStore) ListJoinRequests(_ types.TenantID, _ string, status, _ string, _ int) ([]types.GroupJoinRequest, string, error) {
if status == "" || status == "all" {
return s.joinRequests, "", nil
}
out := []types.GroupJoinRequest{}
for _, item := range s.joinRequests {
if item.Status == status {
out = append(out, item)
}
}
return out, "", nil
}
func (s *testGroupBusinessStore) ResolveJoinRequest(_ types.TenantID, topic, requestID string, actor types.Uid, action, _ string, _ int, now time.Time) (*types.GroupJoinRequest, error) {
status := types.GroupJoinRequestStatusRejected
if action == "accept" {
status = types.GroupJoinRequestStatusAccepted
}
return &types.GroupJoinRequest{RequestID: requestID, Topic: topic, ApplicantUserID: types.Uid(43), Status: status, HandledBy: actor, HandledAt: &now, Version: 2, CreatedAt: now, UpdatedAt: now}, nil
}
func (s *testGroupBusinessStore) LatestAnnouncement(_ types.TenantID, topic string) (*types.GroupAnnouncement, error) {
if s.announcement == nil {
return nil, types.ErrNotFound
}
return s.announcement, nil
}
func (s *testGroupBusinessStore) PutAnnouncement(_ types.TenantID, ann *types.GroupAnnouncement, _ int) (*types.GroupAnnouncement, error) {
ann.Version = 2
if ann.PublishedAt.IsZero() {
ann.PublishedAt = time.Now().UTC()
}
s.announcement = ann
return ann, nil
}
func (s *testGroupBusinessStore) ReadAnnouncement(_ types.TenantID, _, _ string, _ types.Uid, _ time.Time) error {
return nil
}
func (s *testGroupBusinessStore) ListMutes(_ types.TenantID, _ string, _ string, _ int) ([]types.GroupMute, string, error) {
return s.mutes, "", nil
}
func (s *testGroupBusinessStore) UpsertMute(_ types.TenantID, mute *types.GroupMute) (*types.GroupMute, error) {
mute.Version = 1
s.mutes = append(s.mutes, *mute)
return mute, nil
}
func (s *testGroupBusinessStore) DeleteMute(_ types.TenantID, _ string, _ types.Uid, _ types.Uid, _ time.Time) error {
return nil
}
func defaultStringPtr(value *string, fallback string) string {
if value == nil {
return fallback
}
return *value
}
func (s *testWorkbenchStore) Get(tenantID types.TenantID, uid types.Uid) ([]types.WorkbenchModule, int, error) {
s.tenantID = tenantID
s.uid = uid
@@ -2167,6 +2303,7 @@ func TestServeIMGroupCreate(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldStore := store.Store
oldGroupBusiness := store.GroupBusiness
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth)
@@ -2181,10 +2318,12 @@ func TestServeIMGroupCreate(t *testing.T) {
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
store.Topics = topics
store.Subs = subs
store.GroupBusiness = &testGroupBusinessStore{}
defer func() {
store.Store = oldStore
store.Topics = oldTopics
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
}()
topics.EXPECT().Create(types.TenantID(7), gomock.Any(), types.Uid(42), gomock.Any()).DoAndReturn(
@@ -2230,6 +2369,7 @@ func TestServeIMGroupDetailPatchSettingsProfileAndLeave(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldStore := store.Store
oldGroupBusiness := store.GroupBusiness
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth).Times(3)
@@ -2243,10 +2383,12 @@ func TestServeIMGroupDetailPatchSettingsProfileAndLeave(t *testing.T) {
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
store.Topics = topics
store.Subs = subs
store.GroupBusiness = &testGroupBusinessStore{}
defer func() {
store.Store = oldStore
store.Topics = oldTopics
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
}()
selfSub := &types.Subscription{
TenantID: 7,
@@ -2315,13 +2457,16 @@ func TestServeIMGroupLeave(t *testing.T) {
expectIMBearer(t, ctrl, "token-1", 7, 42)
oldTopics := store.Topics
oldSubs := store.Subs
oldGroupBusiness := store.GroupBusiness
topics := mock_store.NewMockTopicsPersistenceInterface(ctrl)
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
store.Topics = topics
store.Subs = subs
store.GroupBusiness = &testGroupBusinessStore{}
defer func() {
store.Topics = oldTopics
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
}()
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(&types.Subscription{
TenantID: 7,
@@ -2426,6 +2571,7 @@ func TestServeIMGroupAnnouncementGovernanceJoinRequest(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldStore := store.Store
oldGroupBusiness := store.GroupBusiness
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth).Times(3)
@@ -2441,16 +2587,15 @@ func TestServeIMGroupAnnouncementGovernanceJoinRequest(t *testing.T) {
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
store.Topics = topics
store.Subs = subs
store.GroupBusiness = &testGroupBusinessStore{}
defer func() {
store.Store = oldStore
store.Topics = oldTopics
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
}()
selfSub := &types.Subscription{TenantID: 7, User: types.Uid(42).String(), Topic: "grpAlpha", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull}
group := &types.Topic{ObjHeader: types.ObjHeader{Id: "grpAlpha"}, TenantID: 7, Owner: types.Uid(42).String(), Aux: types.KVMap{}}
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", types.Uid(42), false).Return(selfSub, nil).Times(3)
topics.EXPECT().Get(types.TenantID(7), "grpAlpha").Return(group, nil).Times(3)
topics.EXPECT().Update(types.TenantID(7), "grpAlpha", gomock.Any()).Return(nil).Times(3)
annReq := httptest.NewRequest(http.MethodPut, "/v1/im/groups/grpAlpha/announcements/current", bytes.NewBufferString(`{"content":"公告","version":1}`))
annReq.Header.Set("Authorization", "Bearer token-1")
@@ -2490,6 +2635,7 @@ func TestServeIMGroupAdminsMutesAndOwnerTransfer(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
oldStore := store.Store
oldGroupBusiness := store.GroupBusiness
mockStore := mock_store.NewMockPersistentStorageInterface(ctrl)
tokenAuth := mock_auth.NewMockAuthHandler(ctrl)
mockStore.EXPECT().GetLogicalAuthHandler("token").Return(tokenAuth).Times(3)
@@ -2503,22 +2649,21 @@ func TestServeIMGroupAdminsMutesAndOwnerTransfer(t *testing.T) {
subs := mock_store.NewMockSubsPersistenceInterface(ctrl)
store.Topics = topics
store.Subs = subs
store.GroupBusiness = &testGroupBusinessStore{}
defer func() {
store.Store = oldStore
store.Topics = oldTopics
store.Subs = oldSubs
store.GroupBusiness = oldGroupBusiness
}()
owner := types.Uid(42)
target := types.Uid(43)
ownerSub := &types.Subscription{TenantID: 7, User: owner.String(), Topic: "grpAlpha", ModeWant: types.ModeCFull, ModeGiven: types.ModeCFull}
group := &types.Topic{ObjHeader: types.ObjHeader{Id: "grpAlpha"}, TenantID: 7, Owner: owner.String(), Aux: types.KVMap{}}
member := types.Subscription{TenantID: 7, User: target.UserId(), Topic: "grpAlpha", ModeWant: types.ModeCPublic, ModeGiven: types.ModeCPublic}
members := []types.Subscription{*ownerSub, member}
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", owner, false).Return(ownerSub, nil).Times(3)
topics.EXPECT().GetUsers(types.TenantID(7), "grpAlpha", gomock.Any()).Return(members, nil).Times(2)
subs.EXPECT().Update(types.TenantID(7), "grpAlpha", target, gomock.Any()).Return(nil).Times(2)
topics.EXPECT().Get(types.TenantID(7), "grpAlpha").Return(group, nil)
topics.EXPECT().Update(types.TenantID(7), "grpAlpha", gomock.Any()).Return(nil)
subs.EXPECT().Get(types.TenantID(7), "grpAlpha", target, false).Return(&member, nil)
subs.EXPECT().Update(types.TenantID(7), "grpAlpha", owner, gomock.Any()).Return(nil)
topics.EXPECT().OwnerChange(types.TenantID(7), "grpAlpha", target).Return(nil)