memos/store/test/inbox_test.go

593 lines
16 KiB
Go

package test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func TestInboxStore(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
const systemBotID int32 = 0
create := &store.Inbox{
SenderID: systemBotID,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_MEMO_COMMENT,
},
}
inbox, err := ts.CreateInbox(ctx, create)
require.NoError(t, err)
require.NotNil(t, inbox)
require.Equal(t, create.Message, inbox.Message)
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 1, len(inboxes))
require.Equal(t, inbox, inboxes[0])
updatedInbox, err := ts.UpdateInbox(ctx, &store.UpdateInbox{
ID: inbox.ID,
Status: store.ARCHIVED,
})
require.NoError(t, err)
require.NotNil(t, updatedInbox)
require.Equal(t, store.ARCHIVED, updatedInbox.Status)
err = ts.DeleteInbox(ctx, &store.DeleteInbox{
ID: inbox.ID,
})
require.NoError(t, err)
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
})
require.NoError(t, err)
require.Equal(t, 0, len(inboxes))
ts.Close()
}
func TestInboxListByID(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
inbox, err := ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// List by ID
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ID: &inbox.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, inbox.ID, inboxes[0].ID)
// List by non-existent ID
nonExistentID := int32(99999)
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{ID: &nonExistentID})
require.NoError(t, err)
require.Len(t, inboxes, 0)
ts.Close()
}
func TestInboxListBySenderID(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user1, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
user2, err := createTestingUserWithRole(ctx, ts, "user2", store.RoleUser)
require.NoError(t, err)
// Create inbox from system bot (senderID = 0)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// Create inbox from user2
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: user2.ID,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// List by sender ID = user2
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{SenderID: &user2.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, user2.ID, inboxes[0].SenderID)
// List by sender ID = 0 (system bot)
systemBotID := int32(0)
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{SenderID: &systemBotID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, int32(0), inboxes[0].SenderID)
ts.Close()
}
func TestInboxListByStatus(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create UNREAD inbox
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// Create another inbox and archive it
inbox2, err := ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
_, err = ts.UpdateInbox(ctx, &store.UpdateInbox{ID: inbox2.ID, Status: store.ARCHIVED})
require.NoError(t, err)
// List by UNREAD status
unreadStatus := store.UNREAD
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{Status: &unreadStatus})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, store.UNREAD, inboxes[0].Status)
// List by ARCHIVED status
archivedStatus := store.ARCHIVED
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{Status: &archivedStatus})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, store.ARCHIVED, inboxes[0].Status)
ts.Close()
}
func TestInboxListByMessageType(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create MEMO_COMMENT inboxes
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// List by MEMO_COMMENT type
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{MessageType: &memoCommentType})
require.NoError(t, err)
require.Len(t, inboxes, 2)
for _, inbox := range inboxes {
require.Equal(t, storepb.InboxMessage_MEMO_COMMENT, inbox.Message.Type)
}
ts.Close()
}
func TestInboxListPagination(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create 5 inboxes
for i := 0; i < 5; i++ {
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
}
// Test Limit only
limit := 3
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
Limit: &limit,
})
require.NoError(t, err)
require.Len(t, inboxes, 3)
// Test Limit + Offset (offset requires limit in the implementation)
limit = 2
offset := 2
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
Limit: &limit,
Offset: &offset,
})
require.NoError(t, err)
require.Len(t, inboxes, 2)
// Test Limit + Offset skipping to end
limit = 10
offset = 3
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
Limit: &limit,
Offset: &offset,
})
require.NoError(t, err)
require.Len(t, inboxes, 2) // Only 2 remaining after offset of 3
ts.Close()
}
func TestInboxListCombinedFilters(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user1, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
user2, err := createTestingUserWithRole(ctx, ts, "user2", store.RoleUser)
require.NoError(t, err)
// Create various inboxes
// user2 -> user1, MEMO_COMMENT, UNREAD
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: user2.ID,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// user2 -> user1, TYPE_UNSPECIFIED, UNREAD
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: user2.ID,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_TYPE_UNSPECIFIED},
})
require.NoError(t, err)
// system -> user1, MEMO_COMMENT, ARCHIVED
inbox3, err := ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
_, err = ts.UpdateInbox(ctx, &store.UpdateInbox{ID: inbox3.ID, Status: store.ARCHIVED})
require.NoError(t, err)
// Combined filter: ReceiverID + SenderID + Status
unreadStatus := store.UNREAD
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user1.ID,
SenderID: &user2.ID,
Status: &unreadStatus,
})
require.NoError(t, err)
require.Len(t, inboxes, 2)
// Combined filter: ReceiverID + MessageType + Status
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user1.ID,
MessageType: &memoCommentType,
Status: &unreadStatus,
})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, user2.ID, inboxes[0].SenderID)
ts.Close()
}
func TestInboxMessagePayload(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create inbox with message payload containing activity ID
activityID := int32(123)
inbox, err := ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_MEMO_COMMENT,
ActivityId: &activityID,
},
})
require.NoError(t, err)
require.NotNil(t, inbox.Message)
require.Equal(t, storepb.InboxMessage_MEMO_COMMENT, inbox.Message.Type)
require.Equal(t, activityID, *inbox.Message.ActivityId)
// List and verify payload is preserved
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, activityID, *inboxes[0].Message.ActivityId)
ts.Close()
}
func TestInboxUpdateStatus(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
inbox, err := ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
require.Equal(t, store.UNREAD, inbox.Status)
// Update to ARCHIVED
updated, err := ts.UpdateInbox(ctx, &store.UpdateInbox{
ID: inbox.ID,
Status: store.ARCHIVED,
})
require.NoError(t, err)
require.Equal(t, store.ARCHIVED, updated.Status)
require.Equal(t, inbox.ID, updated.ID)
// Verify the update persisted
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ID: &inbox.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, store.ARCHIVED, inboxes[0].Status)
ts.Close()
}
func TestInboxListByMessageTypeMultipleTypes(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create inboxes with different message types
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_TYPE_UNSPECIFIED},
})
require.NoError(t, err)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// Filter by MEMO_COMMENT - should get 2
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
MessageType: &memoCommentType,
})
require.NoError(t, err)
require.Len(t, inboxes, 2)
for _, inbox := range inboxes {
require.Equal(t, storepb.InboxMessage_MEMO_COMMENT, inbox.Message.Type)
}
// Filter by TYPE_UNSPECIFIED - should get 1
unspecifiedType := storepb.InboxMessage_TYPE_UNSPECIFIED
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
MessageType: &unspecifiedType,
})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, storepb.InboxMessage_TYPE_UNSPECIFIED, inboxes[0].Message.Type)
ts.Close()
}
func TestInboxMessageTypeFilterWithPayload(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create inbox with full payload
activityID := int32(456)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_MEMO_COMMENT,
ActivityId: &activityID,
},
})
require.NoError(t, err)
// Create inbox with different type but also has payload
otherActivityID := int32(789)
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{
Type: storepb.InboxMessage_TYPE_UNSPECIFIED,
ActivityId: &otherActivityID,
},
})
require.NoError(t, err)
// Filter by type should work correctly even with complex JSON payload
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
MessageType: &memoCommentType,
})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, activityID, *inboxes[0].Message.ActivityId)
ts.Close()
}
func TestInboxMessageTypeFilterWithStatusAndPagination(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create multiple inboxes with various combinations
for i := 0; i < 5; i++ {
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
}
// Archive 2 of them
allInboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user.ID})
require.NoError(t, err)
for i := 0; i < 2; i++ {
_, err = ts.UpdateInbox(ctx, &store.UpdateInbox{ID: allInboxes[i].ID, Status: store.ARCHIVED})
require.NoError(t, err)
}
// Filter by type + status + pagination
memoCommentType := storepb.InboxMessage_MEMO_COMMENT
unreadStatus := store.UNREAD
limit := 2
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
MessageType: &memoCommentType,
Status: &unreadStatus,
Limit: &limit,
})
require.NoError(t, err)
require.Len(t, inboxes, 2)
for _, inbox := range inboxes {
require.Equal(t, storepb.InboxMessage_MEMO_COMMENT, inbox.Message.Type)
require.Equal(t, store.UNREAD, inbox.Status)
}
// Get next page
offset := 2
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &user.ID,
MessageType: &memoCommentType,
Status: &unreadStatus,
Limit: &limit,
Offset: &offset,
})
require.NoError(t, err)
require.Len(t, inboxes, 1) // Only 1 remaining (3 unread total, got 2, now 1 left)
ts.Close()
}
func TestInboxMultipleReceivers(t *testing.T) {
t.Parallel()
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user1, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
user2, err := createTestingUserWithRole(ctx, ts, "user2", store.RoleUser)
require.NoError(t, err)
// Create inbox for user1
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user1.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// Create inbox for user2
_, err = ts.CreateInbox(ctx, &store.Inbox{
SenderID: 0,
ReceiverID: user2.ID,
Status: store.UNREAD,
Message: &storepb.InboxMessage{Type: storepb.InboxMessage_MEMO_COMMENT},
})
require.NoError(t, err)
// User1 should only see their inbox
inboxes, err := ts.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user1.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, user1.ID, inboxes[0].ReceiverID)
// User2 should only see their inbox
inboxes, err = ts.ListInboxes(ctx, &store.FindInbox{ReceiverID: &user2.ID})
require.NoError(t, err)
require.Len(t, inboxes, 1)
require.Equal(t, user2.ID, inboxes[0].ReceiverID)
ts.Close()
}