mirror of https://github.com/usememos/memos.git
chore: add more tests
This commit is contained in:
parent
8f9ff5634c
commit
31f634b71a
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue