- TestRequestUser: bearer, token and auth_token cookies in guard order; expired, foreign, blacklisted, revoked by tokens_valid_after and deactivated tokens are anonymous; only infrastructure failures error - TestEndRequestSession (no database): jti blacklisted until the token expires, forget cookie with the configured Domain and Path, best effort - TestAvatarURL: thumbnail, broken-image and storage-failure fallbacks, gravatar with the size, the user payload's avatar_url unchanged - TestDeactivatingEvent: one event per newly deactivated user inside the bulk action's transaction, a listener error rolls the action back
137 lines
5.5 KiB
Go
137 lines
5.5 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"crypto/md5"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-user-plugin/classes"
|
|
"git.golem15.com/golem15/sm-user-plugin/controllers"
|
|
"git.golem15.com/golem15/sm-user-plugin/models"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
|
"gocloud.dev/blob/memblob"
|
|
)
|
|
|
|
// jpegAvatar stores a real JPEG avatar for user (a system_files row and its
|
|
// blob) and returns the row.
|
|
func (e *adminEnv) jpegAvatar(t *testing.T, user *models.User) attach.File {
|
|
t.Helper()
|
|
disk := "face" + adminStamp() + ".jpg"
|
|
data := jpegBytes(t)
|
|
if err := e.bucket.WriteAll(context.Background(), attach.BlobKey(disk), data, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
row := attach.File{
|
|
DiskName: disk, FileName: "face.jpg", FileSize: int64(len(data)), ContentType: "image/jpeg",
|
|
Field: models.AvatarField, AttachmentID: strconv.FormatUint(uint64(user.ID), 10), AttachmentType: user.MorphName(),
|
|
}
|
|
if err := e.gdb.Create(&row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return row
|
|
}
|
|
|
|
func gravatarFor(email string, size int) string {
|
|
return fmt.Sprintf("//www.gravatar.com/avatar/%x?s=%d&d=mm", md5.Sum([]byte(strings.ToLower(strings.TrimSpace(email)))), size)
|
|
}
|
|
|
|
// TestAvatarURL pins classes.AvatarURL (D-05): the avatar thumbnail at the
|
|
// asked size, the file path when the thumbnail fails, the gravatar
|
|
// fallback with the size, errors only for a failing lookup, and the user
|
|
// API payload (applyAvatar) unchanged: its avatar_url is AvatarURL at 128.
|
|
func TestAvatarURL(t *testing.T) {
|
|
env := newAdminEnv(t)
|
|
ctx := t.Context()
|
|
|
|
if _, err := classes.AvatarURL(ctx, env.app, nil, 128); err == nil {
|
|
t.Fatal("a nil user must be refused")
|
|
}
|
|
|
|
// Gravatar: no app, an unsaved user, no attachment; the email is
|
|
// trimmed and lower-cased, the size is the one asked for.
|
|
plain := env.seedUser(t, "plain", func(u *models.User) { u.Email = " Mixed.Case-" + adminStamp() + "@Example.TEST " })
|
|
for _, size := range []int{32, 128, 256} {
|
|
for name, call := range map[string]func() (string, error){
|
|
"no app": func() (string, error) { return classes.AvatarURL(ctx, nil, plain, size) },
|
|
"unsaved user": func() (string, error) { return classes.AvatarURL(ctx, env.app, &models.User{Email: plain.Email}, size) },
|
|
"no attachment": func() (string, error) { return classes.AvatarURL(ctx, env.app, plain, size) },
|
|
} {
|
|
got, err := call()
|
|
if err != nil || got != gravatarFor(plain.Email, size) {
|
|
t.Fatalf("%s at %d = %q %v, want %q", name, size, got, err, gravatarFor(plain.Email, size))
|
|
}
|
|
}
|
|
}
|
|
if gravatarFor(plain.Email, 1) != gravatarFor(strings.ToLower(strings.TrimSpace(plain.Email)), 1) {
|
|
t.Fatal("the gravatar hash is not of the normalised email")
|
|
}
|
|
|
|
// A file avatar: the thumbnail at size by size, mode auto.
|
|
withFile := env.seedUser(t, "withfile", nil)
|
|
row := env.jpegAvatar(t, withFile)
|
|
thumb, err := classes.AvatarURL(ctx, env.app, withFile, 64)
|
|
want := attach.ThumbFilename(row.ID, 64, 64, 0, 0, "auto", "jpg")
|
|
if err != nil || !strings.HasSuffix(thumb, want) || strings.HasPrefix(thumb, "//") {
|
|
t.Fatalf("thumbnail = %q %v, want a path ending in %s", thumb, err, want)
|
|
}
|
|
// Without a published bucket the gravatar is used even with a file.
|
|
noBucket := sessionUserApp(t, "SUMMER_GOLEM15__USER__JWT__SECRET="+sessionSecret)
|
|
if err := noBucket.Publish(env.gdb); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := classes.AvatarURL(ctx, noBucket, withFile, 64); err != nil || got != gravatarFor(withFile.Email, 64) {
|
|
t.Fatalf("no bucket = %q %v", got, err)
|
|
}
|
|
|
|
// An original that does not decode gets WinterCMS's broken-image
|
|
// thumbnail, still a thumbnail path.
|
|
notImage := env.seedUser(t, "notimage", nil)
|
|
env.seedAvatar(t, notImage) // its blob is not an image
|
|
if got, err := classes.AvatarURL(ctx, env.app, notImage, 128); err != nil || !strings.Contains(got, "thumb_") {
|
|
t.Fatalf("non-image avatar = %q %v", got, err)
|
|
}
|
|
// A thumbnail that cannot be made (here: the storage fails) falls back
|
|
// to the file path.
|
|
closed := memblob.OpenBucket(nil)
|
|
_ = closed.Close()
|
|
failing := sessionUserApp(t, "SUMMER_GOLEM15__USER__JWT__SECRET="+sessionSecret)
|
|
if err := failing.Publish(env.gdb); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := attach.Publish(failing, closed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, err := classes.AvatarURL(ctx, failing, withFile, 128); err != nil || got != "/"+attach.BlobKey(row.DiskName) {
|
|
t.Fatalf("failing thumbnail = %q %v, want /%s", got, err, attach.BlobKey(row.DiskName))
|
|
}
|
|
|
|
// A failing lookup is the only error.
|
|
canceled, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
if got, err := classes.AvatarURL(canceled, env.app, withFile, 128); err == nil {
|
|
t.Fatalf("failing lookup = %q, want an error", got)
|
|
}
|
|
|
|
// The user API payload is unchanged: avatar_url is AvatarURL at 128,
|
|
// next to has_avatar and the disk name.
|
|
payload, err := controllers.APIArray(ctx, env.app, withFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
at128, _ := classes.AvatarURL(ctx, env.app, withFile, 128)
|
|
if payload["has_avatar"] != true || payload["avatar"] != row.DiskName || payload["avatar_url"] != at128 {
|
|
t.Fatalf("payload avatar fields = %v %v %v, want true %s %s", payload["has_avatar"], payload["avatar"], payload["avatar_url"], row.DiskName, at128)
|
|
}
|
|
without, err := controllers.APIArray(ctx, env.app, plain)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Without one the payload keeps its gravatar default (size 25).
|
|
if without["has_avatar"] != false || without["avatar"] != gravatarFor(plain.Email, 25) {
|
|
t.Fatalf("payload without an avatar = %v %v", without["has_avatar"], without["avatar"])
|
|
}
|
|
}
|