164 lines
6.9 KiB
Go
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/modules/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
|
|
}
|