chore: add more tests

This commit is contained in:
Johnny 2026-01-12 22:28:42 +08:00
parent 8f9ff5634c
commit 31f634b71a
6 changed files with 1934 additions and 1 deletions

View File

@ -53,7 +53,8 @@ func (d *DB) ListInboxes(ctx context.Context, find *store.FindInbox) ([]*store.I
if find.MessageType != nil {
// Filter by message type using PostgreSQL JSON extraction
// Note: The type field in JSON is stored as string representation of the enum name
where, args = append(where, "message->>'type' = "+placeholder(len(args)+1)), append(args, find.MessageType.String())
// Cast to JSONB since the column is TEXT
where, args = append(where, "message::JSONB->>'type' = "+placeholder(len(args)+1)), append(args, find.MessageType.String())
}
query := "SELECT id, created_ts, sender_id, receiver_id, status, message FROM inbox WHERE " + strings.Join(where, " AND ") + " ORDER BY created_ts DESC"

View File

@ -99,3 +99,270 @@ func TestActivityListMultiple(t *testing.T) {
ts.Close()
}
func TestActivityListByType(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create activities with MEMO_COMMENT type
_, err = ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
_, err = ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
// List by type
activityType := store.ActivityTypeMemoComment
activities, err := ts.ListActivities(ctx, &store.FindActivity{Type: &activityType})
require.NoError(t, err)
require.Len(t, activities, 2)
for _, activity := range activities {
require.Equal(t, store.ActivityTypeMemoComment, activity.Type)
}
ts.Close()
}
func TestActivityPayloadMemoComment(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create activity with MemoComment payload
memoID := int32(123)
relatedMemoID := int32(456)
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{
MemoComment: &storepb.ActivityMemoCommentPayload{
MemoId: memoID,
RelatedMemoId: relatedMemoID,
},
},
})
require.NoError(t, err)
require.NotNil(t, activity.Payload)
require.NotNil(t, activity.Payload.MemoComment)
require.Equal(t, memoID, activity.Payload.MemoComment.MemoId)
require.Equal(t, relatedMemoID, activity.Payload.MemoComment.RelatedMemoId)
// Verify payload is preserved when listing
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
require.NoError(t, err)
require.NotNil(t, found.Payload.MemoComment)
require.Equal(t, memoID, found.Payload.MemoComment.MemoId)
require.Equal(t, relatedMemoID, found.Payload.MemoComment.RelatedMemoId)
ts.Close()
}
func TestActivityEmptyPayload(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create activity with empty payload
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
require.NotNil(t, activity.Payload)
// Verify empty payload is handled correctly
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
require.NoError(t, err)
require.NotNil(t, found.Payload)
require.Nil(t, found.Payload.MemoComment)
ts.Close()
}
func TestActivityLevel(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create activity with INFO level
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
require.Equal(t, store.ActivityLevelInfo, activity.Level)
// Verify level is preserved when listing
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
require.NoError(t, err)
require.Equal(t, store.ActivityLevelInfo, found.Level)
ts.Close()
}
func TestActivityCreatorID(t *testing.T) {
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 activity for user1
activity1, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user1.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
require.Equal(t, user1.ID, activity1.CreatorID)
// Create activity for user2
activity2, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user2.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
require.Equal(t, user2.ID, activity2.CreatorID)
// List all and verify creator IDs
activities, err := ts.ListActivities(ctx, &store.FindActivity{})
require.NoError(t, err)
require.Len(t, activities, 2)
ts.Close()
}
func TestActivityCreatedTs(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
require.NotZero(t, activity.CreatedTs)
// Verify timestamp is preserved when listing
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
require.NoError(t, err)
require.Equal(t, activity.CreatedTs, found.CreatedTs)
ts.Close()
}
func TestActivityListEmpty(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// List activities when none exist
activities, err := ts.ListActivities(ctx, &store.FindActivity{})
require.NoError(t, err)
require.Len(t, activities, 0)
ts.Close()
}
func TestActivityListWithIDAndType(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{},
})
require.NoError(t, err)
// List with both ID and Type filters
activityType := store.ActivityTypeMemoComment
activities, err := ts.ListActivities(ctx, &store.FindActivity{
ID: &activity.ID,
Type: &activityType,
})
require.NoError(t, err)
require.Len(t, activities, 1)
require.Equal(t, activity.ID, activities[0].ID)
ts.Close()
}
func TestActivityPayloadComplexMemoComment(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a memo first to use its ID
memo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "test-memo-for-activity",
CreatorID: user.ID,
Content: "Test memo content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create comment memo
commentMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "comment-memo",
CreatorID: user.ID,
Content: "This is a comment",
Visibility: store.Public,
})
require.NoError(t, err)
// Create activity with real memo IDs
activity, err := ts.CreateActivity(ctx, &store.Activity{
CreatorID: user.ID,
Type: store.ActivityTypeMemoComment,
Level: store.ActivityLevelInfo,
Payload: &storepb.ActivityPayload{
MemoComment: &storepb.ActivityMemoCommentPayload{
MemoId: memo.ID,
RelatedMemoId: commentMemo.ID,
},
},
})
require.NoError(t, err)
require.Equal(t, memo.ID, activity.Payload.MemoComment.MemoId)
require.Equal(t, commentMemo.ID, activity.Payload.MemoComment.RelatedMemoId)
// Verify payload is preserved
found, err := ts.GetActivity(ctx, &store.FindActivity{ID: &activity.ID})
require.NoError(t, err)
require.Equal(t, memo.ID, found.Payload.MemoComment.MemoId)
require.Equal(t, commentMemo.ID, found.Payload.MemoComment.RelatedMemoId)
ts.Close()
}

View File

@ -58,3 +58,384 @@ func TestIdentityProviderStore(t *testing.T) {
require.Equal(t, 0, len(idpList))
ts.Close()
}
func TestIdentityProviderGetByID(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create IDP
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Test IDP"))
require.NoError(t, err)
// Get by ID
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.NotNil(t, found)
require.Equal(t, idp.Id, found.Id)
require.Equal(t, idp.Name, found.Name)
// Get by non-existent ID
nonExistentID := int32(99999)
notFound, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &nonExistentID})
require.NoError(t, err)
require.Nil(t, notFound)
ts.Close()
}
func TestIdentityProviderListMultiple(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create multiple IDPs
_, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("GitHub OAuth"))
require.NoError(t, err)
_, err = ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Google OAuth"))
require.NoError(t, err)
_, err = ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("GitLab OAuth"))
require.NoError(t, err)
// List all
idpList, err := ts.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
require.NoError(t, err)
require.Len(t, idpList, 3)
ts.Close()
}
func TestIdentityProviderListByID(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create multiple IDPs
idp1, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("GitHub OAuth"))
require.NoError(t, err)
_, err = ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Google OAuth"))
require.NoError(t, err)
// List by specific ID
idpList, err := ts.ListIdentityProviders(ctx, &store.FindIdentityProvider{ID: &idp1.Id})
require.NoError(t, err)
require.Len(t, idpList, 1)
require.Equal(t, "GitHub OAuth", idpList[0].Name)
ts.Close()
}
func TestIdentityProviderUpdateName(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Original Name"))
require.NoError(t, err)
require.Equal(t, "Original Name", idp.Name)
// Update name
newName := "Updated Name"
updated, err := ts.UpdateIdentityProvider(ctx, &store.UpdateIdentityProviderV1{
ID: idp.Id,
Type: storepb.IdentityProvider_OAUTH2,
Name: &newName,
})
require.NoError(t, err)
require.Equal(t, "Updated Name", updated.Name)
// Verify update persisted
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Equal(t, "Updated Name", found.Name)
ts.Close()
}
func TestIdentityProviderUpdateIdentifierFilter(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Test IDP"))
require.NoError(t, err)
require.Equal(t, "", idp.IdentifierFilter)
// Update identifier filter
newFilter := "@example.com$"
updated, err := ts.UpdateIdentityProvider(ctx, &store.UpdateIdentityProviderV1{
ID: idp.Id,
Type: storepb.IdentityProvider_OAUTH2,
IdentifierFilter: &newFilter,
})
require.NoError(t, err)
require.Equal(t, "@example.com$", updated.IdentifierFilter)
// Verify update persisted
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Equal(t, "@example.com$", found.IdentifierFilter)
ts.Close()
}
func TestIdentityProviderUpdateConfig(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Test IDP"))
require.NoError(t, err)
// Update config
newConfig := &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "new_client_id",
ClientSecret: "new_client_secret",
AuthUrl: "https://newprovider.com/auth",
TokenUrl: "https://newprovider.com/token",
UserInfoUrl: "https://newprovider.com/user",
Scopes: []string{"openid", "profile", "email"},
FieldMapping: &storepb.FieldMapping{
Identifier: "sub",
DisplayName: "name",
Email: "email",
},
},
},
}
updated, err := ts.UpdateIdentityProvider(ctx, &store.UpdateIdentityProviderV1{
ID: idp.Id,
Type: storepb.IdentityProvider_OAUTH2,
Config: newConfig,
})
require.NoError(t, err)
require.Equal(t, "new_client_id", updated.Config.GetOauth2Config().ClientId)
require.Equal(t, "new_client_secret", updated.Config.GetOauth2Config().ClientSecret)
require.Contains(t, updated.Config.GetOauth2Config().Scopes, "openid")
// Verify update persisted
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Equal(t, "new_client_id", found.Config.GetOauth2Config().ClientId)
ts.Close()
}
func TestIdentityProviderUpdateMultipleFields(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Original"))
require.NoError(t, err)
// Update multiple fields at once
newName := "Updated IDP"
newFilter := "^admin@"
updated, err := ts.UpdateIdentityProvider(ctx, &store.UpdateIdentityProviderV1{
ID: idp.Id,
Type: storepb.IdentityProvider_OAUTH2,
Name: &newName,
IdentifierFilter: &newFilter,
})
require.NoError(t, err)
require.Equal(t, "Updated IDP", updated.Name)
require.Equal(t, "^admin@", updated.IdentifierFilter)
ts.Close()
}
func TestIdentityProviderDelete(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
idp, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("Test IDP"))
require.NoError(t, err)
// Delete
err = ts.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: idp.Id})
require.NoError(t, err)
// Verify deletion
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Nil(t, found)
ts.Close()
}
func TestIdentityProviderDeleteNotAffectOthers(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create multiple IDPs
idp1, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("IDP 1"))
require.NoError(t, err)
idp2, err := ts.CreateIdentityProvider(ctx, createTestOAuth2IDP("IDP 2"))
require.NoError(t, err)
// Delete first one
err = ts.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: idp1.Id})
require.NoError(t, err)
// Verify second still exists
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp2.Id})
require.NoError(t, err)
require.NotNil(t, found)
require.Equal(t, "IDP 2", found.Name)
// Verify list only contains second
idpList, err := ts.ListIdentityProviders(ctx, &store.FindIdentityProvider{})
require.NoError(t, err)
require.Len(t, idpList, 1)
ts.Close()
}
func TestIdentityProviderOAuth2ConfigScopes(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create IDP with multiple scopes
idp, err := ts.CreateIdentityProvider(ctx, &storepb.IdentityProvider{
Name: "Multi-Scope OAuth",
Type: storepb.IdentityProvider_OAUTH2,
Config: &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "client_id",
ClientSecret: "client_secret",
AuthUrl: "https://provider.com/auth",
TokenUrl: "https://provider.com/token",
UserInfoUrl: "https://provider.com/userinfo",
Scopes: []string{"openid", "profile", "email", "groups"},
FieldMapping: &storepb.FieldMapping{
Identifier: "sub",
DisplayName: "name",
Email: "email",
},
},
},
},
})
require.NoError(t, err)
// Verify scopes are preserved
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Len(t, found.Config.GetOauth2Config().Scopes, 4)
require.Contains(t, found.Config.GetOauth2Config().Scopes, "openid")
require.Contains(t, found.Config.GetOauth2Config().Scopes, "groups")
ts.Close()
}
func TestIdentityProviderFieldMapping(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
// Create IDP with custom field mapping
idp, err := ts.CreateIdentityProvider(ctx, &storepb.IdentityProvider{
Name: "Custom Field Mapping",
Type: storepb.IdentityProvider_OAUTH2,
Config: &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "client_id",
ClientSecret: "client_secret",
AuthUrl: "https://provider.com/auth",
TokenUrl: "https://provider.com/token",
UserInfoUrl: "https://provider.com/userinfo",
Scopes: []string{"login"},
FieldMapping: &storepb.FieldMapping{
Identifier: "preferred_username",
DisplayName: "full_name",
Email: "email_address",
},
},
},
},
})
require.NoError(t, err)
// Verify field mapping is preserved
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Equal(t, "preferred_username", found.Config.GetOauth2Config().FieldMapping.Identifier)
require.Equal(t, "full_name", found.Config.GetOauth2Config().FieldMapping.DisplayName)
require.Equal(t, "email_address", found.Config.GetOauth2Config().FieldMapping.Email)
ts.Close()
}
func TestIdentityProviderIdentifierFilterPatterns(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
testCases := []struct {
name string
filter string
}{
{"Domain filter", "@company\\.com$"},
{"Prefix filter", "^admin_"},
{"Complex regex", "^[a-z]+@(dept1|dept2)\\.example\\.com$"},
{"Empty filter", ""},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
idp, err := ts.CreateIdentityProvider(ctx, &storepb.IdentityProvider{
Name: tc.name,
Type: storepb.IdentityProvider_OAUTH2,
IdentifierFilter: tc.filter,
Config: &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "client_id",
ClientSecret: "client_secret",
AuthUrl: "https://provider.com/auth",
TokenUrl: "https://provider.com/token",
UserInfoUrl: "https://provider.com/userinfo",
Scopes: []string{"login"},
FieldMapping: &storepb.FieldMapping{
Identifier: "sub",
},
},
},
},
})
require.NoError(t, err)
found, err := ts.GetIdentityProvider(ctx, &store.FindIdentityProvider{ID: &idp.Id})
require.NoError(t, err)
require.Equal(t, tc.filter, found.IdentifierFilter)
// Cleanup
err = ts.DeleteIdentityProvider(ctx, &store.DeleteIdentityProvider{ID: idp.Id})
require.NoError(t, err)
})
}
ts.Close()
}
// Helper function to create a test OAuth2 IDP
func createTestOAuth2IDP(name string) *storepb.IdentityProvider {
return &storepb.IdentityProvider{
Name: name,
Type: storepb.IdentityProvider_OAUTH2,
IdentifierFilter: "",
Config: &storepb.IdentityProviderConfig{
Config: &storepb.IdentityProviderConfig_Oauth2Config{
Oauth2Config: &storepb.OAuth2Config{
ClientId: "client_id",
ClientSecret: "client_secret",
AuthUrl: "https://provider.com/auth",
TokenUrl: "https://provider.com/token",
UserInfoUrl: "https://provider.com/userinfo",
Scopes: []string{"login"},
FieldMapping: &storepb.FieldMapping{
Identifier: "login",
DisplayName: "name",
Email: "email",
},
},
},
},
}
}

View File

@ -52,3 +52,528 @@ func TestInboxStore(t *testing.T) {
require.Equal(t, 0, len(inboxes))
ts.Close()
}
func TestInboxListByID(t *testing.T) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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()
}

View File

@ -239,3 +239,432 @@ func TestMemoRelationDifferentTypes(t *testing.T) {
ts.Close()
}
func TestMemoRelationUpsertSameRelation(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
mainMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "main-memo",
CreatorID: user.ID,
Content: "main memo content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo",
CreatorID: user.ID,
Content: "related memo content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create relation
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Upsert the same relation again (should not create duplicate)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Verify only one relation exists
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &mainMemo.ID,
})
require.NoError(t, err)
require.Len(t, relations, 1)
ts.Close()
}
func TestMemoRelationDeleteByType(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
mainMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "main-memo",
CreatorID: user.ID,
Content: "main memo content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo1, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo-1",
CreatorID: user.ID,
Content: "related memo 1 content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo2, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo-2",
CreatorID: user.ID,
Content: "related memo 2 content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create reference relations
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo1.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Create comment relation
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo2.ID,
Type: store.MemoRelationComment,
})
require.NoError(t, err)
// Delete only reference type relations
refType := store.MemoRelationReference
err = ts.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
MemoID: &mainMemo.ID,
Type: &refType,
})
require.NoError(t, err)
// Verify only comment relation remains
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &mainMemo.ID,
})
require.NoError(t, err)
require.Len(t, relations, 1)
require.Equal(t, store.MemoRelationComment, relations[0].Type)
ts.Close()
}
func TestMemoRelationDeleteByMemoID(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memo1, err := ts.CreateMemo(ctx, &store.Memo{
UID: "memo-1",
CreatorID: user.ID,
Content: "memo 1 content",
Visibility: store.Public,
})
require.NoError(t, err)
memo2, err := ts.CreateMemo(ctx, &store.Memo{
UID: "memo-2",
CreatorID: user.ID,
Content: "memo 2 content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo",
CreatorID: user.ID,
Content: "related memo content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create relations for both memos
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memo1.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memo2.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Delete all relations for memo1
err = ts.DeleteMemoRelation(ctx, &store.DeleteMemoRelation{
MemoID: &memo1.ID,
})
require.NoError(t, err)
// Verify memo1's relations are gone
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo1.ID,
})
require.NoError(t, err)
require.Len(t, relations, 0)
// Verify memo2's relations still exist
relations, err = ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo2.ID,
})
require.NoError(t, err)
require.Len(t, relations, 1)
ts.Close()
}
func TestMemoRelationListByRelatedMemoID(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a memo that will be referenced by others
targetMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "target-memo",
CreatorID: user.ID,
Content: "target memo content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create memos that reference the target
referrer1, err := ts.CreateMemo(ctx, &store.Memo{
UID: "referrer-1",
CreatorID: user.ID,
Content: "referrer 1 content",
Visibility: store.Public,
})
require.NoError(t, err)
referrer2, err := ts.CreateMemo(ctx, &store.Memo{
UID: "referrer-2",
CreatorID: user.ID,
Content: "referrer 2 content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create relations pointing to target
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: referrer1.ID,
RelatedMemoID: targetMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: referrer2.ID,
RelatedMemoID: targetMemo.ID,
Type: store.MemoRelationComment,
})
require.NoError(t, err)
// List by related memo ID (find all memos that reference the target)
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
RelatedMemoID: &targetMemo.ID,
})
require.NoError(t, err)
require.Len(t, relations, 2)
ts.Close()
}
func TestMemoRelationListCombinedFilters(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
mainMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "main-memo",
CreatorID: user.ID,
Content: "main memo content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo1, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo-1",
CreatorID: user.ID,
Content: "related memo 1 content",
Visibility: store.Public,
})
require.NoError(t, err)
relatedMemo2, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo-2",
CreatorID: user.ID,
Content: "related memo 2 content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create multiple relations
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo1.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo2.ID,
Type: store.MemoRelationComment,
})
require.NoError(t, err)
// List with MemoID and Type filter
refType := store.MemoRelationReference
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &mainMemo.ID,
Type: &refType,
})
require.NoError(t, err)
require.Len(t, relations, 1)
require.Equal(t, relatedMemo1.ID, relations[0].RelatedMemoID)
// List with MemoID, RelatedMemoID, and Type filter
commentType := store.MemoRelationComment
relations, err = ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &mainMemo.ID,
RelatedMemoID: &relatedMemo2.ID,
Type: &commentType,
})
require.NoError(t, err)
require.Len(t, relations, 1)
ts.Close()
}
func TestMemoRelationListEmpty(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "memo-no-relations",
CreatorID: user.ID,
Content: "memo with no relations",
Visibility: store.Public,
})
require.NoError(t, err)
// List relations for memo with none
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memo.ID,
})
require.NoError(t, err)
require.Len(t, relations, 0)
ts.Close()
}
func TestMemoRelationBidirectional(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
memoA, err := ts.CreateMemo(ctx, &store.Memo{
UID: "memo-a",
CreatorID: user.ID,
Content: "memo A content",
Visibility: store.Public,
})
require.NoError(t, err)
memoB, err := ts.CreateMemo(ctx, &store.Memo{
UID: "memo-b",
CreatorID: user.ID,
Content: "memo B content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create relation A -> B
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memoA.ID,
RelatedMemoID: memoB.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Create relation B -> A (reverse direction)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: memoB.ID,
RelatedMemoID: memoA.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
// Verify A -> B exists
relationsFromA, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memoA.ID,
})
require.NoError(t, err)
require.Len(t, relationsFromA, 1)
require.Equal(t, memoB.ID, relationsFromA[0].RelatedMemoID)
// Verify B -> A exists
relationsFromB, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &memoB.ID,
})
require.NoError(t, err)
require.Len(t, relationsFromB, 1)
require.Equal(t, memoA.ID, relationsFromB[0].RelatedMemoID)
ts.Close()
}
func TestMemoRelationMultipleRelationsToSameMemo(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
mainMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "main-memo",
CreatorID: user.ID,
Content: "main memo content",
Visibility: store.Public,
})
require.NoError(t, err)
// Create multiple memos that all relate to the main memo
for i := 1; i <= 5; i++ {
relatedMemo, err := ts.CreateMemo(ctx, &store.Memo{
UID: "related-memo-" + string(rune('0'+i)),
CreatorID: user.ID,
Content: "related memo content",
Visibility: store.Public,
})
require.NoError(t, err)
_, err = ts.UpsertMemoRelation(ctx, &store.MemoRelation{
MemoID: mainMemo.ID,
RelatedMemoID: relatedMemo.ID,
Type: store.MemoRelationReference,
})
require.NoError(t, err)
}
// Verify all 5 relations exist
relations, err := ts.ListMemoRelations(ctx, &store.FindMemoRelation{
MemoID: &mainMemo.ID,
})
require.NoError(t, err)
require.Len(t, relations, 5)
ts.Close()
}

View File

@ -5,6 +5,7 @@ import (
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
@ -308,3 +309,332 @@ func TestUserSettingShortcuts(t *testing.T) {
ts.Close()
}
func TestUserSettingGetUserByPATHash(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a PAT with a known hash
patHash := "test-pat-hash-12345"
pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-test-1",
TokenHash: patHash,
Description: "Test PAT for lookup",
}
err = ts.AddUserPersonalAccessToken(ctx, user.ID, pat)
require.NoError(t, err)
// Lookup user by PAT hash
result, err := ts.GetUserByPATHash(ctx, patHash)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, user.ID, result.UserID)
require.NotNil(t, result.User)
require.Equal(t, user.Username, result.User.Username)
require.NotNil(t, result.PAT)
require.Equal(t, "pat-test-1", result.PAT.TokenId)
require.Equal(t, "Test PAT for lookup", result.PAT.Description)
ts.Close()
}
func TestUserSettingGetUserByPATHashNotFound(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
_, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Lookup non-existent PAT hash
result, err := ts.GetUserByPATHash(ctx, "non-existent-hash")
require.Error(t, err)
require.Nil(t, result)
ts.Close()
}
func TestUserSettingGetUserByPATHashMultipleUsers(t *testing.T) {
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 PATs for both users
pat1Hash := "user1-pat-hash"
err = ts.AddUserPersonalAccessToken(ctx, user1.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-user1",
TokenHash: pat1Hash,
Description: "User 1 PAT",
})
require.NoError(t, err)
pat2Hash := "user2-pat-hash"
err = ts.AddUserPersonalAccessToken(ctx, user2.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-user2",
TokenHash: pat2Hash,
Description: "User 2 PAT",
})
require.NoError(t, err)
// Lookup user1's PAT
result1, err := ts.GetUserByPATHash(ctx, pat1Hash)
require.NoError(t, err)
require.Equal(t, user1.ID, result1.UserID)
require.Equal(t, user1.Username, result1.User.Username)
// Lookup user2's PAT
result2, err := ts.GetUserByPATHash(ctx, pat2Hash)
require.NoError(t, err)
require.Equal(t, user2.ID, result2.UserID)
require.Equal(t, user2.Username, result2.User.Username)
ts.Close()
}
func TestUserSettingGetUserByPATHashMultiplePATsSameUser(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create multiple PATs for the same user
pat1Hash := "first-pat-hash"
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-1",
TokenHash: pat1Hash,
Description: "First PAT",
})
require.NoError(t, err)
pat2Hash := "second-pat-hash"
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-2",
TokenHash: pat2Hash,
Description: "Second PAT",
})
require.NoError(t, err)
// Both PATs should resolve to the same user
result1, err := ts.GetUserByPATHash(ctx, pat1Hash)
require.NoError(t, err)
require.Equal(t, user.ID, result1.UserID)
require.Equal(t, "pat-1", result1.PAT.TokenId)
result2, err := ts.GetUserByPATHash(ctx, pat2Hash)
require.NoError(t, err)
require.Equal(t, user.ID, result2.UserID)
require.Equal(t, "pat-2", result2.PAT.TokenId)
ts.Close()
}
func TestUserSettingUpdatePATLastUsed(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a PAT
patHash := "pat-hash-for-update"
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-update-test",
TokenHash: patHash,
Description: "PAT for update test",
})
require.NoError(t, err)
// Update last used timestamp
now := timestamppb.Now()
err = ts.UpdatePATLastUsed(ctx, user.ID, "pat-update-test", now)
require.NoError(t, err)
// Verify the update
pats, err := ts.GetUserPersonalAccessTokens(ctx, user.ID)
require.NoError(t, err)
require.Len(t, pats, 1)
require.NotNil(t, pats[0].LastUsedAt)
ts.Close()
}
func TestUserSettingGetUserByPATHashWithExpiredToken(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a PAT with expiration info
patHash := "pat-hash-with-expiry"
expiresAt := timestamppb.Now()
pat := &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-expiry-test",
TokenHash: patHash,
Description: "PAT with expiry",
ExpiresAt: expiresAt,
}
err = ts.AddUserPersonalAccessToken(ctx, user.ID, pat)
require.NoError(t, err)
// Should still be able to look up by hash (expiry check is done at auth level)
result, err := ts.GetUserByPATHash(ctx, patHash)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, user.ID, result.UserID)
require.NotNil(t, result.PAT.ExpiresAt)
ts.Close()
}
func TestUserSettingGetUserByPATHashAfterRemoval(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create a PAT
patHash := "pat-hash-to-remove"
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-remove-test",
TokenHash: patHash,
Description: "PAT to be removed",
})
require.NoError(t, err)
// Verify it exists
result, err := ts.GetUserByPATHash(ctx, patHash)
require.NoError(t, err)
require.NotNil(t, result)
// Remove the PAT
err = ts.RemoveUserPersonalAccessToken(ctx, user.ID, "pat-remove-test")
require.NoError(t, err)
// Should no longer be found
result, err = ts.GetUserByPATHash(ctx, patHash)
require.Error(t, err)
require.Nil(t, result)
ts.Close()
}
func TestUserSettingGetUserByPATHashSpecialCharacters(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create PATs with special characters in hash (simulating real hash values)
testCases := []struct {
tokenID string
tokenHash string
}{
{"pat-special-1", "abc123+/=XYZ"},
{"pat-special-2", "sha256:abcdef1234567890"},
{"pat-special-3", "$2a$10$N9qo8uLOickgx2ZMRZoMy"},
}
for _, tc := range testCases {
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: tc.tokenID,
TokenHash: tc.tokenHash,
Description: "PAT with special chars",
})
require.NoError(t, err)
// Verify lookup works with special characters
result, err := ts.GetUserByPATHash(ctx, tc.tokenHash)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, tc.tokenID, result.PAT.TokenId)
}
ts.Close()
}
func TestUserSettingGetUserByPATHashLargeTokenCount(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create many PATs for the same user
tokenCount := 10
hashes := make([]string, tokenCount)
for i := 0; i < tokenCount; i++ {
hashes[i] = "pat-hash-" + string(rune('A'+i)) + "-large-test"
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-large-" + string(rune('A'+i)),
TokenHash: hashes[i],
Description: "PAT for large count test",
})
require.NoError(t, err)
}
// Verify each hash can be looked up
for i, hash := range hashes {
result, err := ts.GetUserByPATHash(ctx, hash)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, user.ID, result.UserID)
require.Equal(t, "pat-large-"+string(rune('A'+i)), result.PAT.TokenId)
}
ts.Close()
}
func TestUserSettingMultipleSettingTypes(t *testing.T) {
ctx := context.Background()
ts := NewTestingStore(ctx, t)
user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err)
// Create GENERAL setting
_, err = ts.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSetting_GENERAL,
Value: &storepb.UserSetting_General{General: &storepb.GeneralUserSetting{Locale: "ja"}},
})
require.NoError(t, err)
// Create SHORTCUTS setting
_, err = ts.UpsertUserSetting(ctx, &storepb.UserSetting{
UserId: user.ID,
Key: storepb.UserSetting_SHORTCUTS,
Value: &storepb.UserSetting_Shortcuts{Shortcuts: &storepb.ShortcutsUserSetting{
Shortcuts: []*storepb.ShortcutsUserSetting_Shortcut{
{Id: "s1", Title: "Shortcut 1"},
},
}},
})
require.NoError(t, err)
// Add a PAT
err = ts.AddUserPersonalAccessToken(ctx, user.ID, &storepb.PersonalAccessTokensUserSetting_PersonalAccessToken{
TokenId: "pat-multi",
TokenHash: "hash-multi",
})
require.NoError(t, err)
// List all settings for user
settings, err := ts.ListUserSettings(ctx, &store.FindUserSetting{UserID: &user.ID})
require.NoError(t, err)
require.Len(t, settings, 3)
// Verify each setting type
generalSetting, err := ts.GetUserSetting(ctx, &store.FindUserSetting{UserID: &user.ID, Key: storepb.UserSetting_GENERAL})
require.NoError(t, err)
require.Equal(t, "ja", generalSetting.GetGeneral().Locale)
shortcutsSetting, err := ts.GetUserSetting(ctx, &store.FindUserSetting{UserID: &user.ID, Key: storepb.UserSetting_SHORTCUTS})
require.NoError(t, err)
require.Len(t, shortcutsSetting.GetShortcuts().Shortcuts, 1)
patsSetting, err := ts.GetUserSetting(ctx, &store.FindUserSetting{UserID: &user.ID, Key: storepb.UserSetting_PERSONAL_ACCESS_TOKENS})
require.NoError(t, err)
require.Len(t, patsSetting.GetPersonalAccessTokens().Tokens, 1)
ts.Close()
}