Files
sm-user-plugin/register_test.go
Jakub Zych af976e789f refactor(10.2-01): use nested framework modules
- Rewrite application and plugin framework imports
2026-09-28 02:21:38 +02:00

225 lines
7.4 KiB
Go

package user
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"context"
"git.golem15.com/golem15/fonoteka/plugins/golem15/user/classes"
"git.golem15.com/golem15/fonoteka/plugins/golem15/user/controllers"
"git.golem15.com/golem15/fonoteka/plugins/golem15/user/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/postcard"
)
func TestRegister(t *testing.T) {
app, _ := sessionApp(t, "0s")
disableRegisterThrottle(t, app)
body := `{"email":"new-ada@example.test","password":"correct-horse","name":"Ada"}`
rec := postJSON(t, controllers.Register(app), "/_user/api/v1/register", body)
if rec.Code != http.StatusOK {
t.Fatalf("register %d %s", rec.Code, rec.Body.String())
}
var created map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &created); err != nil {
t.Fatal(err)
}
token, _ := created["token"].(string)
if token == "" {
t.Fatalf("body %s", rec.Body.String())
}
userBody, _ := created["user"].(map[string]any)
if userBody["feedback_widget_hidden"] != false {
t.Fatalf("feedback_widget_hidden = %#v", userBody["feedback_widget_hidden"])
}
fetch := bearer(t, controllers.Fetch(app), http.MethodGet, "/_user/api/v1/fetch", token)
if fetch.Code != http.StatusOK || !strings.Contains(fetch.Body.String(), `"email":"new-ada@example.test"`) {
t.Fatalf("fetch %d %s", fetch.Code, fetch.Body.String())
}
missing := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"password":"correct-horse"}`)
if missing.Code != http.StatusUnprocessableEntity {
t.Fatalf("missing email %d %s", missing.Code, missing.Body.String())
}
var verr map[string]any
if err := json.Unmarshal(missing.Body.Bytes(), &verr); err != nil {
t.Fatal(err)
}
errorsBody, _ := verr["errors"].(map[string]any)
if _, ok := errorsBody["email"]; !ok || verr["error"] == "" {
t.Fatalf("validation body %#v", verr)
}
}
func TestRegisterDisabled(t *testing.T) {
app, _ := sessionApp(t, "0s")
if err := app.Config.Set("golem15.user.registration.allow_registration", false); err != nil {
t.Fatal(err)
}
if err := app.Config.Set("app.debug", false); err != nil {
t.Fatal(err)
}
rec := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"email":"off@example.test","password":"correct-horse"}`)
if rec.Code != http.StatusInternalServerError || rec.Body.String() != `{"error":"Internal server error"}` {
t.Fatalf("production %d %s", rec.Code, rec.Body.String())
}
if err := app.Config.Set("app.debug", true); err != nil {
t.Fatal(err)
}
debug := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"email":"off@example.test","password":"correct-horse"}`)
if debug.Code != http.StatusInternalServerError || debug.Body.String() != `{"error":"Registrations are currently disabled."}` {
t.Fatalf("debug %d %s", debug.Code, debug.Body.String())
}
}
func TestRegisterThrottle(t *testing.T) {
app, gdb := sessionApp(t, "0s")
ip := "203.0.113.9"
for i := 1; i <= 3; i++ {
addr := ip
user := &models.User{Email: "throttle-reg-" + string(rune('a'+i-1)) + "@example.test", Password: "x", CreatedIPAddress: &addr}
if err := gdb.Create(user).Error; err != nil {
t.Fatal(err)
}
}
rec := postRegister(t, app, ip, `{"email":"fourth@example.test","password":"correct-horse"}`)
if rec.Code != http.StatusInternalServerError || rec.Body.String() != `{"error":"Internal server error"}` {
t.Fatalf("throttled %d %s", rec.Code, rec.Body.String())
}
if err := app.Config.Set("app.debug", true); err != nil {
t.Fatal(err)
}
debug := postRegister(t, app, ip, `{"email":"fifth@example.test","password":"correct-horse"}`)
if debug.Body.String() != `{"error":"Registration is throttled. Please try again later."}` {
t.Fatalf("debug throttle %d %s", debug.Code, debug.Body.String())
}
}
func TestRegisterUserModeMail(t *testing.T) {
app, _ := sessionApp(t, "0s")
disableRegisterThrottle(t, app)
if err := app.Config.Set("golem15.user.activation.activate_mode", "user"); err != nil {
t.Fatal(err)
}
mail := &captureMail{}
if err := app.Publish[postcard.Mailer](mail); err != nil {
t.Fatal(err)
}
rec := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"email":"en-user@example.test","password":"correct-horse","preferred_locale":"en"}`)
if rec.Code != http.StatusOK || rec.Body.String() != `{"message":"Activation email sent"}` {
t.Fatalf("user mode %d %s", rec.Code, rec.Body.String())
}
if len(mail.got) != 1 || mail.got[0] != "golem15.user::mail.activate-en" {
t.Fatalf("template = %#v", mail.got)
}
}
func TestRegisterAdminMode(t *testing.T) {
app, _ := sessionApp(t, "0s")
disableRegisterThrottle(t, app)
if err := app.Config.Set("golem15.user.activation.activate_mode", "admin"); err != nil {
t.Fatal(err)
}
if err := app.Config.Set("golem15.user.activation.require_activation", true); err != nil {
t.Fatal(err)
}
rec := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"email":"admin-mode@example.test","password":"correct-horse"}`)
if rec.Code != http.StatusOK || rec.Body.String() != `{}` {
t.Fatalf("admin mode %d %s", rec.Code, rec.Body.String())
}
}
func TestRegisterEvent(t *testing.T) {
app, _ := sessionApp(t, "0s")
disableRegisterThrottle(t, app)
var seen string
app.Events.Listen[*classes.RegisterEvent]("test", func(_ context.Context, e *classes.RegisterEvent) error {
if e != nil && e.User != nil {
seen = e.User.Email
}
return nil
})
rec := postJSON(t, controllers.Register(app), "/_user/api/v1/register", `{"email":"event@example.test","password":"correct-horse"}`)
if rec.Code != http.StatusOK {
t.Fatalf("register %d %s", rec.Code, rec.Body.String())
}
if seen != "event@example.test" {
t.Fatalf("event email = %q", seen)
}
}
func TestRegisterImportDirection(t *testing.T) {
cmd := exec.Command("go", "list", "-deps", "./...")
cmd.Dir = moduleRoot(t)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("go list: %v\n%s", err, out)
}
if strings.Contains(string(out), "plugins/golem15/fonoteka") {
t.Fatal("golem15.user imports golem15.fonoteka")
}
}
func TestRegisterCORSPath(t *testing.T) {
body, err := os.ReadFile(filepath.Join(appRoot(t), "config", "http.yaml"))
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(body), "_user/api/*") {
t.Fatal("cors paths missing _user/api/*")
}
}
func disableRegisterThrottle(t *testing.T, app *backpack.App) {
t.Helper()
if err := app.Config.Set("golem15.user.registration.use_register_throttle", false); err != nil {
t.Fatal(err)
}
}
func postRegister(t *testing.T, app *backpack.App, ip, body string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodPost, "/_user/api/v1/register", strings.NewReader(body))
req.Host = "example.com"
req.RemoteAddr = ip + ":9999"
req.Header.Set("Content-Type", "application/json")
rec := httptest.NewRecorder()
controllers.Register(app)(rec, req)
return rec
}
func moduleRoot(t *testing.T) string {
t.Helper()
return findUp(t, "go.mod")
}
func appRoot(t *testing.T) string {
t.Helper()
return findUp(t, filepath.Join("config", "http.yaml"))
}
func findUp(t *testing.T, rel string) string {
t.Helper()
dir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
for {
if _, err := os.Stat(filepath.Join(dir, rel)); err == nil {
return dir
}
parent := filepath.Dir(dir)
if parent == dir {
t.Fatalf("%s not found", rel)
}
dir = parent
}
}