0.29.1原版
This commit is contained in:
@@ -0,0 +1,489 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
||||
storepb "github.com/usememos/memos/proto/gen/store"
|
||||
apiv1 "github.com/usememos/memos/server/router/api/v1"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func TestDeleteUserSelfDeleteCleansAccountDataAndAuthCookies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
user, err := ts.CreateRegularUser(ctx, "alice")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.CreateUserIdentity(ctx, &store.UserIdentity{
|
||||
UserID: user.ID,
|
||||
Provider: "google",
|
||||
ExternUID: "alice-google-sub",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ts.Store.AddUserRefreshToken(ctx, user.ID, &storepb.RefreshTokensUserSetting_RefreshToken{
|
||||
TokenId: "refresh-token-id",
|
||||
ExpiresAt: timestamppb.New(time.Now().Add(time.Hour)),
|
||||
CreatedAt: timestamppb.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
headerCtx := apiv1.WithHeaderCarrier(ctx)
|
||||
authCtx := ts.CreateUserContext(headerCtx, user.ID)
|
||||
_, err = ts.Service.DeleteUser(authCtx, &v1pb.DeleteUserRequest{
|
||||
Name: apiv1.BuildUserName(user.Username),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedUser, err := ts.Store.GetUser(ctx, &store.FindUser{ID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, deletedUser)
|
||||
|
||||
identities, err := ts.Store.ListUserIdentities(ctx, &store.FindUserIdentity{UserID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, identities)
|
||||
|
||||
refreshSetting, err := ts.Store.GetUserSetting(ctx, &store.FindUserSetting{
|
||||
UserID: &user.ID,
|
||||
Key: storepb.UserSetting_REFRESH_TOKENS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, refreshSetting)
|
||||
|
||||
carrier := apiv1.GetHeaderCarrier(authCtx)
|
||||
require.NotNil(t, carrier)
|
||||
require.Contains(t, strings.ToLower(carrier.Get("Set-Cookie")), "memos_refresh=")
|
||||
}
|
||||
|
||||
func TestDeleteUserSelfDeleteRemovesOwnedResourcesAndMemoSubtrees(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
user, err := ts.CreateRegularUser(ctx, "resource-owner")
|
||||
require.NoError(t, err)
|
||||
peer, err := ts.CreateRegularUser(ctx, "resource-peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
peerCtx := ts.CreateUserContext(ctx, peer.ID)
|
||||
|
||||
ownMemo, err := ts.Service.CreateMemo(userCtx, &v1pb.CreateMemoRequest{
|
||||
Memo: &v1pb.Memo{
|
||||
Content: "owner memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
foreignMemo, err := ts.Service.CreateMemo(peerCtx, &v1pb.CreateMemoRequest{
|
||||
Memo: &v1pb.Memo{
|
||||
Content: "peer memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
peerCommentOnOwnMemo, err := ts.Service.CreateMemoComment(peerCtx, &v1pb.CreateMemoCommentRequest{
|
||||
Name: ownMemo.Name,
|
||||
Comment: &v1pb.Memo{
|
||||
Content: "peer comment on owner memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
peerNestedCommentOnOwnMemo, err := ts.Service.CreateMemoComment(peerCtx, &v1pb.CreateMemoCommentRequest{
|
||||
Name: peerCommentOnOwnMemo.Name,
|
||||
Comment: &v1pb.Memo{
|
||||
Content: "peer nested comment on owner memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
userCommentOnForeignMemo, err := ts.Service.CreateMemoComment(userCtx, &v1pb.CreateMemoCommentRequest{
|
||||
Name: foreignMemo.Name,
|
||||
Comment: &v1pb.Memo{
|
||||
Content: "owner comment on peer memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
peerReplyToUserComment, err := ts.Service.CreateMemoComment(peerCtx, &v1pb.CreateMemoCommentRequest{
|
||||
Name: userCommentOnForeignMemo.Name,
|
||||
Comment: &v1pb.Memo{
|
||||
Content: "peer reply to owner comment",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ownMemoUID, err := apiv1.ExtractMemoUIDFromName(ownMemo.Name)
|
||||
require.NoError(t, err)
|
||||
ownMemoStore, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &ownMemoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ownMemoStore)
|
||||
|
||||
foreignMemoUID, err := apiv1.ExtractMemoUIDFromName(foreignMemo.Name)
|
||||
require.NoError(t, err)
|
||||
foreignMemoStore, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &foreignMemoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, foreignMemoStore)
|
||||
|
||||
attachedAttachment, err := ts.Store.CreateAttachment(ctx, &store.Attachment{
|
||||
UID: "attach-owner-memo",
|
||||
CreatorID: user.ID,
|
||||
Filename: "owner.txt",
|
||||
Type: "text/plain",
|
||||
Size: 4,
|
||||
Blob: []byte("memo"),
|
||||
MemoID: &ownMemoStore.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
thumbnailCachePath := filepath.Join(ts.Profile.Data, ".thumbnail_cache", attachedAttachment.UID+".jpeg")
|
||||
motionCachePath := filepath.Join(ts.Profile.Data, ".motion_cache", attachedAttachment.UID+".mp4")
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(thumbnailCachePath), 0o755))
|
||||
require.NoError(t, os.WriteFile(thumbnailCachePath, []byte("thumb"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(motionCachePath), 0o755))
|
||||
require.NoError(t, os.WriteFile(motionCachePath, []byte("motion"), 0o644))
|
||||
|
||||
unattachedAttachment, err := ts.Store.CreateAttachment(ctx, &store.Attachment{
|
||||
UID: "attach-owner-loose",
|
||||
CreatorID: user.ID,
|
||||
Filename: "loose.txt",
|
||||
Type: "text/plain",
|
||||
Size: 5,
|
||||
Blob: []byte("loose"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
peerAttachment, err := ts.Store.CreateAttachment(ctx, &store.Attachment{
|
||||
UID: "attach-peer-keep",
|
||||
CreatorID: peer.ID,
|
||||
Filename: "peer.txt",
|
||||
Type: "text/plain",
|
||||
Size: 4,
|
||||
Blob: []byte("peer"),
|
||||
MemoID: &foreignMemoStore.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.UpsertReaction(ctx, &store.Reaction{
|
||||
CreatorID: peer.ID,
|
||||
ContentID: ownMemo.Name,
|
||||
ReactionType: "👍",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ts.Store.UpsertReaction(ctx, &store.Reaction{
|
||||
CreatorID: peer.ID,
|
||||
ContentID: userCommentOnForeignMemo.Name,
|
||||
ReactionType: "🔥",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ts.Store.UpsertReaction(ctx, &store.Reaction{
|
||||
CreatorID: user.ID,
|
||||
ContentID: foreignMemo.Name,
|
||||
ReactionType: "👋",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
peerReactionOnForeignMemo, err := ts.Store.UpsertReaction(ctx, &store.Reaction{
|
||||
CreatorID: peer.ID,
|
||||
ContentID: foreignMemo.Name,
|
||||
ReactionType: "✅",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.CreateMemoShare(ctx, &store.MemoShare{
|
||||
UID: "share-owner-ownmemo",
|
||||
MemoID: ownMemoStore.ID,
|
||||
CreatorID: user.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = ts.Store.CreateMemoShare(ctx, &store.MemoShare{
|
||||
UID: "share-owner-foreignmemo",
|
||||
MemoID: foreignMemoStore.ID,
|
||||
CreatorID: user.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
peerShare, err := ts.Store.CreateMemoShare(ctx, &store.MemoShare{
|
||||
UID: "share-peer-foreignmemo",
|
||||
MemoID: foreignMemoStore.ID,
|
||||
CreatorID: peer.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.CreateUserIdentity(ctx, &store.UserIdentity{
|
||||
UserID: user.ID,
|
||||
Provider: "google",
|
||||
ExternUID: "resource-owner-google-sub",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
|
||||
TokenId: "pat-owner",
|
||||
TokenHash: "pat-owner-hash",
|
||||
Description: "owner pat",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
headerCtx := apiv1.WithHeaderCarrier(ctx)
|
||||
authCtx := ts.CreateUserContext(headerCtx, user.ID)
|
||||
_, err = ts.Service.DeleteUser(authCtx, &v1pb.DeleteUserRequest{
|
||||
Name: apiv1.BuildUserName(user.Username),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
deletedUser, err := ts.Store.GetUser(ctx, &store.FindUser{ID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, deletedUser)
|
||||
|
||||
for _, memoName := range []string{
|
||||
ownMemo.Name,
|
||||
peerCommentOnOwnMemo.Name,
|
||||
peerNestedCommentOnOwnMemo.Name,
|
||||
userCommentOnForeignMemo.Name,
|
||||
peerReplyToUserComment.Name,
|
||||
} {
|
||||
memoUID, extractErr := apiv1.ExtractMemoUIDFromName(memoName)
|
||||
require.NoError(t, extractErr)
|
||||
memo, getErr := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &memoUID})
|
||||
require.NoError(t, getErr)
|
||||
require.Nil(t, memo, memoName)
|
||||
}
|
||||
|
||||
foreignMemoAfterDelete, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &foreignMemoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, foreignMemoAfterDelete)
|
||||
|
||||
for _, attachmentID := range []int32{attachedAttachment.ID, unattachedAttachment.ID} {
|
||||
attachment, getErr := ts.Store.GetAttachment(ctx, &store.FindAttachment{ID: &attachmentID})
|
||||
require.NoError(t, getErr)
|
||||
require.Nil(t, attachment)
|
||||
}
|
||||
peerAttachmentAfterDelete, err := ts.Store.GetAttachment(ctx, &store.FindAttachment{ID: &peerAttachment.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, peerAttachmentAfterDelete)
|
||||
_, err = os.Stat(thumbnailCachePath)
|
||||
require.ErrorIs(t, err, os.ErrNotExist)
|
||||
_, err = os.Stat(motionCachePath)
|
||||
require.ErrorIs(t, err, os.ErrNotExist)
|
||||
|
||||
ownMemoReactions, err := ts.Store.ListReactions(ctx, &store.FindReaction{ContentID: &ownMemo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, ownMemoReactions)
|
||||
|
||||
userCommentReactions, err := ts.Store.ListReactions(ctx, &store.FindReaction{ContentID: &userCommentOnForeignMemo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, userCommentReactions)
|
||||
|
||||
foreignMemoReactions, err := ts.Store.ListReactions(ctx, &store.FindReaction{ContentID: &foreignMemo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, foreignMemoReactions, 1)
|
||||
require.Equal(t, peerReactionOnForeignMemo.ID, foreignMemoReactions[0].ID)
|
||||
|
||||
ownerShares, err := ts.Store.ListMemoShares(ctx, &store.FindMemoShare{CreatorID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, ownerShares)
|
||||
|
||||
peerShares, err := ts.Store.ListMemoShares(ctx, &store.FindMemoShare{CreatorID: &peer.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, peerShares, 1)
|
||||
require.Equal(t, peerShare.ID, peerShares[0].ID)
|
||||
|
||||
sentInboxes, err := ts.Store.ListInboxes(ctx, &store.FindInbox{SenderID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, sentInboxes)
|
||||
receivedInboxes, err := ts.Store.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, receivedInboxes)
|
||||
|
||||
identities, err := ts.Store.ListUserIdentities(ctx, &store.FindUserIdentity{UserID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, identities)
|
||||
|
||||
patSetting, err := ts.Store.GetUserSetting(ctx, &store.FindUserSetting{
|
||||
UserID: &user.ID,
|
||||
Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, patSetting)
|
||||
}
|
||||
|
||||
func TestDeleteUserRollbackPreservesAllResources(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
user, err := ts.CreateRegularUser(ctx, "rollback-owner")
|
||||
require.NoError(t, err)
|
||||
peer, err := ts.CreateRegularUser(ctx, "rollback-peer")
|
||||
require.NoError(t, err)
|
||||
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
ownMemo, err := ts.Service.CreateMemo(userCtx, &v1pb.CreateMemoRequest{
|
||||
Memo: &v1pb.Memo{
|
||||
Content: "rollback owner memo",
|
||||
Visibility: v1pb.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ownMemoUID, err := apiv1.ExtractMemoUIDFromName(ownMemo.Name)
|
||||
require.NoError(t, err)
|
||||
ownMemoStore, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &ownMemoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, ownMemoStore)
|
||||
|
||||
attachment, err := ts.Store.CreateAttachment(ctx, &store.Attachment{
|
||||
UID: "attach-rollback-owner",
|
||||
CreatorID: user.ID,
|
||||
Filename: "rollback.txt",
|
||||
Type: "text/plain",
|
||||
Size: 8,
|
||||
Blob: []byte("rollback"),
|
||||
MemoID: &ownMemoStore.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
reaction, err := ts.Store.UpsertReaction(ctx, &store.Reaction{
|
||||
CreatorID: user.ID,
|
||||
ContentID: ownMemo.Name,
|
||||
ReactionType: "💥",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
share, err := ts.Store.CreateMemoShare(ctx, &store.MemoShare{
|
||||
UID: "share-rollback-owner",
|
||||
MemoID: ownMemoStore.ID,
|
||||
CreatorID: user.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
||||
SenderID: peer.ID,
|
||||
ReceiverID: user.ID,
|
||||
Status: store.UNREAD,
|
||||
Message: &storepb.InboxMessage{
|
||||
Type: storepb.InboxMessage_MEMO_COMMENT,
|
||||
Payload: &storepb.InboxMessage_MemoComment{
|
||||
MemoComment: &storepb.InboxMessage_MemoCommentPayload{
|
||||
MemoId: ownMemoStore.ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.CreateUserIdentity(ctx, &store.UserIdentity{
|
||||
UserID: user.ID,
|
||||
Provider: "google",
|
||||
ExternUID: "rollback-owner-google-sub",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = ts.Store.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
|
||||
TokenId: "pat-rollback-owner",
|
||||
TokenHash: "pat-rollback-owner-hash",
|
||||
Description: "rollback pat",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
headerCtx := apiv1.WithHeaderCarrier(ctx)
|
||||
failCtx := store.WithDeleteUserFailpoint(headerCtx, store.DeleteUserFailpointBeforeCommit)
|
||||
authCtx := ts.CreateUserContext(failCtx, user.ID)
|
||||
_, err = ts.Service.DeleteUser(authCtx, &v1pb.DeleteUserRequest{
|
||||
Name: apiv1.BuildUserName(user.Username),
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "delete user failpoint before commit")
|
||||
|
||||
userAfterRollback, err := ts.Store.GetUser(ctx, &store.FindUser{ID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, userAfterRollback)
|
||||
|
||||
memoAfterRollback, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &ownMemoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memoAfterRollback)
|
||||
|
||||
attachmentAfterRollback, err := ts.Store.GetAttachment(ctx, &store.FindAttachment{ID: &attachment.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, attachmentAfterRollback)
|
||||
|
||||
reactionAfterRollback, err := ts.Store.GetReaction(ctx, &store.FindReaction{ID: &reaction.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, reactionAfterRollback)
|
||||
|
||||
shareAfterRollback, err := ts.Store.GetMemoShare(ctx, &store.FindMemoShare{ID: &share.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, shareAfterRollback)
|
||||
|
||||
inboxesAfterRollback, err := ts.Store.ListInboxes(ctx, &store.FindInbox{ID: &inbox.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, inboxesAfterRollback, 1)
|
||||
|
||||
identitiesAfterRollback, err := ts.Store.ListUserIdentities(ctx, &store.FindUserIdentity{UserID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, identitiesAfterRollback, 1)
|
||||
|
||||
patSetting, err := ts.Store.GetUserSetting(ctx, &store.FindUserSetting{
|
||||
UserID: &user.ID,
|
||||
Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, patSetting)
|
||||
}
|
||||
|
||||
func TestDeleteUserReturnsErrorWhenAttachmentStorageCleanupFails(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
ctx := context.Background()
|
||||
user, err := ts.CreateRegularUser(ctx, "cleanup-failure-owner")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Store.CreateAttachment(ctx, &store.Attachment{
|
||||
UID: "attach-cleanup-failure",
|
||||
CreatorID: user.ID,
|
||||
Filename: "failure.txt",
|
||||
Type: "text/plain",
|
||||
Size: 7,
|
||||
Blob: []byte("failure"),
|
||||
StorageType: storepb.AttachmentStorageType_LOCAL,
|
||||
Reference: "cleanup-failure.txt",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
headerCtx := apiv1.WithHeaderCarrier(ctx)
|
||||
failCtx := store.WithDeleteAttachmentStorageFailpoint(headerCtx)
|
||||
authCtx := ts.CreateUserContext(failCtx, user.ID)
|
||||
_, err = ts.Service.DeleteUser(authCtx, &v1pb.DeleteUserRequest{
|
||||
Name: apiv1.BuildUserName(user.Username),
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorContains(t, err, "attachment storage cleanup failed")
|
||||
require.ErrorContains(t, err, "attachment_id=")
|
||||
require.ErrorContains(t, err, store.ErrDeleteAttachmentStorageFailpoint.Error())
|
||||
|
||||
deletedUser, err := ts.Store.GetUser(ctx, &store.FindUser{ID: &user.ID})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, deletedUser)
|
||||
}
|
||||
Reference in New Issue
Block a user