mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-11 12:19:04 +02:00
207 lines
7.2 KiB
Go
207 lines
7.2 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"uuid"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/pocket-id/pocket-id/backend/internal/appconfig"
|
|
"github.com/pocket-id/pocket-id/backend/internal/apperror"
|
|
"github.com/pocket-id/pocket-id/backend/internal/dto"
|
|
"github.com/pocket-id/pocket-id/backend/internal/model"
|
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
|
"github.com/pocket-id/pocket-id/backend/internal/storage"
|
|
testutils "github.com/pocket-id/pocket-id/backend/internal/utils/testing"
|
|
)
|
|
|
|
func newTestUserService(t *testing.T) (*UserService, *UserGroupService) {
|
|
t.Helper()
|
|
|
|
db := testutils.NewDatabaseForTest(t)
|
|
|
|
fileStorage, err := storage.NewDatabaseStorage(db)
|
|
require.NoError(t, err)
|
|
|
|
userService := NewUserService(
|
|
db,
|
|
nil,
|
|
NewCustomClaimService(db),
|
|
NewAppImagesService(map[string]string{}, fileStorage),
|
|
nil,
|
|
nil,
|
|
fileStorage,
|
|
)
|
|
groupService := NewUserGroupService(db, nil, nil)
|
|
|
|
return userService, groupService
|
|
}
|
|
|
|
func TestUserAndGroupLookupsReturnSpecificNotFoundErrors(t *testing.T) {
|
|
userService, groupService := newTestUserService(t)
|
|
|
|
_, err := userService.GetUser(t.Context(), "missing-user")
|
|
require.True(t, apperror.IsCode(err, apperror.CodeUserNotFound))
|
|
|
|
_, err = groupService.Get(t.Context(), "missing-group")
|
|
require.True(t, apperror.IsCode(err, apperror.CodeNotFound))
|
|
var appErr *apperror.Error
|
|
require.ErrorAs(t, err, &appErr)
|
|
require.Equal(t, "User group", appErr.Details()["resource"])
|
|
}
|
|
|
|
func TestUpdateProfilePictureRejectsInvalidImageData(t *testing.T) {
|
|
userService, _ := newTestUserService(t)
|
|
config := &appconfig.AppConfigModel{RequireUserEmail: "false"}
|
|
user, err := userService.CreateUser(t.Context(), config, dto.UserCreateDto{
|
|
ID: uuid.NewV4().String(),
|
|
Username: "image-test",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
err = userService.UpdateProfilePicture(
|
|
t.Context(),
|
|
user.ID,
|
|
strings.NewReader("not an image"),
|
|
)
|
|
|
|
require.True(t, apperror.IsCode(err, apperror.CodeInvalidImage))
|
|
}
|
|
|
|
func TestProfilePictureUpdatesRejectMissingUser(t *testing.T) {
|
|
userService, _ := newTestUserService(t)
|
|
missingUserID := uuid.NewV4().String()
|
|
|
|
err := userService.UpdateProfilePicture(t.Context(), missingUserID, strings.NewReader("not an image"))
|
|
require.True(t, apperror.IsCode(err, apperror.CodeUserNotFound))
|
|
|
|
err = userService.ResetProfilePicture(t.Context(), missingUserID)
|
|
require.True(t, apperror.IsCode(err, apperror.CodeUserNotFound))
|
|
}
|
|
|
|
func TestCreateUserBumpsGroupUpdatedAt(t *testing.T) {
|
|
config := &appconfig.AppConfigModel{RequireUserEmail: "false"}
|
|
userService, groupService := newTestUserService(t)
|
|
|
|
group, err := groupService.Create(t.Context(), dto.UserGroupCreateDto{
|
|
Name: "members",
|
|
FriendlyName: "Members",
|
|
})
|
|
require.NoError(t, err)
|
|
require.Nil(t, group.UpdatedAt, "a freshly created group has no UpdatedAt yet")
|
|
|
|
// Create a user that is a member of the group
|
|
// This mirrors signing up via an invite link that adds the user to a group
|
|
email := "member@example.com"
|
|
_, err = userService.CreateUser(t.Context(), config, dto.UserCreateDto{
|
|
Username: "member",
|
|
Email: &email,
|
|
FirstName: "Group",
|
|
LastName: "Member",
|
|
UserGroupIds: []string{group.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// The group's UpdatedAt must now be set
|
|
updated, err := groupService.Get(t.Context(), group.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, updated.UpdatedAt, "creating a group member must bump the group's UpdatedAt")
|
|
require.False(t, updated.LastModified().Before(updated.CreatedAt.ToTime()), "group LastModified must not predate its CreatedAt after a membership change")
|
|
require.Len(t, updated.Users, 1, "the user should be a member of the group")
|
|
}
|
|
|
|
func TestCreateUserBumpsDefaultGroupUpdatedAt(t *testing.T) {
|
|
config := &appconfig.AppConfigModel{RequireUserEmail: "false"}
|
|
userService, groupService := newTestUserService(t)
|
|
|
|
group, err := groupService.Create(t.Context(), dto.UserGroupCreateDto{
|
|
Name: "default",
|
|
FriendlyName: "Default",
|
|
})
|
|
require.NoError(t, err)
|
|
require.Nil(t, group.UpdatedAt)
|
|
|
|
// Configure the group as a default signup group
|
|
defaultGroups, err := json.Marshal([]string{group.ID})
|
|
require.NoError(t, err)
|
|
config.SignupDefaultUserGroupIDs = appconfig.AppConfigValue(defaultGroups)
|
|
|
|
// Create a user without explicit group IDs, so the default groups apply
|
|
email := "default@example.com"
|
|
_, err = userService.CreateUser(t.Context(), config, dto.UserCreateDto{
|
|
Username: "defaultmember",
|
|
Email: &email,
|
|
FirstName: "Default",
|
|
LastName: "Member",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
updated, err := groupService.Get(t.Context(), group.ID)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, updated.UpdatedAt, "adding a default group member must bump the group's UpdatedAt")
|
|
require.Len(t, updated.Users, 1)
|
|
}
|
|
|
|
func TestUpdateUserGroupsBumpsRemovedGroupUpdatedAt(t *testing.T) {
|
|
config := &appconfig.AppConfigModel{RequireUserEmail: "false"}
|
|
userService, groupService := newTestUserService(t)
|
|
|
|
oldGroup, err := groupService.Create(t.Context(), dto.UserGroupCreateDto{Name: "old", FriendlyName: "Old"})
|
|
require.NoError(t, err)
|
|
newGroup, err := groupService.Create(t.Context(), dto.UserGroupCreateDto{Name: "new", FriendlyName: "New"})
|
|
require.NoError(t, err)
|
|
|
|
user, err := userService.CreateUser(t.Context(), config, dto.UserCreateDto{
|
|
Username: "mover",
|
|
FirstName: "Group",
|
|
LastName: "Mover",
|
|
UserGroupIds: []string{oldGroup.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Backdate both groups so a bump is visible regardless of the timestamp precision
|
|
past := time.Now().Add(-time.Hour)
|
|
require.NoError(t, userService.db.Model(&model.UserGroup{}).Where("id IN ?", []string{oldGroup.ID, newGroup.ID}).Update("updated_at", datatype.DateTime(past)).Error)
|
|
|
|
// Move the user from the old group to the new one, as the user's group selection does
|
|
_, err = userService.UpdateUserGroups(t.Context(), user.ID, []string{newGroup.ID})
|
|
require.NoError(t, err)
|
|
|
|
for _, groupID := range []string{oldGroup.ID, newGroup.ID} {
|
|
updated, err := groupService.Get(t.Context(), groupID)
|
|
require.NoError(t, err)
|
|
require.True(t, updated.LastModified().After(past), "group %s changed membership, so its UpdatedAt must be bumped", updated.Name)
|
|
}
|
|
}
|
|
|
|
func TestDeleteUserBumpsGroupUpdatedAt(t *testing.T) {
|
|
config := &appconfig.AppConfigModel{RequireUserEmail: "false"}
|
|
userService, groupService := newTestUserService(t)
|
|
|
|
group, err := groupService.Create(t.Context(), dto.UserGroupCreateDto{Name: "members", FriendlyName: "Members"})
|
|
require.NoError(t, err)
|
|
|
|
user, err := userService.CreateUser(t.Context(), config, dto.UserCreateDto{
|
|
Username: "leaver",
|
|
FirstName: "Group",
|
|
LastName: "Leaver",
|
|
UserGroupIds: []string{group.ID},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Backdate the group so a bump is visible regardless of the timestamp precision
|
|
past := time.Now().Add(-time.Hour)
|
|
require.NoError(t, userService.db.Model(&model.UserGroup{}).Where("id = ?", group.ID).Update("updated_at", datatype.DateTime(past)).Error)
|
|
|
|
err = userService.DeleteUser(t.Context(), config, user.ID, false)
|
|
require.NoError(t, err)
|
|
|
|
updated, err := groupService.Get(t.Context(), group.ID)
|
|
require.NoError(t, err)
|
|
require.Empty(t, updated.Users)
|
|
require.True(t, updated.LastModified().After(past), "deleting a member must bump the group's UpdatedAt")
|
|
}
|