memos/server/router/api/v1/test/memo_move_test.go

112 lines
5.4 KiB
Go

package test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/fieldmaskpb"
apiv1 "github.com/usememos/memos/proto/gen/api/v1"
"github.com/usememos/memos/store"
)
func TestMoveMemoPreservesIndependentConversation(t *testing.T) {
ctx := context.Background()
ts := NewTestService(t)
defer ts.Cleanup()
author, err := ts.CreateRegularUser(ctx, "move-author")
require.NoError(t, err)
commenter, err := ts.CreateRegularUser(ctx, "move-commenter")
require.NoError(t, err)
reader, err := ts.CreateRegularUser(ctx, "move-reader")
require.NoError(t, err)
authorCtx := ts.CreateUserContext(ctx, author.ID)
commenterCtx := ts.CreateUserContext(ctx, commenter.ID)
readerCtx := ts.CreateUserContext(ctx, reader.ID)
source, err := ts.Store.CreateSpace(ctx, &store.Space{UID: "move-source", Title: "Source"}, author.ID)
require.NoError(t, err)
target, err := ts.Store.CreateSpace(ctx, &store.Space{UID: "move-target", Title: "Target"}, author.ID)
require.NoError(t, err)
for _, member := range []*store.SpaceMember{
{SpaceID: source.ID, UserID: commenter.ID, Role: store.SpaceMemberRoleUser},
{SpaceID: target.ID, UserID: reader.ID, Role: store.SpaceMemberRoleUser},
} {
_, err := ts.InviteAndAcceptSpaceMember(ctx, member, author.ID)
require.NoError(t, err)
}
sourceName, targetName := "spaces/"+source.UID, "spaces/"+target.UID
reference, err := ts.Service.CreateMemo(authorCtx, &apiv1.CreateMemoRequest{Memo: &apiv1.Memo{
Content: "source reference", Space: &sourceName, Visibility: apiv1.Visibility_SPACE,
}})
require.NoError(t, err)
attachment, err := ts.Service.CreateAttachment(authorCtx, &apiv1.CreateAttachmentRequest{
Attachment: &apiv1.Attachment{Filename: "proposal.txt", Type: "text/plain", Content: []byte("proposal attachment")},
})
require.NoError(t, err)
original, err := ts.Service.CreateMemo(authorCtx, &apiv1.CreateMemoRequest{Memo: &apiv1.Memo{
Content: "original memo", Space: &sourceName, Visibility: apiv1.Visibility_SPACE,
Attachments: []*apiv1.Attachment{attachment},
Relations: []*apiv1.MemoRelation{{Type: apiv1.MemoRelation_REFERENCE, RelatedMemo: &apiv1.MemoRelation_Memo{Name: reference.Name}}},
}})
require.NoError(t, err)
comment, err := ts.Service.CreateMemoComment(commenterCtx, &apiv1.CreateMemoCommentRequest{
Name: original.Name, Comment: &apiv1.Memo{Content: "source comment", Space: &sourceName, Visibility: apiv1.Visibility_SPACE},
})
require.NoError(t, err)
reaction, err := ts.Service.UpsertMemoReaction(commenterCtx, &apiv1.UpsertMemoReactionRequest{
Name: original.Name, Reaction: &apiv1.Reaction{ReactionType: "👍"},
})
require.NoError(t, err)
moved, err := ts.Service.UpdateMemo(authorCtx, &apiv1.UpdateMemoRequest{
Memo: &apiv1.Memo{Name: original.Name, Space: &targetName, Visibility: apiv1.Visibility_SPACE},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"space", "visibility"}},
})
require.NoError(t, err)
require.Equal(t, original.Name, moved.Name)
require.Equal(t, original.Creator, moved.Creator)
require.Equal(t, original.CreateTime, moved.CreateTime)
require.Equal(t, original.UpdateTime, moved.UpdateTime)
require.Equal(t, original.Content, moved.Content)
require.Len(t, moved.Attachments, 1)
require.Equal(t, original.Attachments, moved.Attachments)
require.Len(t, moved.Relations, 2, "the author in both Spaces retains references and comments")
require.Len(t, moved.Reactions, 1)
require.Equal(t, reaction.Name, moved.Reactions[0].Name)
_, err = ts.Service.GetMemo(commenterCtx, &apiv1.GetMemoRequest{Name: original.Name})
require.Equal(t, codes.PermissionDenied, status.Code(err))
readableComment, err := ts.Service.GetMemo(commenterCtx, &apiv1.GetMemoRequest{Name: comment.Name})
require.NoError(t, err)
require.Equal(t, original.Name, readableComment.GetParent(), "parent identity survives lost parent access")
require.Equal(t, sourceName, readableComment.GetSpace())
require.Equal(t, comment.Visibility, readableComment.Visibility)
require.Equal(t, comment.Content, readableComment.Content)
require.Empty(t, readableComment.Relations, "the inaccessible parent must not appear in relation previews")
for _, viewer := range []context.Context{authorCtx, readerCtx} {
comments, err := ts.Service.ListMemoComments(viewer, &apiv1.ListMemoCommentsRequest{Name: original.Name})
require.NoError(t, err)
if viewer == authorCtx {
require.Len(t, comments.Memos, 1)
} else {
require.Empty(t, comments.Memos)
}
}
destinationMemo, err := ts.Service.GetMemo(readerCtx, &apiv1.GetMemoRequest{Name: original.Name})
require.NoError(t, err)
require.Empty(t, destinationMemo.Relations)
require.Len(t, destinationMemo.Reactions, 1, "existing reactions are visible to new readers")
_, err = ts.Service.GetMemo(readerCtx, &apiv1.GetMemoRequest{Name: comment.Name})
require.Equal(t, codes.PermissionDenied, status.Code(err))
_, err = ts.Service.DeleteMemoReaction(readerCtx, &apiv1.DeleteMemoReactionRequest{Name: reaction.Name})
require.Equal(t, codes.PermissionDenied, status.Code(err))
_, err = ts.Service.DeleteMemoReaction(commenterCtx, &apiv1.DeleteMemoReactionRequest{Name: reaction.Name})
require.NoError(t, err, "the creator can withdraw a reaction without parent access")
reactions, err := ts.Service.ListMemoReactions(readerCtx, &apiv1.ListMemoReactionsRequest{Name: original.Name})
require.NoError(t, err)
require.Empty(t, reactions.Reactions)
}