Files
sm-user-plugin/account_test.go
Jakub Zych d4066a8c3f test(07-07): cover already-activated activate and 22-ported corpus counts
Unit tests now expect Winter 500 HTML when the target user is already
active, and the inventory assertions match 169/22/147.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-22 23:57:10 +02:00

164 lines
6.9 KiB
Go

package user
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"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/postcard"
)
func TestForgotPassword(t *testing.T) {
app, gdb := sessionApp(t, "0s")
loc := "en"
user := insertUser(t, gdb, "forgot@example.test", "correct-horse")
if err := gdb.Model(user).Update("preferred_locale", loc).Error; err != nil {
t.Fatal(err)
}
mail := &captureMail{}
if err := app.Publish[postcard.Mailer](mail); err != nil {
t.Fatal(err)
}
known := postJSON(t, controllers.ForgotPassword(app), "/_user/api/v1/forgot-password", `{"email":"forgot@example.test"}`)
unknown := postJSON(t, controllers.ForgotPassword(app), "/_user/api/v1/forgot-password", `{"email":"missing@example.test"}`)
const body = `{"message":"If that email exists, a reset link has been sent."}`
if known.Code != http.StatusOK || known.Body.String() != body || unknown.Body.String() != body {
t.Fatalf("known %d %s unknown %d %s", known.Code, known.Body.String(), unknown.Code, unknown.Body.String())
}
if len(mail.got) != 1 || mail.got[0] != "golem15.user::mail.restore-en" {
t.Fatalf("template = %#v", mail.got)
}
bad := postJSON(t, controllers.ForgotPassword(app), "/_user/api/v1/forgot-password", `{"email":"nope"}`)
if bad.Code != http.StatusUnprocessableEntity || !strings.Contains(bad.Body.String(), `"errors":`) {
t.Fatalf("malformed %d %s", bad.Code, bad.Body.String())
}
var verr map[string]any
if err := json.Unmarshal(bad.Body.Bytes(), &verr); err != nil {
t.Fatal(err)
}
errorsBody, _ := verr["errors"].(map[string]any)
if _, ok := errorsBody["email"]; !ok {
t.Fatalf("errors = %#v", verr)
}
}
func TestResetPassword(t *testing.T) {
app, gdb := sessionApp(t, "0s")
user := insertUser(t, gdb, "reset-me@example.test", "correct-horse")
code, err := classes.IssueResetCode(t.Context(), gdb, user)
if err != nil {
t.Fatal(err)
}
packed := strconv.FormatUint(uint64(user.ID), 10) + "!" + code
ok := postJSON(t, controllers.ResetPassword(app), "/_user/api/v1/reset-password", `{"code":"`+packed+`","password":"new-password","password_confirmation":"new-password"}`)
if ok.Code != http.StatusOK || ok.Body.String() != `{"message":"Password has been reset"}` {
t.Fatalf("reset %d %s", ok.Code, ok.Body.String())
}
loginNew := postJSON(t, controllers.Login(app), "/_user/api/v1/login", `{"email":"reset-me@example.test","password":"new-password"}`)
if loginNew.Code != http.StatusOK {
t.Fatalf("new password %d %s", loginNew.Code, loginNew.Body.String())
}
loginOld := postJSON(t, controllers.Login(app), "/_user/api/v1/login", `{"email":"reset-me@example.test","password":"correct-horse"}`)
if loginOld.Code != http.StatusUnauthorized {
t.Fatalf("old password still works: %d %s", loginOld.Code, loginOld.Body.String())
}
malformed := postJSON(t, controllers.ResetPassword(app), "/_user/api/v1/reset-password", `{"code":"not-a-code","password":"new-password","password_confirmation":"new-password"}`)
if malformed.Code != http.StatusUnprocessableEntity || malformed.Body.String() != `{"error":"Invalid reset code"}` {
t.Fatalf("malformed %d %s", malformed.Code, malformed.Body.String())
}
wrong := postJSON(t, controllers.ResetPassword(app), "/_user/api/v1/reset-password", `{"code":"`+strconv.FormatUint(uint64(user.ID), 10)+`!nope","password":"new-password","password_confirmation":"new-password"}`)
if wrong.Body.String() != `{"error":"Invalid or expired reset code"}` {
t.Fatalf("wrong %d %s", wrong.Code, wrong.Body.String())
}
}
func TestActivate(t *testing.T) {
app, gdb := sessionApp(t, "0s")
token := loginToken(t, app, gdb, "activate-auth@example.test")
rec := bearerJSON(t, controllers.Activate(app), "/_user/api/v1/activate", token, `{"code":"wrong"}`)
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"is_activated":false`) {
t.Fatalf("activate %d %s", rec.Code, rec.Body.String())
}
already := loginToken(t, app, gdb, "activate-already@example.test")
if err := gdb.Model(&models.User{}).Where("email = ?", "activate-already@example.test").Update("is_activated", true).Error; err != nil {
t.Fatal(err)
}
locked := bearerJSON(t, controllers.Activate(app), "/_user/api/v1/activate", already, `{"code":"wrong"}`)
assertWinterErrorPage(t, locked)
}
func TestActivateByCode(t *testing.T) {
app, gdb := sessionApp(t, "0s")
user := insertUser(t, gdb, "activate-code@example.test", "correct-horse")
code, err := classes.IssueActivationCode(t.Context(), gdb, user)
if err != nil {
t.Fatal(err)
}
packed := strconv.FormatUint(uint64(user.ID), 10) + "!" + code
rec := postJSON(t, controllers.ActivateByCode(app), "/_user/api/v1/activate-by-code", `{"code":"`+packed+`"}`)
if rec.Code != http.StatusOK {
t.Fatalf("activate-by-code %d %s", rec.Code, rec.Body.String())
}
var body map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
token, _ := body["token"].(string)
userBody, _ := body["user"].(map[string]any)
if token == "" || userBody["is_activated"] != true || body["message"] != "Account activated" {
t.Fatalf("body %#v", body)
}
fetch := bearer(t, controllers.Fetch(app), http.MethodGet, "/_user/api/v1/fetch", token)
if fetch.Code != http.StatusOK {
t.Fatalf("fetch %d %s", fetch.Code, fetch.Body.String())
}
missing := postJSON(t, controllers.ActivateByCode(app), "/_user/api/v1/activate-by-code", `{}`)
if missing.Code != http.StatusUnprocessableEntity {
t.Fatalf("missing %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["code"]; !ok {
t.Fatalf("errors = %#v", verr)
}
bad := postJSON(t, controllers.ActivateByCode(app), "/_user/api/v1/activate-by-code", `{"code":"`+strconv.FormatUint(uint64(user.ID), 10)+`!nope"}`)
assertWinterErrorPage(t, bad)
}
func assertWinterErrorPage(t *testing.T, rec *httptest.ResponseRecorder) {
t.Helper()
if rec.Code != http.StatusInternalServerError {
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
}
if rec.Header().Get("Content-Type") != "text/html; charset=UTF-8" {
t.Fatalf("content-type %q", rec.Header().Get("Content-Type"))
}
if !strings.Contains(rec.Body.String(), "Błąd strony") {
t.Fatalf("missing winter error marker: %s", rec.Body.String())
}
}
func bearerJSON(t *testing.T, h http.HandlerFunc, path, token, body string) *httptest.ResponseRecorder {
t.Helper()
req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body))
req.Host = "example.com"
req.Header.Set("Content-Type", "application/json")
if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}
rec := httptest.NewRecorder()
h(rec, req)
return rec
}