Prefix replacement of the old application-nested module path in go.mod and every import. No identifier, string, table, migration ID, config key or comment changes.
184 lines
5.5 KiB
Go
184 lines
5.5 KiB
Go
package user
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"image"
|
|
"image/jpeg"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-user-plugin/controllers"
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
|
"gocloud.dev/blob"
|
|
"gocloud.dev/blob/memblob"
|
|
)
|
|
|
|
func TestUploadAvatar(t *testing.T) {
|
|
app, gdb := sessionApp(t, "0s")
|
|
publishAvatarBucket(t, app)
|
|
token := loginToken(t, app, gdb, "avatar-upload@example.test")
|
|
|
|
ok := postAvatar(t, controllers.UploadAvatar(app), token, "avatar", "face.jpg", jpegBytes(t))
|
|
if ok.Code != http.StatusOK {
|
|
t.Fatalf("upload %d %s", ok.Code, ok.Body.String())
|
|
}
|
|
var body map[string]any
|
|
if err := json.Unmarshal(ok.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body["message"] != "Avatar updated" {
|
|
t.Fatalf("message = %v", body["message"])
|
|
}
|
|
user, _ := body["user"].(map[string]any)
|
|
if user["has_avatar"] != true {
|
|
t.Fatalf("has_avatar = %v", user["has_avatar"])
|
|
}
|
|
url, _ := user["avatar_url"].(string)
|
|
if url == "" {
|
|
t.Fatalf("avatar_url = %#v", user["avatar_url"])
|
|
}
|
|
|
|
svg := postAvatar(t, controllers.UploadAvatar(app), token, "avatar", "pic.svg", []byte(`<svg xmlns="http://www.w3.org/2000/svg"></svg>`))
|
|
assertAvatarError(t, svg, "The avatar must be a file of type: jpeg, jpg, png, webp, gif.")
|
|
|
|
big := make([]byte, 4_096_001)
|
|
copy(big, []byte{0xff, 0xd8, 0xff, 0xe0})
|
|
over := postAvatar(t, controllers.UploadAvatar(app), token, "avatar", "big.jpg", big)
|
|
assertAvatarError(t, over, "The avatar must not be greater than 4000 kilobytes.")
|
|
|
|
missing := postAvatar(t, controllers.UploadAvatar(app), token, "", "", nil)
|
|
assertAvatarError(t, missing, "The avatar field is required.")
|
|
|
|
var n int64
|
|
if err := gdb.Model(&attach.File{}).Where("field = ?", "avatar").Count(&n).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 1 {
|
|
t.Fatalf("avatar rows = %d, want 1 after rejected replacements", n)
|
|
}
|
|
}
|
|
|
|
func TestRemoveAvatar(t *testing.T) {
|
|
app, gdb := sessionApp(t, "0s")
|
|
bucket := publishAvatarBucket(t, app)
|
|
token := loginToken(t, app, gdb, "avatar-remove@example.test")
|
|
up := postAvatar(t, controllers.UploadAvatar(app), token, "avatar", "face.jpg", jpegBytes(t))
|
|
if up.Code != http.StatusOK {
|
|
t.Fatalf("upload %d %s", up.Code, up.Body.String())
|
|
}
|
|
var row attach.File
|
|
if err := gdb.Where("field = ?", "avatar").Last(&row).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key := attach.BlobKey(row.DiskName)
|
|
exists, err := bucket.Exists(context.Background(), key)
|
|
if err != nil || !exists {
|
|
t.Fatalf("original blob exists=%v err=%v", exists, err)
|
|
}
|
|
|
|
gone := bearer(t, controllers.RemoveAvatar(app), http.MethodPost, "/_user/api/v1/avatar/remove", token)
|
|
if gone.Code != http.StatusOK {
|
|
t.Fatalf("remove %d %s", gone.Code, gone.Body.String())
|
|
}
|
|
var body map[string]any
|
|
if err := json.Unmarshal(gone.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body["message"] != "Avatar removed" {
|
|
t.Fatalf("message = %v", body["message"])
|
|
}
|
|
user, _ := body["user"].(map[string]any)
|
|
if user["has_avatar"] != false {
|
|
t.Fatalf("has_avatar = %v", user["has_avatar"])
|
|
}
|
|
var n int64
|
|
if err := gdb.Model(&attach.File{}).Where("disk_name = ?", row.DiskName).Count(&n).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if n != 0 {
|
|
t.Fatalf("system_files rows left = %d", n)
|
|
}
|
|
exists, err = bucket.Exists(context.Background(), key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if exists {
|
|
t.Fatal("original blob still present")
|
|
}
|
|
|
|
again := bearer(t, controllers.RemoveAvatar(app), http.MethodPost, "/_user/api/v1/avatar/remove", token)
|
|
const want = `{"error":"Your account has no display picture to remove.","errors":{"avatar":["Your account has no display picture to remove."]}}`
|
|
if again.Code != http.StatusUnprocessableEntity || again.Body.String() != want {
|
|
t.Fatalf("missing avatar %d %s", again.Code, again.Body.String())
|
|
}
|
|
}
|
|
|
|
func publishAvatarBucket(t *testing.T, app *backpack.App) *blob.Bucket {
|
|
t.Helper()
|
|
bucket := memblob.OpenBucket(nil)
|
|
t.Cleanup(func() { _ = bucket.Close() })
|
|
if err := attach.Publish(app, bucket); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return bucket
|
|
}
|
|
|
|
func jpegBytes(t *testing.T) []byte {
|
|
t.Helper()
|
|
img := image.NewRGBA(image.Rect(0, 0, 8, 8))
|
|
var buf bytes.Buffer
|
|
if err := jpeg.Encode(&buf, img, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func postAvatar(t *testing.T, h http.HandlerFunc, token, field, filename string, body []byte) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
w := multipart.NewWriter(&buf)
|
|
if field != "" {
|
|
part, err := w.CreateFormFile(field, filename)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := part.Write(body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := w.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req := httptest.NewRequest(http.MethodPost, "/_user/api/v1/avatar", &buf)
|
|
req.Host = "example.com"
|
|
req.Header.Set("Content-Type", w.FormDataContentType())
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "Bearer "+token)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
h(rec, req)
|
|
return rec
|
|
}
|
|
|
|
func assertAvatarError(t *testing.T, rec *httptest.ResponseRecorder, message string) {
|
|
t.Helper()
|
|
if rec.Code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
|
|
}
|
|
var body struct {
|
|
Error string `json:"error"`
|
|
Errors map[string][]string `json:"errors"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if body.Error != message || len(body.Errors["avatar"]) != 1 || body.Errors["avatar"][0] != message {
|
|
t.Fatalf("body = %s", rec.Body.String())
|
|
}
|
|
}
|