0.29.1原版
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
apiv1 "github.com/usememos/memos/proto/gen/api/v1"
|
||||
"github.com/usememos/memos/store"
|
||||
)
|
||||
|
||||
func TestSetMemoAttachments(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("SetMemoAttachments success by memo owner", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
// Create user
|
||||
user, err := ts.CreateRegularUser(ctx, "user")
|
||||
require.NoError(t, err)
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
|
||||
// Create memo
|
||||
memo, err := ts.Service.CreateMemo(userCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "Test memo",
|
||||
Visibility: apiv1.Visibility_PRIVATE,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memo)
|
||||
|
||||
// Create attachment
|
||||
attachment, err := ts.Service.CreateAttachment(userCtx, &apiv1.CreateAttachmentRequest{
|
||||
Attachment: &apiv1.Attachment{
|
||||
Filename: "test.txt",
|
||||
Size: 5,
|
||||
Type: "text/plain",
|
||||
Content: []byte("hello"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, attachment)
|
||||
|
||||
// Set memo attachments - should succeed
|
||||
_, err = ts.Service.SetMemoAttachments(userCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: memo.Name,
|
||||
Attachments: []*apiv1.Attachment{
|
||||
{Name: attachment.Name},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments success by host user", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
// Create regular user
|
||||
regularUser, err := ts.CreateRegularUser(ctx, "user")
|
||||
require.NoError(t, err)
|
||||
regularUserCtx := ts.CreateUserContext(ctx, regularUser.ID)
|
||||
|
||||
// Create host user
|
||||
hostUser, err := ts.CreateHostUser(ctx, "admin")
|
||||
require.NoError(t, err)
|
||||
hostCtx := ts.CreateUserContext(ctx, hostUser.ID)
|
||||
|
||||
// Create memo by regular user
|
||||
memo, err := ts.Service.CreateMemo(regularUserCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "Test memo",
|
||||
Visibility: apiv1.Visibility_PRIVATE,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memo)
|
||||
|
||||
// Host user can modify attachments - should succeed
|
||||
_, err = ts.Service.SetMemoAttachments(hostCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: memo.Name,
|
||||
Attachments: []*apiv1.Attachment{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments permission denied for non-owner", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
// Create user1
|
||||
user1, err := ts.CreateRegularUser(ctx, "user1")
|
||||
require.NoError(t, err)
|
||||
user1Ctx := ts.CreateUserContext(ctx, user1.ID)
|
||||
|
||||
// Create user2
|
||||
user2, err := ts.CreateRegularUser(ctx, "user2")
|
||||
require.NoError(t, err)
|
||||
user2Ctx := ts.CreateUserContext(ctx, user2.ID)
|
||||
|
||||
// Create memo by user1
|
||||
memo, err := ts.Service.CreateMemo(user1Ctx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "Test memo",
|
||||
Visibility: apiv1.Visibility_PRIVATE,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memo)
|
||||
|
||||
// User2 tries to modify attachments - should fail
|
||||
_, err = ts.Service.SetMemoAttachments(user2Ctx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: memo.Name,
|
||||
Attachments: []*apiv1.Attachment{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "permission denied")
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments unauthenticated", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
// Create user
|
||||
user, err := ts.CreateRegularUser(ctx, "user")
|
||||
require.NoError(t, err)
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
|
||||
// Create memo
|
||||
memo, err := ts.Service.CreateMemo(userCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "Test memo",
|
||||
Visibility: apiv1.Visibility_PRIVATE,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memo)
|
||||
|
||||
// Unauthenticated user tries to modify attachments - should fail
|
||||
_, err = ts.Service.SetMemoAttachments(ctx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: memo.Name,
|
||||
Attachments: []*apiv1.Attachment{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "not authenticated")
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments memo not found", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
// Create user
|
||||
user, err := ts.CreateRegularUser(ctx, "user")
|
||||
require.NoError(t, err)
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
|
||||
// Try to set attachments on non-existent memo - should fail
|
||||
_, err = ts.Service.SetMemoAttachments(userCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: "memos/nonexistent-uid-12345",
|
||||
Attachments: []*apiv1.Attachment{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "not found")
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments removes incomplete live photo groups", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
user, err := ts.CreateRegularUser(ctx, "live_group_user")
|
||||
require.NoError(t, err)
|
||||
userCtx := ts.CreateUserContext(ctx, user.ID)
|
||||
|
||||
still, err := ts.Service.CreateAttachment(userCtx, &apiv1.CreateAttachmentRequest{
|
||||
Attachment: &apiv1.Attachment{
|
||||
Filename: "live.heic",
|
||||
Type: "image/heic",
|
||||
Content: []byte("still"),
|
||||
MotionMedia: &apiv1.MotionMedia{
|
||||
Family: apiv1.MotionMediaFamily_APPLE_LIVE_PHOTO,
|
||||
Role: apiv1.MotionMediaRole_STILL,
|
||||
GroupId: "memo-live-group",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
video, err := ts.Service.CreateAttachment(userCtx, &apiv1.CreateAttachmentRequest{
|
||||
Attachment: &apiv1.Attachment{
|
||||
Filename: "live.mov",
|
||||
Type: "video/quicktime",
|
||||
Content: []byte("video"),
|
||||
MotionMedia: &apiv1.MotionMedia{
|
||||
Family: apiv1.MotionMediaFamily_APPLE_LIVE_PHOTO,
|
||||
Role: apiv1.MotionMediaRole_VIDEO,
|
||||
GroupId: "memo-live-group",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
memo, err := ts.Service.CreateMemo(userCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "memo with live photo",
|
||||
Visibility: apiv1.Visibility_PRIVATE,
|
||||
Attachments: []*apiv1.Attachment{
|
||||
{Name: still.Name},
|
||||
{Name: video.Name},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Service.SetMemoAttachments(userCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: memo.Name,
|
||||
Attachments: []*apiv1.Attachment{
|
||||
{Name: still.Name},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
response, err := ts.Service.ListMemoAttachments(userCtx, &apiv1.ListMemoAttachmentsRequest{Name: memo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, response.Attachments, 0)
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments denies attaching another user's attachment", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
victim, err := ts.CreateRegularUser(ctx, "attachment_victim")
|
||||
require.NoError(t, err)
|
||||
attacker, err := ts.CreateRegularUser(ctx, "attachment_attacker")
|
||||
require.NoError(t, err)
|
||||
victimCtx := ts.CreateUserContext(ctx, victim.ID)
|
||||
attackerCtx := ts.CreateUserContext(ctx, attacker.ID)
|
||||
|
||||
victimAttachment, err := ts.Service.CreateAttachment(victimCtx, &apiv1.CreateAttachmentRequest{
|
||||
Attachment: &apiv1.Attachment{
|
||||
Filename: "secret.txt",
|
||||
Size: 6,
|
||||
Type: "text/plain",
|
||||
Content: []byte("secret"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
victimMemo, err := ts.Service.CreateMemo(victimCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "victim protected memo",
|
||||
Visibility: apiv1.Visibility_PROTECTED,
|
||||
Attachments: []*apiv1.Attachment{
|
||||
{Name: victimAttachment.Name},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
attackerMemo, err := ts.Service.CreateMemo(attackerCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "attacker public memo",
|
||||
Visibility: apiv1.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Service.SetMemoAttachments(attackerCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: attackerMemo.Name,
|
||||
Attachments: []*apiv1.Attachment{
|
||||
{Name: victimAttachment.Name},
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "cannot attach another user's attachment")
|
||||
|
||||
victimAttachments, err := ts.Service.ListMemoAttachments(victimCtx, &apiv1.ListMemoAttachmentsRequest{Name: victimMemo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, victimAttachments.Attachments, 1)
|
||||
require.Equal(t, victimAttachment.Name, victimAttachments.Attachments[0].Name)
|
||||
|
||||
attackerAttachments, err := ts.Service.ListMemoAttachments(attackerCtx, &apiv1.ListMemoAttachmentsRequest{Name: attackerMemo.Name})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, attackerAttachments.Attachments)
|
||||
})
|
||||
|
||||
t.Run("SetMemoAttachments denies removing another user's attached attachment", func(t *testing.T) {
|
||||
ts := NewTestService(t)
|
||||
defer ts.Cleanup()
|
||||
|
||||
victim, err := ts.CreateRegularUser(ctx, "remove_victim")
|
||||
require.NoError(t, err)
|
||||
attacker, err := ts.CreateRegularUser(ctx, "remove_attacker")
|
||||
require.NoError(t, err)
|
||||
victimCtx := ts.CreateUserContext(ctx, victim.ID)
|
||||
attackerCtx := ts.CreateUserContext(ctx, attacker.ID)
|
||||
|
||||
victimAttachment, err := ts.Service.CreateAttachment(victimCtx, &apiv1.CreateAttachmentRequest{
|
||||
Attachment: &apiv1.Attachment{
|
||||
Filename: "kept.txt",
|
||||
Size: 4,
|
||||
Type: "text/plain",
|
||||
Content: []byte("kept"),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
attackerMemo, err := ts.Service.CreateMemo(attackerCtx, &apiv1.CreateMemoRequest{
|
||||
Memo: &apiv1.Memo{
|
||||
Content: "contaminated memo",
|
||||
Visibility: apiv1.Visibility_PUBLIC,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
attachmentUID := strings.TrimPrefix(victimAttachment.Name, "attachments/")
|
||||
attachment, err := ts.Store.GetAttachment(ctx, &store.FindAttachment{UID: &attachmentUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, attachment)
|
||||
|
||||
memoUID := strings.TrimPrefix(attackerMemo.Name, "memos/")
|
||||
memo, err := ts.Store.GetMemo(ctx, &store.FindMemo{UID: &memoUID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, memo)
|
||||
|
||||
err = ts.Store.UpdateAttachment(ctx, &store.UpdateAttachment{
|
||||
ID: attachment.ID,
|
||||
MemoID: &memo.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ts.Service.SetMemoAttachments(attackerCtx, &apiv1.SetMemoAttachmentsRequest{
|
||||
Name: attackerMemo.Name,
|
||||
Attachments: []*apiv1.Attachment{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "cannot remove another user's attachment")
|
||||
|
||||
attachmentAfter, err := ts.Store.GetAttachment(ctx, &store.FindAttachment{ID: &attachment.ID})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, attachmentAfter)
|
||||
require.NotNil(t, attachmentAfter.MemoID)
|
||||
require.Equal(t, memo.ID, *attachmentAfter.MemoID)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user