test: RequestUser, EndRequestSession, AvatarURL and user.Deactivating
- 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
This commit is contained in:
136
avatar_url_test.go
Normal file
136
avatar_url_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
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"])
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user