mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
560 lines
14 KiB
Go
560 lines
14 KiB
Go
package v1
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
|
|
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
|
storepb "github.com/usememos/memos/proto/gen/store"
|
|
"github.com/usememos/memos/store"
|
|
)
|
|
|
|
func TestListInboxes(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("ListInboxes success", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
// List inboxes (should be empty initially)
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: fmt.Sprintf("users/%d", user.ID),
|
|
}
|
|
|
|
resp, err := ts.Service.ListInboxes(userCtx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Empty(t, resp.Inboxes)
|
|
require.Equal(t, int32(0), resp.TotalSize)
|
|
})
|
|
|
|
t.Run("ListInboxes with pagination", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create some inbox entries
|
|
const systemBotID int32 = 0
|
|
for i := 0; i < 3; i++ {
|
|
_, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
// List inboxes with page size limit
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: fmt.Sprintf("users/%d", user.ID),
|
|
PageSize: 2,
|
|
}
|
|
|
|
resp, err := ts.Service.ListInboxes(userCtx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, 2, len(resp.Inboxes))
|
|
require.NotEmpty(t, resp.NextPageToken)
|
|
})
|
|
|
|
t.Run("ListInboxes permission denied for different user", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create two users
|
|
user1, err := ts.CreateRegularUser(ctx, "user1")
|
|
require.NoError(t, err)
|
|
user2, err := ts.CreateRegularUser(ctx, "user2")
|
|
require.NoError(t, err)
|
|
|
|
// Set user1 context but try to list user2's inboxes
|
|
userCtx := ts.CreateUserContext(ctx, user1.Username)
|
|
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: fmt.Sprintf("users/%d", user2.ID),
|
|
}
|
|
|
|
_, err = ts.Service.ListInboxes(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "cannot access inboxes")
|
|
})
|
|
|
|
t.Run("ListInboxes host can access other users' inboxes", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a host user and a regular user
|
|
hostUser, err := ts.CreateHostUser(ctx, "hostuser")
|
|
require.NoError(t, err)
|
|
regularUser, err := ts.CreateRegularUser(ctx, "regularuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox for the regular user
|
|
const systemBotID int32 = 0
|
|
_, err = ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: regularUser.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set host user context and try to list regular user's inboxes
|
|
hostCtx := ts.CreateUserContext(ctx, hostUser.Username)
|
|
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: fmt.Sprintf("users/%d", regularUser.ID),
|
|
}
|
|
|
|
resp, err := ts.Service.ListInboxes(hostCtx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, 1, len(resp.Inboxes))
|
|
})
|
|
|
|
t.Run("ListInboxes invalid parent format", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: "invalid-parent-format",
|
|
}
|
|
|
|
_, err = ts.Service.ListInboxes(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid parent name")
|
|
})
|
|
|
|
t.Run("ListInboxes unauthenticated", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
req := &v1pb.ListInboxesRequest{
|
|
Parent: "users/1",
|
|
}
|
|
|
|
_, err := ts.Service.ListInboxes(ctx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "user not authenticated")
|
|
})
|
|
}
|
|
|
|
func TestUpdateInbox(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("UpdateInbox success", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
// Update inbox status
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"status"},
|
|
},
|
|
}
|
|
|
|
resp, err := ts.Service.UpdateInbox(userCtx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, v1pb.Inbox_ARCHIVED, resp.Status)
|
|
})
|
|
|
|
t.Run("UpdateInbox permission denied for different user", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create two users
|
|
user1, err := ts.CreateRegularUser(ctx, "user1")
|
|
require.NoError(t, err)
|
|
user2, err := ts.CreateRegularUser(ctx, "user2")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry for user2
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user2.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user1 context but try to update user2's inbox
|
|
userCtx := ts.CreateUserContext(ctx, user1.Username)
|
|
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"status"},
|
|
},
|
|
}
|
|
|
|
_, err = ts.Service.UpdateInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "cannot update inbox")
|
|
})
|
|
|
|
t.Run("UpdateInbox missing update mask", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: "inboxes/1",
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
}
|
|
|
|
_, err = ts.Service.UpdateInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "update mask is required")
|
|
})
|
|
|
|
t.Run("UpdateInbox invalid name format", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: "invalid-inbox-name",
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"status"},
|
|
},
|
|
}
|
|
|
|
_, err = ts.Service.UpdateInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid inbox name")
|
|
})
|
|
|
|
t.Run("UpdateInbox not found", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: "inboxes/99999", // Non-existent inbox
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"status"},
|
|
},
|
|
}
|
|
|
|
_, err = ts.Service.UpdateInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
require.Equal(t, codes.NotFound, st.Code())
|
|
})
|
|
|
|
t.Run("UpdateInbox unsupported field", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"unsupported_field"},
|
|
},
|
|
}
|
|
|
|
_, err = ts.Service.UpdateInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "unsupported field")
|
|
})
|
|
}
|
|
|
|
func TestDeleteInbox(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("DeleteInbox success", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
// Delete inbox
|
|
req := &v1pb.DeleteInboxRequest{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
}
|
|
|
|
_, err = ts.Service.DeleteInbox(userCtx, req)
|
|
require.NoError(t, err)
|
|
|
|
// Verify inbox is deleted
|
|
inboxes, err := ts.Store.ListInboxes(ctx, &store.FindInbox{
|
|
ReceiverID: &user.ID,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(inboxes))
|
|
})
|
|
|
|
t.Run("DeleteInbox permission denied for different user", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create two users
|
|
user1, err := ts.CreateRegularUser(ctx, "user1")
|
|
require.NoError(t, err)
|
|
user2, err := ts.CreateRegularUser(ctx, "user2")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry for user2
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user2.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user1 context but try to delete user2's inbox
|
|
userCtx := ts.CreateUserContext(ctx, user1.Username)
|
|
|
|
req := &v1pb.DeleteInboxRequest{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
}
|
|
|
|
_, err = ts.Service.DeleteInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "cannot delete inbox")
|
|
})
|
|
|
|
t.Run("DeleteInbox invalid name format", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.DeleteInboxRequest{
|
|
Name: "invalid-inbox-name",
|
|
}
|
|
|
|
_, err = ts.Service.DeleteInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
require.Contains(t, err.Error(), "invalid inbox name")
|
|
})
|
|
|
|
t.Run("DeleteInbox not found", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
req := &v1pb.DeleteInboxRequest{
|
|
Name: "inboxes/99999", // Non-existent inbox
|
|
}
|
|
|
|
_, err = ts.Service.DeleteInbox(userCtx, req)
|
|
require.Error(t, err)
|
|
st, ok := status.FromError(err)
|
|
require.True(t, ok)
|
|
require.Equal(t, codes.NotFound, st.Code())
|
|
})
|
|
}
|
|
|
|
func TestInboxCRUDComplete(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("Complete CRUD lifecycle", func(t *testing.T) {
|
|
ts := NewTestService(t)
|
|
defer ts.Cleanup()
|
|
|
|
// Create a user
|
|
user, err := ts.CreateRegularUser(ctx, "testuser")
|
|
require.NoError(t, err)
|
|
|
|
// Create an inbox entry directly in store
|
|
const systemBotID int32 = 0
|
|
inbox, err := ts.Store.CreateInbox(ctx, &store.Inbox{
|
|
SenderID: systemBotID,
|
|
ReceiverID: user.ID,
|
|
Status: store.UNREAD,
|
|
Message: &storepb.InboxMessage{
|
|
Type: storepb.InboxMessage_MEMO_COMMENT,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Set user context
|
|
userCtx := ts.CreateUserContext(ctx, user.Username)
|
|
|
|
// 1. List inboxes - should have 1
|
|
listReq := &v1pb.ListInboxesRequest{
|
|
Parent: fmt.Sprintf("users/%d", user.ID),
|
|
}
|
|
listResp, err := ts.Service.ListInboxes(userCtx, listReq)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(listResp.Inboxes))
|
|
require.Equal(t, v1pb.Inbox_UNREAD, listResp.Inboxes[0].Status)
|
|
|
|
// 2. Update inbox status to ARCHIVED
|
|
updateReq := &v1pb.UpdateInboxRequest{
|
|
Inbox: &v1pb.Inbox{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
Status: v1pb.Inbox_ARCHIVED,
|
|
},
|
|
UpdateMask: &fieldmaskpb.FieldMask{
|
|
Paths: []string{"status"},
|
|
},
|
|
}
|
|
updateResp, err := ts.Service.UpdateInbox(userCtx, updateReq)
|
|
require.NoError(t, err)
|
|
require.Equal(t, v1pb.Inbox_ARCHIVED, updateResp.Status)
|
|
|
|
// 3. List inboxes again - should still have 1 but ARCHIVED
|
|
listResp, err = ts.Service.ListInboxes(userCtx, listReq)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(listResp.Inboxes))
|
|
require.Equal(t, v1pb.Inbox_ARCHIVED, listResp.Inboxes[0].Status)
|
|
|
|
// 4. Delete inbox
|
|
deleteReq := &v1pb.DeleteInboxRequest{
|
|
Name: fmt.Sprintf("inboxes/%d", inbox.ID),
|
|
}
|
|
_, err = ts.Service.DeleteInbox(userCtx, deleteReq)
|
|
require.NoError(t, err)
|
|
|
|
// 5. List inboxes - should be empty
|
|
listResp, err = ts.Service.ListInboxes(userCtx, listReq)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 0, len(listResp.Inboxes))
|
|
require.Equal(t, int32(0), listResp.TotalSize)
|
|
})
|
|
}
|