add flash message helper
This commit is contained in:
parent
da62b1eecc
commit
cec7bc0e44
|
@ -9,6 +9,7 @@ cmd/insert-user/insert-user
|
|||
muxrpc/test/go/testrun
|
||||
muxrpc/test/nodejs/testrun
|
||||
web/handlers/testrun
|
||||
web/handlers/admin/testrun
|
||||
roomdb/sqlite/testrun
|
||||
|
||||
# build artifacts from node.js project web/styles
|
||||
|
|
2
go.mod
2
go.mod
|
@ -33,7 +33,7 @@ require (
|
|||
go.cryptoscope.co/muxrpc/v2 v2.0.0-beta.1.0.20210308090127-5f1f5f9cbb59
|
||||
go.cryptoscope.co/netwrap v0.1.1
|
||||
go.cryptoscope.co/secretstream v1.2.2
|
||||
go.mindeco.de v1.10.0
|
||||
go.mindeco.de v1.11.0
|
||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
|
|
2
go.sum
2
go.sum
|
@ -499,6 +499,8 @@ go.mindeco.de v1.9.0 h1:/xli02DkzpIUZxp/rp1nj8z/OZ9MHvkMIr9TfDVcmBg=
|
|||
go.mindeco.de v1.9.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
|
||||
go.mindeco.de v1.10.0 h1:H/bhL+dIgZZnUgBEDlKUJBisTszNiHDONeGZtGdiJJ0=
|
||||
go.mindeco.de v1.10.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
|
||||
go.mindeco.de v1.11.0 h1:zrNJFjRr7l+eoRywKxBetB1sgQwnrJ9NA65H/xeusTs=
|
||||
go.mindeco.de v1.11.0/go.mod h1:ePOcyktbpqzhMPRBDv2gUaDd3h8QtT+DUU1DK+VbQZE=
|
||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870 h1:TCI3AefMAaOYECvppn30+CfEB0Fn8IES1SKvvacc3/c=
|
||||
go.mindeco.de/ssb-refs v0.1.1-0.20210108133850-cf1f44fea870/go.mod h1:OnBnV02ux4lLsZ39LID6yYLqSDp+dqTHb/3miYPkQFs=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
|
|
|
@ -34,6 +34,13 @@ func (f ErrForbidden) Error() string {
|
|||
return fmt.Sprintf("rooms/web: access denied: %s", f.Details)
|
||||
}
|
||||
|
||||
// ErrRedirect decide to not render a page during the controller
|
||||
type ErrRedirect struct{ Path string }
|
||||
|
||||
func (err ErrRedirect) Error() string {
|
||||
return fmt.Sprintf("rooms/web: redirecting to: %s", err.Path)
|
||||
}
|
||||
|
||||
type PageNotFound struct{ Path string }
|
||||
|
||||
func (e PageNotFound) Error() string {
|
||||
|
|
|
@ -31,7 +31,40 @@ func (eh *ErrorHandler) SetRenderer(r *render.Renderer) {
|
|||
}
|
||||
|
||||
func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code int, err error) {
|
||||
var ih = i18n.LocalizerFromRequest(eh.locHelper, req)
|
||||
var redirectErr ErrRedirect
|
||||
if errors.As(err, &redirectErr) {
|
||||
http.Redirect(rw, req, redirectErr.Path, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
var ih = eh.locHelper.FromRequest(req)
|
||||
|
||||
code, msg := localizeError(ih, err)
|
||||
|
||||
data := errorTemplateData{
|
||||
Err: template.HTML(msg),
|
||||
// TODO: localize status codes? might be fine with a few
|
||||
Status: http.StatusText(code),
|
||||
StatusCode: code,
|
||||
}
|
||||
|
||||
renderErr := eh.render.Render(rw, req, "error.tmpl", code, data)
|
||||
if renderErr != nil {
|
||||
logger := logging.FromContext(req.Context())
|
||||
level.Error(logger).Log("event", "error template renderfailed",
|
||||
"orig-err", err,
|
||||
"render-err", renderErr,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type errorTemplateData struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Err template.HTML
|
||||
}
|
||||
|
||||
func localizeError(ih *i18n.Localizer, err error) (int, string) {
|
||||
|
||||
// default, unlocalized message
|
||||
msg := err.Error()
|
||||
|
@ -44,10 +77,13 @@ func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code i
|
|||
f ErrForbidden
|
||||
)
|
||||
|
||||
code := http.StatusInternalServerError
|
||||
|
||||
switch {
|
||||
|
||||
case err == ErrNotAuthorized:
|
||||
code = http.StatusForbidden
|
||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
||||
msg = ih.LocalizeSimple("ErrorNotAuthorized")
|
||||
|
||||
case err == auth.ErrBadLogin:
|
||||
msg = ih.LocalizeSimple("ErrorAuthBadLogin")
|
||||
|
@ -83,25 +119,5 @@ func (eh *ErrorHandler) Handle(rw http.ResponseWriter, req *http.Request, code i
|
|||
})
|
||||
}
|
||||
|
||||
data := errorTemplateData{
|
||||
Err: template.HTML(msg),
|
||||
// TODO: localize status codes? might be fine with a few
|
||||
Status: http.StatusText(code),
|
||||
StatusCode: code,
|
||||
}
|
||||
|
||||
renderErr := eh.render.Render(rw, req, "error.tmpl", code, data)
|
||||
if renderErr != nil {
|
||||
logger := logging.FromContext(req.Context())
|
||||
level.Error(logger).Log("event", "error template renderfailed",
|
||||
"orig-err", err,
|
||||
"render-err", renderErr,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type errorTemplateData struct {
|
||||
StatusCode int
|
||||
Status string
|
||||
Err template.HTML
|
||||
return code, msg
|
||||
}
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||
)
|
||||
|
||||
type FlashHelper struct {
|
||||
store sessions.Store
|
||||
|
||||
locHelper *i18n.Helper
|
||||
}
|
||||
|
||||
func NewFlashHelper(s sessions.Store, loc *i18n.Helper) *FlashHelper {
|
||||
gob.Register(FlashMessage{})
|
||||
|
||||
return &FlashHelper{
|
||||
store: s,
|
||||
locHelper: loc,
|
||||
}
|
||||
}
|
||||
|
||||
const flashSession = "go-ssb-room-flash-messages"
|
||||
|
||||
type FlashKind uint
|
||||
|
||||
const (
|
||||
_ FlashKind = iota
|
||||
// FlashError signals that a problem occured
|
||||
FlashError
|
||||
// FlashNotification represents a normal message (like "xyz added/updated successfull")
|
||||
FlashNotification
|
||||
)
|
||||
|
||||
type FlashMessage struct {
|
||||
Kind FlashKind
|
||||
Message string
|
||||
}
|
||||
|
||||
// TODO: rethink error return - maybe panic() / maybe render package?
|
||||
|
||||
// AddMessage expects a i18n label, translates it and adds it as a FlashNotification
|
||||
func (fh FlashHelper) AddMessage(rw http.ResponseWriter, req *http.Request, label string) {
|
||||
session, err := fh.store.Get(req, flashSession)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("flashHelper: failed to get session: %w", err))
|
||||
}
|
||||
|
||||
ih := fh.locHelper.FromRequest(req)
|
||||
|
||||
session.AddFlash(FlashMessage{
|
||||
Kind: FlashNotification,
|
||||
Message: ih.LocalizeSimple(label),
|
||||
})
|
||||
|
||||
if err := session.Save(req, rw); err != nil {
|
||||
panic(fmt.Errorf("flashHelper: failed to save session: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// AddError adds a FlashError and translates the passed err using localizeError()
|
||||
func (fh FlashHelper) AddError(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
session, getErr := fh.store.Get(req, flashSession)
|
||||
if getErr != nil {
|
||||
panic(fmt.Errorf("flashHelper: failed to get session: %w", err))
|
||||
}
|
||||
|
||||
ih := fh.locHelper.FromRequest(req)
|
||||
|
||||
_, msg := localizeError(ih, err)
|
||||
|
||||
session.AddFlash(FlashMessage{
|
||||
Kind: FlashError,
|
||||
Message: msg,
|
||||
})
|
||||
if err := session.Save(req, rw); err != nil {
|
||||
panic(fmt.Errorf("flashHelper: failed to save session: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetAll returns all the FlashMessages, emptys and updates the store
|
||||
func (fh FlashHelper) GetAll(rw http.ResponseWriter, req *http.Request) ([]FlashMessage, error) {
|
||||
session, err := fh.store.Get(req, flashSession)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opaqueFlashes := session.Flashes()
|
||||
|
||||
flashes := make([]FlashMessage, len(opaqueFlashes))
|
||||
|
||||
for i, of := range opaqueFlashes {
|
||||
f, ok := of.(FlashMessage)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("GetFlashes: failed to unpack flash: %T", of)
|
||||
}
|
||||
|
||||
flashes[i].Kind = f.Kind
|
||||
flashes[i].Message = f.Message
|
||||
}
|
||||
|
||||
err = session.Save(req, rw)
|
||||
|
||||
return flashes, err
|
||||
}
|
|
@ -3,7 +3,6 @@
|
|||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
@ -20,6 +19,8 @@ import (
|
|||
type aliasesHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
flashes *weberrors.FlashHelper
|
||||
|
||||
db roomdb.AliasesService
|
||||
}
|
||||
|
||||
|
@ -38,11 +39,8 @@ func (h aliasesHandler) revokeConfirm(rw http.ResponseWriter, req *http.Request)
|
|||
|
||||
entry, err := h.db.GetByID(req.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
http.Redirect(rw, req, redirectToAliases, http.StatusFound)
|
||||
return nil, ErrRedirected
|
||||
}
|
||||
return nil, err
|
||||
h.flashes.AddError(rw, req, err)
|
||||
return nil, weberrors.ErrRedirect{Path: redirectToAliases}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
|
@ -65,17 +63,12 @@ func (h aliasesHandler) revoke(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
status := http.StatusFound
|
||||
status := http.StatusTemporaryRedirect
|
||||
err = h.db.Revoke(req.Context(), req.FormValue("name"))
|
||||
if err != nil {
|
||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||
// TODO: flash error
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status = http.StatusNotFound
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
h.flashes.AddError(rw, req, err)
|
||||
} else {
|
||||
h.flashes.AddMessage(rw, req, "AdminAliasRevoked")
|
||||
}
|
||||
|
||||
http.Redirect(rw, req, redirectToAliases, status)
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
@ -23,10 +22,9 @@ func TestAliasesRevokeConfirmation(t *testing.T) {
|
|||
testEntry := roomdb.Alias{ID: 666, Name: "the-test-name", Feed: *testKey}
|
||||
ts.AliasesDB.GetByIDReturns(testEntry, nil)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRevokeConfirm := urlTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
||||
urlRevokeConfirm := ts.URLTo(router.AdminAliasesRevokeConfirm, "id", 3)
|
||||
|
||||
html, resp := ts.Client.GetHTML(urlRevokeConfirm.String())
|
||||
html, resp := ts.Client.GetHTML(urlRevokeConfirm)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||
|
@ -40,10 +38,8 @@ func TestAliasesRevokeConfirmation(t *testing.T) {
|
|||
action, ok := form.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminAliasesRevoke).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminAliasesRevoke)
|
||||
a.Equal(addURL.Path, action)
|
||||
|
||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||
{Name: "name", Type: "hidden", Value: testEntry.Name},
|
||||
|
@ -54,14 +50,23 @@ func TestAliasesRevoke(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRevoke := urlTo(router.AdminAliasesRevoke)
|
||||
urlRevoke := ts.URLTo(router.AdminAliasesRevoke)
|
||||
overviewURL := ts.URLTo(router.AdminAliasesOverview)
|
||||
|
||||
ts.AliasesDB.RevokeReturns(nil)
|
||||
|
||||
addVals := url.Values{"name": []string{"the-name"}}
|
||||
rec := ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
rec := ts.Client.PostForm(urlRevoke, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
a.Equal(overviewURL.Path, rec.Header().Get("Location"))
|
||||
a.True(len(rec.Result().Cookies()) > 0, "got a cookie")
|
||||
|
||||
// check flash messages
|
||||
doc, resp := ts.Client.GetHTML(overviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
flashes := doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("AdminAliasRevoked", flashes.Text())
|
||||
|
||||
a.Equal(1, ts.AliasesDB.RevokeCallCount())
|
||||
_, theName := ts.AliasesDB.RevokeArgsForCall(0)
|
||||
|
@ -70,7 +75,15 @@ func TestAliasesRevoke(t *testing.T) {
|
|||
// now for unknown ID
|
||||
ts.AliasesDB.RevokeReturns(roomdb.ErrNotFound)
|
||||
addVals = url.Values{"name": []string{"nope"}}
|
||||
rec = ts.Client.PostForm(urlRevoke.String(), addVals)
|
||||
a.Equal(http.StatusNotFound, rec.Code)
|
||||
//TODO: update redirect code with flash errors
|
||||
rec = ts.Client.PostForm(urlRevoke, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
a.Equal(overviewURL.Path, rec.Header().Get("Location"))
|
||||
a.True(len(rec.Result().Cookies()) > 0, "got a cookie")
|
||||
|
||||
// check flash messages
|
||||
doc, resp = ts.Client.GetHTML(overviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
flashes = doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("ErrorNotFound", flashes.Text())
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
type deniedKeysHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
flashes *weberrors.FlashHelper
|
||||
|
||||
db roomdb.DeniedKeysService
|
||||
}
|
||||
|
||||
|
@ -40,7 +42,8 @@ func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
|||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
h.flashes.AddError(w, req, err)
|
||||
http.Redirect(w, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -49,16 +52,12 @@ func (h deniedKeysHandler) add(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
err = h.db.Add(req.Context(), *newEntryParsed, comment)
|
||||
if err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
var aa roomdb.ErrAlreadyAdded
|
||||
if errors.As(err, &aa) {
|
||||
code = http.StatusBadRequest
|
||||
}
|
||||
h.r.Error(w, req, code, err)
|
||||
return
|
||||
h.flashes.AddError(w, req, err)
|
||||
} else {
|
||||
h.flashes.AddMessage(w, req, "AdminDeniedKeysAdded")
|
||||
}
|
||||
|
||||
http.Redirect(w, req, redirectToDeniedKeys, http.StatusFound)
|
||||
http.Redirect(w, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (h deniedKeysHandler) overview(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
|
@ -77,13 +76,14 @@ func (h deniedKeysHandler) overview(rw http.ResponseWriter, req *http.Request) (
|
|||
}
|
||||
|
||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
// TODO: move to render package so that we can decide to not render a page during the controller
|
||||
var ErrRedirected = errors.New("render: not rendered but redirected")
|
||||
|
||||
func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||
id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64)
|
||||
if err != nil {
|
||||
|
@ -93,12 +93,8 @@ func (h deniedKeysHandler) removeConfirm(rw http.ResponseWriter, req *http.Reque
|
|||
|
||||
entry, err := h.db.GetByID(req.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
||||
return nil, ErrRedirected
|
||||
}
|
||||
return nil, err
|
||||
h.flashes.AddError(rw, req, err)
|
||||
return nil, weberrors.ErrRedirect{Path: redirectToDeniedKeys}
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
|
@ -112,7 +108,7 @@ func (h deniedKeysHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
|||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -120,7 +116,7 @@ func (h deniedKeysHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
|||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusFound)
|
||||
http.Redirect(rw, req, redirectToDeniedKeys, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,11 @@ import (
|
|||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
@ -24,10 +20,9 @@ func TestDeniedKeysEmpty(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
url, err := ts.Router.Get(router.AdminDeniedKeysOverview).URL()
|
||||
a.Nil(err)
|
||||
url := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
html, resp := ts.Client.GetHTML(url)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -41,10 +36,9 @@ func TestDeniedKeysAdd(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
listURL, err := ts.Router.Get(router.AdminDeniedKeysOverview).URL()
|
||||
a.NoError(err)
|
||||
listURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(listURL.String())
|
||||
html, resp := ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
formSelection := html.Find("form#add-entry")
|
||||
|
@ -57,10 +51,8 @@ func TestDeniedKeysAdd(t *testing.T) {
|
|||
action, ok := formSelection.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysAdd).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminDeniedKeysAdd)
|
||||
a.Equal(addURL.Path, action)
|
||||
|
||||
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
||||
{Name: "pub_key", Type: "text"},
|
||||
|
@ -73,8 +65,8 @@ func TestDeniedKeysAdd(t *testing.T) {
|
|||
// just any key that looks valid
|
||||
"pub_key": []string{newKey},
|
||||
}
|
||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
rec := ts.Client.PostForm(addURL, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
a.Equal(1, ts.DeniedKeysDB.AddCallCount())
|
||||
_, addedKey, addedComment := ts.DeniedKeysDB.AddArgsForCall(0)
|
||||
|
@ -85,29 +77,30 @@ func TestDeniedKeysAdd(t *testing.T) {
|
|||
func TestDeniedKeysDontAddInvalid(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysAdd).URL()
|
||||
a.NoError(err)
|
||||
addURL := ts.URLTo(router.AdminDeniedKeysAdd)
|
||||
|
||||
newKey := "@some-garbage"
|
||||
addVals := url.Values{
|
||||
"comment": []string{"some-comment"},
|
||||
"pub_key": []string{newKey},
|
||||
}
|
||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
||||
a.Equal(http.StatusBadRequest, rec.Code)
|
||||
rec := ts.Client.PostForm(addURL, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
a.Equal(0, ts.DeniedKeysDB.AddCallCount())
|
||||
a.Equal(0, ts.DeniedKeysDB.AddCallCount(), "did not call add")
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(rec.Body)
|
||||
r.NoError(err)
|
||||
listURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||
res := rec.Result()
|
||||
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||
|
||||
expErr := `bad request: feedRef: couldn't parse "@some-garbage"`
|
||||
gotMsg := doc.Find("#errBody").Text()
|
||||
if !a.True(strings.HasPrefix(gotMsg, expErr), "did not find errBody") {
|
||||
t.Log(gotMsg)
|
||||
}
|
||||
doc, resp := ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
flashes := doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("ErrorBadRequest", flashes.Text())
|
||||
}
|
||||
|
||||
func TestDeniedKeys(t *testing.T) {
|
||||
|
@ -121,7 +114,9 @@ func TestDeniedKeys(t *testing.T) {
|
|||
}
|
||||
ts.DeniedKeysDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp := ts.Client.GetHTML("/denied")
|
||||
deniedOverviewURL := ts.URLTo(router.AdminDeniedKeysOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(deniedOverviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -137,7 +132,7 @@ func TestDeniedKeys(t *testing.T) {
|
|||
}
|
||||
ts.DeniedKeysDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML("/denied")
|
||||
html, resp = ts.Client.GetHTML(deniedOverviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -164,10 +159,9 @@ func TestDeniedKeysRemoveConfirmation(t *testing.T) {
|
|||
testEntry := roomdb.ListEntry{ID: 666, PubKey: *testKey}
|
||||
ts.DeniedKeysDB.GetByIDReturns(testEntry, nil)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemoveConfirm := urlTo(router.AdminDeniedKeysRemoveConfirm, "id", 3)
|
||||
urlRemoveConfirm := ts.URLTo(router.AdminDeniedKeysRemoveConfirm, "id", 3)
|
||||
|
||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm.String())
|
||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||
|
@ -181,10 +175,8 @@ func TestDeniedKeysRemoveConfirmation(t *testing.T) {
|
|||
action, ok := form.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminDeniedKeysRemove).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminDeniedKeysRemove)
|
||||
a.Equal(addURL.Path, action)
|
||||
|
||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||
{Name: "id", Type: "hidden", Value: "666"},
|
||||
|
@ -195,13 +187,12 @@ func TestDeniedKeysRemove(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemove := urlTo(router.AdminDeniedKeysRemove)
|
||||
urlRemove := ts.URLTo(router.AdminDeniedKeysRemove)
|
||||
|
||||
ts.DeniedKeysDB.RemoveIDReturns(nil)
|
||||
|
||||
addVals := url.Values{"id": []string{"666"}}
|
||||
rec := ts.Client.PostForm(urlRemove.String(), addVals)
|
||||
rec := ts.Client.PostForm(urlRemove, addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
|
||||
a.Equal(1, ts.DeniedKeysDB.RemoveIDCallCount())
|
||||
|
@ -211,7 +202,7 @@ func TestDeniedKeysRemove(t *testing.T) {
|
|||
// now for unknown ID
|
||||
ts.DeniedKeysDB.RemoveIDReturns(roomdb.ErrNotFound)
|
||||
addVals = url.Values{"id": []string{"667"}}
|
||||
rec = ts.Client.PostForm(urlRemove.String(), addVals)
|
||||
rec = ts.Client.PostForm(urlRemove, addVals)
|
||||
a.Equal(http.StatusNotFound, rec.Code)
|
||||
//TODO: update redirect code with flash errors
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
)
|
||||
|
||||
// HTMLTemplates define the list of files the template system should load.
|
||||
|
@ -60,11 +61,11 @@ func Handler(
|
|||
domainName string,
|
||||
r *render.Renderer,
|
||||
roomState *roomstate.Manager,
|
||||
fh *weberrors.FlashHelper,
|
||||
dbs Databases,
|
||||
) http.Handler {
|
||||
mux := &http.ServeMux{}
|
||||
|
||||
// TODO: configure 404 handler
|
||||
var dashboardHandler = dashboardHandler{
|
||||
r: r,
|
||||
dbs: dbs,
|
||||
|
@ -85,6 +86,8 @@ func Handler(
|
|||
|
||||
var ah = aliasesHandler{
|
||||
r: r,
|
||||
flashes: fh,
|
||||
|
||||
db: dbs.Aliases,
|
||||
}
|
||||
mux.HandleFunc("/aliases/revoke/confirm", r.HTML("admin/aliases-revoke-confirm.tmpl", ah.revokeConfirm))
|
||||
|
@ -92,6 +95,8 @@ func Handler(
|
|||
|
||||
var dh = deniedKeysHandler{
|
||||
r: r,
|
||||
flashes: fh,
|
||||
|
||||
db: dbs.DeniedKeys,
|
||||
}
|
||||
mux.HandleFunc("/denied", r.HTML("admin/denied-keys.tmpl", dh.overview))
|
||||
|
@ -101,6 +106,8 @@ func Handler(
|
|||
|
||||
var mh = membersHandler{
|
||||
r: r,
|
||||
flashes: fh,
|
||||
|
||||
db: dbs.Members,
|
||||
}
|
||||
mux.HandleFunc("/member", r.HTML("admin/member.tmpl", mh.details))
|
||||
|
@ -112,6 +119,8 @@ func Handler(
|
|||
|
||||
var ih = invitesHandler{
|
||||
r: r,
|
||||
flashes: fh,
|
||||
|
||||
db: dbs.Invites,
|
||||
config: dbs.Config,
|
||||
|
||||
|
@ -125,6 +134,8 @@ func Handler(
|
|||
|
||||
var nh = noticeHandler{
|
||||
r: r,
|
||||
flashes: fh,
|
||||
|
||||
noticeDB: dbs.Notices,
|
||||
pinnedDB: dbs.PinnedNotices,
|
||||
}
|
||||
|
@ -133,6 +144,11 @@ func Handler(
|
|||
mux.HandleFunc("/notice/translation/add", nh.addTranslation)
|
||||
mux.HandleFunc("/notice/save", nh.save)
|
||||
|
||||
// path:/ matches everything that isn't registerd (ie. its the "Not Found handler")
|
||||
mux.HandleFunc("/", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
r.Error(rw, req, 404, weberrors.ErrNotFound{What: req.URL.Path})
|
||||
}))
|
||||
|
||||
return customStripPrefix("/admin", mux)
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,9 @@ func TestDashoard(t *testing.T) {
|
|||
ts.InvitesDB.CountReturns(3, nil) // 3 invites
|
||||
ts.DeniedKeysDB.CountReturns(2, nil) // 2 banned
|
||||
|
||||
url, err := ts.Router.Get(router.AdminDashboard).URL()
|
||||
a.Nil(err)
|
||||
dashURL := ts.URLTo(router.AdminDashboard)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
html, resp := ts.Client.GetHTML(dashURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal("1", html.Find("#online-count").Text())
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
type invitesHandler struct {
|
||||
r *render.Renderer
|
||||
flashes *weberrors.FlashHelper
|
||||
|
||||
db roomdb.InvitesService
|
||||
config roomdb.RoomConfig
|
||||
|
@ -42,6 +43,10 @@ func (h invitesHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
|||
}
|
||||
|
||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
)
|
||||
|
@ -28,7 +27,9 @@ func TestInvitesOverview(t *testing.T) {
|
|||
}
|
||||
ts.InvitesDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp := ts.Client.GetHTML("/invites")
|
||||
invitesOverviewURL := ts.URLTo(router.AdminInvitesOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(invitesOverviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -46,7 +47,7 @@ func TestInvitesOverview(t *testing.T) {
|
|||
}
|
||||
ts.InvitesDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML("/invites")
|
||||
html, resp = ts.Client.GetHTML(invitesOverviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -68,10 +69,9 @@ func TestInvitesCreateForm(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
url, err := ts.Router.Get(router.AdminInvitesOverview).URL()
|
||||
a.Nil(err)
|
||||
overviewURL := ts.URLTo(router.AdminInvitesOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
html, resp := ts.Client.GetHTML(overviewURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -89,10 +89,8 @@ func TestInvitesCreateForm(t *testing.T) {
|
|||
action, ok := formSelection.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminInvitesCreate).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminInvitesCreate)
|
||||
a.Equal(addURL.Path, action)
|
||||
}
|
||||
|
||||
func TestInvitesCreate(t *testing.T) {
|
||||
|
@ -100,13 +98,12 @@ func TestInvitesCreate(t *testing.T) {
|
|||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemove := urlTo(router.AdminInvitesCreate)
|
||||
urlRemove := ts.URLTo(router.AdminInvitesCreate)
|
||||
|
||||
testInvite := "your-fake-test-invite"
|
||||
ts.InvitesDB.CreateReturns(testInvite, nil)
|
||||
|
||||
rec := ts.Client.PostForm(urlRemove.String(), url.Values{})
|
||||
rec := ts.Client.PostForm(urlRemove, url.Values{})
|
||||
a.Equal(http.StatusOK, rec.Code)
|
||||
|
||||
r.Equal(1, ts.InvitesDB.CreateCallCount(), "expected one invites.Create call")
|
||||
|
@ -121,9 +118,7 @@ func TestInvitesCreate(t *testing.T) {
|
|||
{"#welcome", "AdminInviteCreatedTitle" + "AdminInviteCreatedInstruct"},
|
||||
})
|
||||
|
||||
wantURL := urlTo(router.CompleteInviteFacade, "token", testInvite)
|
||||
wantURL.Host = ts.Domain
|
||||
wantURL.Scheme = "https"
|
||||
wantURL := ts.URLTo(router.CompleteInviteFacade, "token", testInvite)
|
||||
|
||||
shownLink := doc.Find("#invite-facade-link").Text()
|
||||
a.Equal(wantURL.String(), shownLink)
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
type membersHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
flashes *weberrors.FlashHelper
|
||||
|
||||
db roomdb.MembersService
|
||||
}
|
||||
|
||||
|
@ -44,22 +46,19 @@ func (h membersHandler) add(w http.ResponseWriter, req *http.Request) {
|
|||
newEntryParsed, err := refs.ParseFeedRef(newEntry)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Public Key", Details: err}
|
||||
h.r.Error(w, req, http.StatusBadRequest, err)
|
||||
h.flashes.AddError(w, req, err)
|
||||
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = h.db.Add(req.Context(), *newEntryParsed, roomdb.RoleMember)
|
||||
if err != nil {
|
||||
code := http.StatusInternalServerError
|
||||
var aa roomdb.ErrAlreadyAdded
|
||||
if errors.As(err, &aa) {
|
||||
code = http.StatusBadRequest
|
||||
}
|
||||
h.r.Error(w, req, code, err)
|
||||
return
|
||||
h.flashes.AddError(w, req, err)
|
||||
} else {
|
||||
h.flashes.AddMessage(w, req, "AdminMemberAdded")
|
||||
}
|
||||
|
||||
http.Redirect(w, req, redirectToMembers, http.StatusFound)
|
||||
http.Redirect(w, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -98,11 +97,12 @@ func (h membersHandler) changeRole(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
if err := h.db.SetRole(req.Context(), memberID, role); err != nil {
|
||||
err = weberrors.DatabaseError{Reason: err}
|
||||
// TODO: not found error
|
||||
h.r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.flashes.AddMessage(w, req, "AdminMemberUpdated")
|
||||
|
||||
urlTo := web.NewURLTo(router.CompleteApp())
|
||||
memberDetailsURL := urlTo(router.AdminMemberDetails, "id", memberID).String()
|
||||
http.Redirect(w, req, memberDetailsURL, http.StatusTemporaryRedirect)
|
||||
|
@ -127,6 +127,11 @@ func (h membersHandler) overview(rw http.ResponseWriter, req *http.Request) (int
|
|||
|
||||
pageData["AllRoles"] = []roomdb.Role{roomdb.RoleMember, roomdb.RoleModerator, roomdb.RoleAdmin}
|
||||
|
||||
pageData["Flashes"], err = h.flashes.GetAll(rw, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pageData, nil
|
||||
}
|
||||
|
||||
|
@ -162,8 +167,8 @@ func (h membersHandler) removeConfirm(rw http.ResponseWriter, req *http.Request)
|
|||
entry, err := h.db.GetByID(req.Context(), id)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
||||
return nil, ErrRedirected
|
||||
h.flashes.AddError(rw, req, err)
|
||||
return nil, weberrors.ErrRedirect{Path: redirectToMembers}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
@ -175,32 +180,32 @@ func (h membersHandler) removeConfirm(rw http.ResponseWriter, req *http.Request)
|
|||
}
|
||||
|
||||
func (h membersHandler) remove(rw http.ResponseWriter, req *http.Request) {
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
if req.Method != "POST" {
|
||||
err := weberrors.ErrBadRequest{Where: "HTTP Method", Details: fmt.Errorf("expected POST not %s", req.Method)}
|
||||
h.r.Error(rw, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := req.ParseForm(); err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "Form data", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
||||
h.r.Error(rw, req, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseInt(req.FormValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
err = weberrors.ErrBadRequest{Where: "ID", Details: err}
|
||||
// TODO "flash" errors
|
||||
http.Redirect(rw, req, redirectToMembers, http.StatusFound)
|
||||
h.flashes.AddError(rw, req, err)
|
||||
http.Redirect(rw, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
status := http.StatusFound
|
||||
err = h.db.RemoveID(req.Context(), id)
|
||||
if err != nil {
|
||||
if !errors.Is(err, roomdb.ErrNotFound) {
|
||||
// TODO "flash" errors
|
||||
h.r.Error(rw, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
status = http.StatusNotFound
|
||||
h.flashes.AddError(rw, req, err)
|
||||
} else {
|
||||
h.flashes.AddMessage(rw, req, "AdminMemberRemoved")
|
||||
}
|
||||
|
||||
http.Redirect(rw, req, redirectToMembers, status)
|
||||
http.Redirect(rw, req, redirectToMembers, http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
|
|
@ -4,16 +4,11 @@ import (
|
|||
"bytes"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
@ -23,10 +18,9 @@ func TestMembersEmpty(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
url, err := ts.Router.Get(router.AdminMembersOverview).URL()
|
||||
a.Nil(err)
|
||||
url := ts.URLTo(router.AdminMembersOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
html, resp := ts.Client.GetHTML(url)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -40,10 +34,9 @@ func TestMembersAdd(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
listURL, err := ts.Router.Get(router.AdminMembersOverview).URL()
|
||||
a.NoError(err)
|
||||
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(listURL.String())
|
||||
html, resp := ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
formSelection := html.Find("form#add-entry")
|
||||
|
@ -56,10 +49,8 @@ func TestMembersAdd(t *testing.T) {
|
|||
action, ok := formSelection.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminMembersAdd).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminMembersAdd)
|
||||
a.Equal(addURL.Path, action)
|
||||
|
||||
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
|
||||
{Name: "pub_key", Type: "text"},
|
||||
|
@ -70,8 +61,8 @@ func TestMembersAdd(t *testing.T) {
|
|||
// just any key that looks valid
|
||||
"pub_key": []string{newKey},
|
||||
}
|
||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
rec := ts.Client.PostForm(addURL, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
a.Equal(1, ts.MembersDB.AddCallCount())
|
||||
_, addedPubKey, addedRole := ts.MembersDB.AddArgsForCall(0)
|
||||
|
@ -83,29 +74,31 @@ func TestMembersAdd(t *testing.T) {
|
|||
func TestMembersDontAddInvalid(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminMembersAdd).URL()
|
||||
a.NoError(err)
|
||||
addURL := ts.URLTo(router.AdminMembersAdd)
|
||||
|
||||
newKey := "@some-garbage"
|
||||
addVals := url.Values{
|
||||
"nick": []string{"some-test-nick"},
|
||||
"pub_key": []string{newKey},
|
||||
}
|
||||
rec := ts.Client.PostForm(addURL.String(), addVals)
|
||||
a.Equal(http.StatusBadRequest, rec.Code)
|
||||
rec := ts.Client.PostForm(addURL, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
a.Equal(0, ts.MembersDB.AddCallCount())
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(rec.Body)
|
||||
r.NoError(err)
|
||||
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||
res := rec.Result()
|
||||
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||
|
||||
doc, resp := ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
flashes := doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("ErrorBadRequest", flashes.Text())
|
||||
|
||||
expErr := `bad public key: feedRef: couldn't parse "@some-garbage"`
|
||||
gotMsg := doc.Find("#errBody").Text()
|
||||
if !a.True(strings.HasPrefix(gotMsg, expErr), "did not find errBody") {
|
||||
t.Log(gotMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMembers(t *testing.T) {
|
||||
|
@ -119,7 +112,9 @@ func TestMembers(t *testing.T) {
|
|||
}
|
||||
ts.MembersDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp := ts.Client.GetHTML("/members")
|
||||
membersOveriwURL := ts.URLTo(router.AdminMembersOverview)
|
||||
|
||||
html, resp := ts.Client.GetHTML(membersOveriwURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -135,7 +130,7 @@ func TestMembers(t *testing.T) {
|
|||
}
|
||||
ts.MembersDB.ListReturns(lst, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML("/members")
|
||||
html, resp = ts.Client.GetHTML(membersOveriwURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -218,10 +213,9 @@ func TestMembersRemoveConfirmation(t *testing.T) {
|
|||
testEntry := roomdb.Member{ID: 666, PubKey: *testKey}
|
||||
ts.MembersDB.GetByIDReturns(testEntry, nil)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemoveConfirm := urlTo(router.AdminMembersRemoveConfirm, "id", 3)
|
||||
urlRemoveConfirm := ts.URLTo(router.AdminMembersRemoveConfirm, "id", 3)
|
||||
|
||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm.String())
|
||||
html, resp := ts.Client.GetHTML(urlRemoveConfirm)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
a.Equal(testKey.Ref(), html.Find("pre#verify").Text(), "has the key for verification")
|
||||
|
@ -235,10 +229,8 @@ func TestMembersRemoveConfirmation(t *testing.T) {
|
|||
action, ok := form.Attr("action")
|
||||
a.True(ok, "form has action set")
|
||||
|
||||
addURL, err := ts.Router.Get(router.AdminMembersRemove).URL()
|
||||
a.NoError(err)
|
||||
|
||||
a.Equal(addURL.String(), action)
|
||||
addURL := ts.URLTo(router.AdminMembersRemove)
|
||||
a.Equal(addURL.Path, action)
|
||||
|
||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||
{Name: "id", Type: "hidden", Value: "666"},
|
||||
|
@ -249,23 +241,46 @@ func TestMembersRemove(t *testing.T) {
|
|||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
urlRemove := urlTo(router.AdminMembersRemove)
|
||||
urlRemove := ts.URLTo(router.AdminMembersRemove)
|
||||
|
||||
ts.MembersDB.RemoveIDReturns(nil)
|
||||
|
||||
addVals := url.Values{"id": []string{"666"}}
|
||||
rec := ts.Client.PostForm(urlRemove.String(), addVals)
|
||||
a.Equal(http.StatusFound, rec.Code)
|
||||
rec := ts.Client.PostForm(urlRemove, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
a.Equal(1, ts.MembersDB.RemoveIDCallCount())
|
||||
_, theID := ts.MembersDB.RemoveIDArgsForCall(0)
|
||||
a.EqualValues(666, theID)
|
||||
|
||||
listURL := ts.URLTo(router.AdminMembersOverview)
|
||||
// check flash message
|
||||
res := rec.Result()
|
||||
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||
|
||||
doc, resp := ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
flashes := doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("AdminMemberRemoved", flashes.Text())
|
||||
|
||||
// now for unknown ID
|
||||
ts.MembersDB.RemoveIDReturns(roomdb.ErrNotFound)
|
||||
addVals = url.Values{"id": []string{"667"}}
|
||||
rec = ts.Client.PostForm(urlRemove.String(), addVals)
|
||||
a.Equal(http.StatusNotFound, rec.Code)
|
||||
//TODO: update redirect code with flash errors
|
||||
rec = ts.Client.PostForm(urlRemove, addVals)
|
||||
a.Equal(http.StatusTemporaryRedirect, rec.Code)
|
||||
|
||||
// check flash message
|
||||
res = rec.Result()
|
||||
a.Equal(listURL.Path, res.Header.Get("Location"), "redirecting to overview")
|
||||
a.True(len(res.Cookies()) > 0, "got a cookie (flash msg)")
|
||||
|
||||
doc, resp = ts.Client.GetHTML(listURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
flashes = doc.Find("#flashes-list").Children()
|
||||
a.Equal(1, flashes.Length())
|
||||
a.Equal("ErrorNotFound", flashes.Text())
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
type noticeHandler struct {
|
||||
r *render.Renderer
|
||||
|
||||
flashes *weberrors.FlashHelper
|
||||
|
||||
noticeDB roomdb.NoticesService
|
||||
pinnedDB roomdb.PinnedNoticesService
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -17,8 +16,6 @@ import (
|
|||
func TestNoticeSaveActuallyCalled(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
id := []string{"1"}
|
||||
title := []string{"SSB Breaking News: This Test Is Great"}
|
||||
|
@ -26,9 +23,9 @@ func TestNoticeSaveActuallyCalled(t *testing.T) {
|
|||
language := []string{"en-GB"}
|
||||
|
||||
// POST a correct request to the save handler, and verify that the save was handled using the mock database)
|
||||
u := urlTo(router.AdminNoticeSave)
|
||||
u := ts.URLTo(router.AdminNoticeSave)
|
||||
formValues := url.Values{"id": id, "title": title, "content": content, "language": language}
|
||||
resp := ts.Client.PostForm(u.String(), formValues)
|
||||
resp := ts.Client.PostForm(u, formValues)
|
||||
a.Equal(http.StatusSeeOther, resp.Code, "POST should work")
|
||||
a.Equal(1, ts.NoticeDB.SaveCallCount(), "noticedb should have saved after POST completed")
|
||||
}
|
||||
|
@ -37,8 +34,6 @@ func TestNoticeSaveActuallyCalled(t *testing.T) {
|
|||
func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
// notice values we are selectively omitting in the tests below
|
||||
id := []string{"1"}
|
||||
|
@ -47,24 +42,24 @@ func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
|||
language := []string{"pt"}
|
||||
|
||||
/* save without id */
|
||||
u := urlTo(router.AdminNoticeSave)
|
||||
u := ts.URLTo(router.AdminNoticeSave)
|
||||
emptyParams := url.Values{}
|
||||
resp := ts.Client.PostForm(u.String(), emptyParams)
|
||||
resp := ts.Client.PostForm(u, emptyParams)
|
||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without id should not work")
|
||||
|
||||
/* save without title */
|
||||
formValues := url.Values{"id": id, "content": content, "language": language}
|
||||
resp = ts.Client.PostForm(u.String(), formValues)
|
||||
resp = ts.Client.PostForm(u, formValues)
|
||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without title should not work")
|
||||
|
||||
/* save without content */
|
||||
formValues = url.Values{"id": id, "title": title, "language": language}
|
||||
resp = ts.Client.PostForm(u.String(), formValues)
|
||||
resp = ts.Client.PostForm(u, formValues)
|
||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without content should not work")
|
||||
|
||||
/* save without language */
|
||||
formValues = url.Values{"id": id, "title": title, "content": content}
|
||||
resp = ts.Client.PostForm(u.String(), formValues)
|
||||
resp = ts.Client.PostForm(u, formValues)
|
||||
a.Equal(http.StatusInternalServerError, resp.Code, "saving without language should not work")
|
||||
|
||||
a.Equal(0, ts.NoticeDB.SaveCallCount(), "noticedb should never save incomplete requests")
|
||||
|
@ -74,12 +69,11 @@ func TestNoticeSaveRefusesIncomplete(t *testing.T) {
|
|||
func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||
|
||||
// verify that a GET request is no bueno
|
||||
u := urlTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
|
||||
_, resp := ts.Client.GetHTML(u.String())
|
||||
u := ts.URLTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
|
||||
_, resp := ts.Client.GetHTML(u)
|
||||
a.Equal(http.StatusMethodNotAllowed, resp.Code, "GET should not be allowed for this route")
|
||||
|
||||
// next up, we verify that a correct POST request actually works:
|
||||
|
@ -89,7 +83,7 @@ func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
|||
language := []string{"pt"}
|
||||
|
||||
formValues := url.Values{"name": []string{roomdb.NoticeNews.String()}, "id": id, "title": title, "content": content, "language": language}
|
||||
resp = ts.Client.PostForm(u.String(), formValues)
|
||||
resp = ts.Client.PostForm(u, formValues)
|
||||
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
||||
}
|
||||
|
||||
|
@ -97,8 +91,7 @@ func TestNoticeAddLanguageOnlyAllowsPost(t *testing.T) {
|
|||
func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||
|
||||
// to test translations we first need to add a notice to the notice mockdb
|
||||
notice := roomdb.Notice{
|
||||
|
@ -110,8 +103,8 @@ func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
|||
// make sure we return a notice when accessing pinned notices (which are the only notices with translations at writing (2021-03-11)
|
||||
ts.PinnedDB.GetReturns(¬ice, nil)
|
||||
|
||||
u := urlTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews.String())
|
||||
html, resp := ts.Client.GetHTML(u.String())
|
||||
u := ts.URLTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews.String())
|
||||
html, resp := ts.Client.GetHTML(u)
|
||||
form := html.Find("form")
|
||||
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
||||
// FormElement defaults to input if tag omitted
|
||||
|
@ -125,8 +118,7 @@ func TestNoticeDraftLanguageIncludesAllFields(t *testing.T) {
|
|||
func TestNoticeEditFormIncludesAllFields(t *testing.T) {
|
||||
ts := newSession(t)
|
||||
a := assert.New(t)
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
// instantiate the ts.URLTo helper (constructs urls for us!)
|
||||
|
||||
// Create mock notice data to operate on
|
||||
notice := roomdb.Notice{
|
||||
|
@ -137,8 +129,8 @@ func TestNoticeEditFormIncludesAllFields(t *testing.T) {
|
|||
}
|
||||
ts.NoticeDB.GetByIDReturns(notice, nil)
|
||||
|
||||
u := urlTo(router.AdminNoticeEdit, "id", 1)
|
||||
html, resp := ts.Client.GetHTML(u.String())
|
||||
u := ts.URLTo(router.AdminNoticeEdit, "id", 1)
|
||||
html, resp := ts.Client.GetHTML(u)
|
||||
form := html.Find("form")
|
||||
|
||||
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
|
||||
|
|
|
@ -4,30 +4,41 @@ package admin
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/pkg/errors"
|
||||
"go.mindeco.de/http/render"
|
||||
"go.mindeco.de/http/tester"
|
||||
"go.mindeco.de/logging/logtest"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/randutil"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
weberrs "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n/i18ntesting"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/members"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
)
|
||||
|
||||
type testSession struct {
|
||||
Domain string
|
||||
Mux *http.ServeMux
|
||||
|
||||
Client *tester.Tester
|
||||
Router *mux.Router
|
||||
|
||||
URLTo web.URLMaker
|
||||
|
||||
AliasesDB *mockdb.FakeAliasesService
|
||||
ConfigDB *mockdb.FakeRoomConfig
|
||||
|
@ -39,8 +50,6 @@ type testSession struct {
|
|||
|
||||
User roomdb.Member
|
||||
|
||||
Domain string
|
||||
|
||||
RoomState *roomstate.Manager
|
||||
}
|
||||
|
||||
|
@ -62,49 +71,78 @@ func newSession(t *testing.T) *testSession {
|
|||
ctx := context.TODO()
|
||||
ts.RoomState = roomstate.NewManager(ctx, log)
|
||||
|
||||
ts.Router = router.CompleteApp()
|
||||
|
||||
ts.Domain = randutil.String(10)
|
||||
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
// the cookiejar in our custom http/tester needs a non-empty domain and scheme
|
||||
router := router.CompleteApp()
|
||||
urlTo := web.NewURLTo(router)
|
||||
ts.URLTo = func(name string, vals ...interface{}) *url.URL {
|
||||
testURL := urlTo(name, vals...)
|
||||
if testURL == nil {
|
||||
t.Fatalf("no URL for %s", name)
|
||||
}
|
||||
testURL.Host = ts.Domain
|
||||
testURL.Scheme = "https" // fake
|
||||
return testURL
|
||||
}
|
||||
|
||||
// fake user
|
||||
ts.User = roomdb.Member{
|
||||
ID: 1234,
|
||||
Role: roomdb.RoleModerator,
|
||||
}
|
||||
|
||||
testPath := filepath.Join("testrun", t.Name())
|
||||
os.RemoveAll(testPath)
|
||||
|
||||
i18ntesting.WriteReplacement(t)
|
||||
|
||||
testRepo := repo.New(testPath)
|
||||
locHelper, err := i18n.New(testRepo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
authKey := make([]byte, 64)
|
||||
rand.Read(authKey)
|
||||
encKey := make([]byte, 32)
|
||||
rand.Read(encKey)
|
||||
|
||||
sessionsPath := filepath.Join(testPath, "sessions")
|
||||
os.MkdirAll(sessionsPath, 0700)
|
||||
fsStore := sessions.NewFilesystemStore(sessionsPath, authKey, encKey)
|
||||
|
||||
flashHelper := weberrs.NewFlashHelper(fsStore, locHelper)
|
||||
|
||||
// setup rendering
|
||||
|
||||
// TODO: make testing utils and move these there
|
||||
testFuncs := web.TemplateFuncs(ts.Router)
|
||||
testFuncs["i18n"] = func(msgID string) string { return msgID }
|
||||
testFuncs["i18npl"] = func(msgID string, count int, _ ...interface{}) string {
|
||||
if count == 1 {
|
||||
return msgID + "Singular"
|
||||
}
|
||||
return msgID + "Plural"
|
||||
}
|
||||
testFuncs := web.TemplateFuncs(router)
|
||||
testFuncs["current_page_is"] = func(routeName string) bool { return true }
|
||||
testFuncs["is_logged_in"] = func() *roomdb.Member { return &ts.User }
|
||||
testFuncs["urlToNotice"] = func(name string) string { return "" }
|
||||
testFuncs["relative_time"] = func(when time.Time) string { return humanize.Time(when) }
|
||||
|
||||
r, err := render.New(web.Templates,
|
||||
renderOpts := []render.Option{
|
||||
render.SetLogger(log),
|
||||
render.BaseTemplates("base.tmpl", "menu.tmpl"),
|
||||
render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"),
|
||||
render.AddTemplates(append(HTMLTemplates, "error.tmpl")...),
|
||||
render.ErrorTemplate("error.tmpl"),
|
||||
render.FuncMap(testFuncs),
|
||||
)
|
||||
}
|
||||
renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...)
|
||||
|
||||
r, err := render.New(web.Templates, renderOpts...)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "setup: render init failed"))
|
||||
}
|
||||
|
||||
ts.Mux = http.NewServeMux()
|
||||
|
||||
handler := Handler(
|
||||
ts.Domain,
|
||||
r,
|
||||
ts.RoomState,
|
||||
flashHelper,
|
||||
Databases{
|
||||
Aliases: ts.AliasesDB,
|
||||
Config: ts.ConfigDB,
|
||||
|
@ -118,6 +156,7 @@ func newSession(t *testing.T) *testSession {
|
|||
|
||||
handler = members.MiddlewareForTests(ts.User)(handler)
|
||||
|
||||
ts.Mux = http.NewServeMux()
|
||||
ts.Mux.Handle("/", handler)
|
||||
|
||||
ts.Client = tester.New(ts.Mux, t)
|
||||
|
|
|
@ -32,23 +32,30 @@ func TestAliasResolve(t *testing.T) {
|
|||
}
|
||||
ts.AliasesDB.ResolveReturns(testAlias, nil)
|
||||
|
||||
// to construct the /alias/{name} url we need to bypass urlTo
|
||||
// (which builds ?alias=name)
|
||||
routes := router.CompleteApp()
|
||||
|
||||
// default is HTML
|
||||
htmlURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.Nil(err)
|
||||
|
||||
htmlURL, err := routes.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.NoError(err)
|
||||
|
||||
t.Log("resolving", htmlURL.String())
|
||||
html, resp := ts.Client.GetHTML(htmlURL.String())
|
||||
html, resp := ts.Client.GetHTML(htmlURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
a.Equal(testAlias.Name, html.Find("title").Text())
|
||||
|
||||
// default is HTML
|
||||
jsonURL, err := ts.Router.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.Nil(err)
|
||||
// now as JSON
|
||||
jsonURL, err := routes.Get(router.CompleteAliasResolve).URL("alias", testAlias.Name)
|
||||
r.NoError(err)
|
||||
|
||||
q := jsonURL.Query()
|
||||
q.Set("encoding", "json")
|
||||
jsonURL.RawQuery = q.Encode()
|
||||
t.Log("resolving", jsonURL.String())
|
||||
resp = ts.Client.GetBody(jsonURL.String())
|
||||
resp = ts.Client.GetBody(jsonURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
var ar aliasJSONResponse
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"image/color"
|
||||
|
@ -223,9 +224,8 @@ func (h WithSSBHandler) DecideMethod(w http.ResponseWriter, req *http.Request) {
|
|||
|
||||
_, err := h.membersdb.GetByFeed(req.Context(), *cid)
|
||||
if err != nil {
|
||||
if err == roomdb.ErrNotFound {
|
||||
errMsg := fmt.Errorf("ssb http auth: client isn't a member: %w", err)
|
||||
h.render.Error(w, req, http.StatusForbidden, errMsg)
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
h.render.Error(w, req, http.StatusForbidden, weberrors.ErrForbidden{Details: err})
|
||||
return
|
||||
}
|
||||
h.render.Error(w, req, http.StatusInternalServerError, err)
|
||||
|
@ -273,11 +273,11 @@ func (h WithSSBHandler) clientInitiated(w http.ResponseWriter, req *http.Request
|
|||
// check that we have that member
|
||||
member, err := h.membersdb.GetByFeed(req.Context(), client)
|
||||
if err != nil {
|
||||
if errors.Is(err, roomdb.ErrNotFound) {
|
||||
errMsg := fmt.Errorf("ssb http auth: client isn't a member: %w", err)
|
||||
if err == roomdb.ErrNotFound {
|
||||
return weberrors.ErrForbidden{Details: errMsg}
|
||||
}
|
||||
return errMsg
|
||||
return err
|
||||
}
|
||||
payload.ClientID = client
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -21,7 +20,6 @@ import (
|
|||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/signinwithssb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
|
@ -29,30 +27,32 @@ import (
|
|||
|
||||
func TestRestricted(t *testing.T) {
|
||||
ts := setup(t)
|
||||
|
||||
a := assert.New(t)
|
||||
|
||||
testURLs := []string{
|
||||
"/admin/admin",
|
||||
"/admin/admin/",
|
||||
"/admin/",
|
||||
"/admin/anything/",
|
||||
}
|
||||
|
||||
for _, turl := range testURLs {
|
||||
for _, tstr := range testURLs {
|
||||
turl, err := url.Parse(tstr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
html, resp := ts.Client.GetHTML(turl)
|
||||
a.Equal(http.StatusUnauthorized, resp.Code, "wrong HTTP status code for %q", turl)
|
||||
a.Equal(http.StatusForbidden, resp.Code, "wrong HTTP status code for %q", turl)
|
||||
found := html.Find("h1").Text()
|
||||
a.Equal("Error #401 - Unauthorized", found, "wrong error message code for %q", turl)
|
||||
a.Equal("Error #403 - Forbidden", found, "wrong error message code for %q", turl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginForm(t *testing.T) {
|
||||
ts := setup(t)
|
||||
a := assert.New(t)
|
||||
|
||||
a, r := assert.New(t), require.New(t)
|
||||
url := ts.URLTo(router.AuthFallbackLogin)
|
||||
|
||||
url, err := ts.Router.Get(router.AuthFallbackLogin).URL()
|
||||
r.Nil(err)
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
html, resp := ts.Client.GetHTML(url)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -63,24 +63,15 @@ func TestLoginForm(t *testing.T) {
|
|||
|
||||
func TestFallbackAuth(t *testing.T) {
|
||||
ts := setup(t)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
a := assert.New(t)
|
||||
|
||||
// very cheap "browser" client session
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
signInFormURL := ts.URLTo(router.AuthFallbackLogin)
|
||||
|
||||
signInFormURL, err := ts.Router.Get(router.AuthFallbackLogin).URL()
|
||||
r.Nil(err)
|
||||
signInFormURL.Host = "localhost"
|
||||
signInFormURL.Scheme = "https"
|
||||
|
||||
doc, resp := ts.Client.GetHTML(signInFormURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInFormURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
csrfCookie := resp.Result().Cookies()
|
||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
||||
|
||||
jar.SetCookies(signInFormURL, csrfCookie)
|
||||
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
||||
|
||||
passwordForm := doc.Find("#password-fallback")
|
||||
webassert.CSRFTokenPresent(t, passwordForm)
|
||||
|
@ -102,53 +93,21 @@ func TestFallbackAuth(t *testing.T) {
|
|||
}
|
||||
ts.AuthFallbackDB.CheckReturns(int64(23), nil)
|
||||
|
||||
signInURL, err := ts.Router.Get(router.AuthFallbackFinalize).URL()
|
||||
r.Nil(err)
|
||||
signInURL := ts.URLTo(router.AuthFallbackFinalize)
|
||||
|
||||
signInURL.Host = "localhost"
|
||||
signInURL.Scheme = "https"
|
||||
|
||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
||||
var csrfCookieHeader = make(http.Header)
|
||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
||||
cs := jar.Cookies(signInURL)
|
||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
||||
theCookie := cs[0].String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
csrfCookieHeader.Set("Cookie", theCookie)
|
||||
ts.Client.SetHeaders(csrfCookieHeader)
|
||||
|
||||
resp = ts.Client.PostForm(signInURL.String(), loginVals)
|
||||
resp = ts.Client.PostForm(signInURL, loginVals)
|
||||
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
||||
|
||||
a.Equal(1, ts.AuthFallbackDB.CheckCallCount())
|
||||
|
||||
sessionCookie := resp.Result().Cookies()
|
||||
jar.SetCookies(signInURL, sessionCookie)
|
||||
|
||||
// now request the protected dashboard page
|
||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
||||
r.Nil(err)
|
||||
dashboardURL.Host = "localhost"
|
||||
dashboardURL.Scheme = "https"
|
||||
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||
|
||||
var sessionHeader = http.Header(map[string][]string{})
|
||||
cs = jar.Cookies(dashboardURL)
|
||||
// TODO: why doesnt this return the csrf cookie?!
|
||||
// r.Len(cs, 2, "expecting one cookie!")
|
||||
for _, c := range cs {
|
||||
theCookie := c.String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
sessionHeader.Add("Cookie", theCookie)
|
||||
}
|
||||
|
||||
durl := dashboardURL.String()
|
||||
t.Log(durl)
|
||||
|
||||
// update headers
|
||||
ts.Client.ClearHeaders()
|
||||
ts.Client.SetHeaders(sessionHeader)
|
||||
|
||||
html, resp := ts.Client.GetHTML(durl)
|
||||
html, resp := ts.Client.GetHTML(dashboardURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -160,7 +119,7 @@ func TestFallbackAuth(t *testing.T) {
|
|||
testRef := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{0}, 16)}
|
||||
ts.RoomState.AddEndpoint(testRef, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML(durl)
|
||||
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -171,7 +130,7 @@ func TestFallbackAuth(t *testing.T) {
|
|||
testRef2 := refs.FeedRef{Algo: "test", ID: bytes.Repeat([]byte{1}, 16)}
|
||||
ts.RoomState.AddEndpoint(testRef2, nil)
|
||||
|
||||
html, resp = ts.Client.GetHTML(durl)
|
||||
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
|
@ -192,17 +151,13 @@ func TestAuthWithSSBClientInitNotConnected(t *testing.T) {
|
|||
|
||||
cc := signinwithssb.GenerateChallenge()
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
||||
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||
"cid", client.Feed.Ref(),
|
||||
"cc", cc,
|
||||
)
|
||||
r.NotNil(signInStartURL)
|
||||
|
||||
t.Log(signInStartURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
||||
a.Equal(http.StatusInternalServerError, resp.Code) // TODO: StatusForbidden
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||
a.Equal(http.StatusForbidden, resp.Code)
|
||||
|
||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||
// {"#welcome", "AuthWithSSBWelcome"},
|
||||
|
@ -223,17 +178,15 @@ func TestAuthWithSSBClientInitNotAllowed(t *testing.T) {
|
|||
|
||||
cc := signinwithssb.GenerateChallenge()
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
||||
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||
"cid", client.Feed.Ref(),
|
||||
"cc", cc,
|
||||
)
|
||||
r.NotNil(signInStartURL)
|
||||
|
||||
t.Log(signInStartURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||
a.Equal(http.StatusForbidden, resp.Code)
|
||||
t.Log(resp.Body.String())
|
||||
|
||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||
// {"#welcome", "AuthWithSSBWelcome"},
|
||||
|
@ -278,10 +231,6 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
|||
ts := setup(t)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
|
||||
// very cheap "browser" client session
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
|
||||
// the request to be signed later
|
||||
var payload signinwithssb.ClientPayload
|
||||
payload.ServerID = ts.NetworkInfo.RoomID
|
||||
|
@ -339,8 +288,8 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
|||
payload.ClientChallenge = cc
|
||||
|
||||
// prepare the url
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
||||
|
||||
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||
"cid", client.Feed.Ref(),
|
||||
"cc", cc,
|
||||
)
|
||||
|
@ -350,11 +299,10 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
|||
r.NotNil(signInStartURL)
|
||||
|
||||
t.Log(signInStartURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL.String())
|
||||
doc, resp := ts.Client.GetHTML(signInStartURL)
|
||||
a.Equal(http.StatusTemporaryRedirect, resp.Code)
|
||||
|
||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
||||
r.Nil(err)
|
||||
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||
a.Equal(dashboardURL.Path, resp.Header().Get("Location"))
|
||||
|
||||
webassert.Localized(t, doc, []webassert.LocalizedElement{
|
||||
|
@ -373,31 +321,8 @@ func TestAuthWithSSBClientInitHasClient(t *testing.T) {
|
|||
// check that we have a new cookie
|
||||
sessionCookie := resp.Result().Cookies()
|
||||
r.True(len(sessionCookie) > 0, "expecting one cookie!")
|
||||
jar.SetCookies(signInStartURL, sessionCookie)
|
||||
|
||||
// now request the protected dashboard page
|
||||
dashboardURL.Host = "localhost"
|
||||
dashboardURL.Scheme = "https"
|
||||
|
||||
// load the cookie for the dashboard
|
||||
cs := jar.Cookies(dashboardURL)
|
||||
r.True(len(cs) > 0, "expecting one cookie!")
|
||||
|
||||
var sessionHeader = http.Header(map[string][]string{})
|
||||
for _, c := range cs {
|
||||
theCookie := c.String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
sessionHeader.Add("Cookie", theCookie)
|
||||
}
|
||||
|
||||
durl := dashboardURL.String()
|
||||
t.Log(durl)
|
||||
|
||||
// update headers
|
||||
ts.Client.ClearHeaders()
|
||||
ts.Client.SetHeaders(sessionHeader)
|
||||
|
||||
html, resp := ts.Client.GetHTML(durl)
|
||||
html, resp := ts.Client.GetHTML(dashboardURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -421,13 +346,13 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
|||
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
||||
|
||||
// prepare the url
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
||||
|
||||
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||
"cid", client.Feed.Ref(),
|
||||
)
|
||||
r.NotNil(signInStartURL)
|
||||
|
||||
html, resp := ts.Client.GetHTML(signInStartURL.String())
|
||||
html, resp := ts.Client.GetHTML(signInStartURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -476,8 +401,8 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
|||
}()
|
||||
|
||||
// start reading sse
|
||||
sseURL := urlTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||
resp = ts.Client.GetBody(sseURL.String())
|
||||
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||
resp = ts.Client.GetBody(sseURL)
|
||||
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
||||
|
||||
// check contents of sse channel
|
||||
|
@ -492,38 +417,14 @@ func TestAuthWithSSBServerInitHappyPath(t *testing.T) {
|
|||
|
||||
// use the token and go to /withssb/finalize and get a cookie
|
||||
// (this happens in the browser engine via login-events.js)
|
||||
finalizeURL := urlTo(router.AuthWithSSBFinalize, "token", testToken)
|
||||
finalizeURL.Host = "localhost"
|
||||
finalizeURL.Scheme = "https"
|
||||
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", testToken)
|
||||
|
||||
resp = ts.Client.GetBody(finalizeURL.String())
|
||||
|
||||
csrfCookie := resp.Result().Cookies()
|
||||
a.Len(csrfCookie, 2, "csrf and session cookie")
|
||||
|
||||
// very cheap "browser" client session
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
jar.SetCookies(finalizeURL, csrfCookie)
|
||||
resp = ts.Client.GetBody(finalizeURL)
|
||||
|
||||
// now request the protected dashboard page
|
||||
dashboardURL, err := ts.Router.Get(router.AdminDashboard).URL()
|
||||
r.Nil(err)
|
||||
dashboardURL.Host = "localhost"
|
||||
dashboardURL.Scheme = "https"
|
||||
dashboardURL := ts.URLTo(router.AdminDashboard)
|
||||
|
||||
// load the cookie for the dashboard
|
||||
cs := jar.Cookies(dashboardURL)
|
||||
r.True(len(cs) > 0, "expecting one cookie!")
|
||||
var sessionHeader = http.Header(map[string][]string{})
|
||||
for _, c := range cs {
|
||||
theCookie := c.String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
sessionHeader.Add("Cookie", theCookie)
|
||||
}
|
||||
ts.Client.SetHeaders(sessionHeader)
|
||||
|
||||
html, resp = ts.Client.GetHTML(dashboardURL.String())
|
||||
html, resp = ts.Client.GetHTML(dashboardURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -547,13 +448,12 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
|||
ts.MembersDB.GetByFeedReturns(testMember, nil)
|
||||
|
||||
// prepare the url
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
signInStartURL := urlTo(router.AuthWithSSBLogin,
|
||||
signInStartURL := ts.URLTo(router.AuthWithSSBLogin,
|
||||
"cid", client.Feed.Ref(),
|
||||
)
|
||||
r.NotNil(signInStartURL)
|
||||
|
||||
html, resp := ts.Client.GetHTML(signInStartURL.String())
|
||||
html, resp := ts.Client.GetHTML(signInStartURL)
|
||||
if !a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for dashboard") {
|
||||
t.Log(html.Find("body").Text())
|
||||
}
|
||||
|
@ -571,8 +471,8 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
|||
}()
|
||||
|
||||
// start reading sse
|
||||
sseURL := urlTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||
resp = ts.Client.GetBody(sseURL.String())
|
||||
sseURL := ts.URLTo(router.AuthWithSSBServerEvents, "sc", serverChallenge)
|
||||
resp = ts.Client.GetBody(sseURL)
|
||||
a.Equal(http.StatusOK, resp.Result().StatusCode)
|
||||
|
||||
// check contents of sse channel
|
||||
|
@ -585,7 +485,7 @@ func TestAuthWithSSBServerInitWrongSolution(t *testing.T) {
|
|||
a.True(strings.Contains(sseBody, "event: failed\n"), "success event")
|
||||
|
||||
// use an invalid token
|
||||
finalizeURL := urlTo(router.AuthWithSSBFinalize, "token", "wrong")
|
||||
resp = ts.Client.GetBody(finalizeURL.String())
|
||||
finalizeURL := ts.URLTo(router.AuthWithSSBFinalize, "token", "wrong")
|
||||
resp = ts.Client.GetBody(finalizeURL)
|
||||
a.Equal(http.StatusForbidden, resp.Result().StatusCode)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ package handlers
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
|
@ -17,11 +17,10 @@ func TestIndex(t *testing.T) {
|
|||
ts := setup(t)
|
||||
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
url, err := ts.Router.Get(router.CompleteIndex).URL()
|
||||
r.Nil(err)
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
url := ts.URLTo(router.CompleteIndex)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
webassert.Localized(t, html, []webassert.LocalizedElement{
|
||||
{"h1", "Default Notice Title"},
|
||||
|
@ -36,11 +35,10 @@ func TestAbout(t *testing.T) {
|
|||
ts := setup(t)
|
||||
|
||||
a := assert.New(t)
|
||||
r := require.New(t)
|
||||
|
||||
url, err := ts.Router.Get(router.CompleteAbout).URL()
|
||||
r.Nil(err)
|
||||
html, resp := ts.Client.GetHTML(url.String())
|
||||
url := ts.URLTo(router.CompleteAbout)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code")
|
||||
found := html.Find("h1").Text()
|
||||
a.Equal("The about page", found)
|
||||
|
@ -51,7 +49,10 @@ func TestNotFound(t *testing.T) {
|
|||
|
||||
a := assert.New(t)
|
||||
|
||||
html, resp := ts.Client.GetHTML("/some/random/ASDKLANZXC")
|
||||
url404, err := url.Parse("/some/random/ASDKLANZXC")
|
||||
a.NoError(err)
|
||||
|
||||
html, resp := ts.Client.GetHTML(url404)
|
||||
a.Equal(http.StatusNotFound, resp.Code, "wrong HTTP status code")
|
||||
found := html.Find("h1").Text()
|
||||
a.Equal("Error #404 - Not Found", found)
|
||||
|
|
|
@ -85,26 +85,14 @@ func New(
|
|||
roomsAuth.HTMLTemplates,
|
||||
admin.HTMLTemplates,
|
||||
)
|
||||
allTheTemplates = append(allTheTemplates, "error.tmpl")
|
||||
|
||||
r, err := render.New(web.Templates,
|
||||
renderOpts := []render.Option{
|
||||
render.SetLogger(logger),
|
||||
render.BaseTemplates("base.tmpl", "menu.tmpl"),
|
||||
render.BaseTemplates("base.tmpl", "menu.tmpl", "flashes.tmpl"),
|
||||
render.AddTemplates(allTheTemplates...),
|
||||
// render.ErrorTemplate(),
|
||||
render.SetErrorHandler(eh.Handle),
|
||||
render.FuncMap(web.TemplateFuncs(m)),
|
||||
|
||||
// TODO: move these to the i18n helper pkg
|
||||
render.InjectTemplateFunc("i18npl", func(r *http.Request) interface{} {
|
||||
loc := i18n.LocalizerFromRequest(locHelper, r)
|
||||
return loc.LocalizePlurals
|
||||
}),
|
||||
render.InjectTemplateFunc("i18n", func(r *http.Request) interface{} {
|
||||
loc := i18n.LocalizerFromRequest(locHelper, r)
|
||||
return loc.LocalizeSimple
|
||||
}),
|
||||
|
||||
render.InjectTemplateFunc("current_page_is", func(r *http.Request) interface{} {
|
||||
return func(routeName string) bool {
|
||||
route := m.Get(routeName)
|
||||
|
@ -146,7 +134,10 @@ func New(
|
|||
}),
|
||||
|
||||
render.InjectTemplateFunc("is_logged_in", members.TemplateHelper()),
|
||||
)
|
||||
}
|
||||
renderOpts = append(renderOpts, locHelper.GetRenderFuncs()...)
|
||||
|
||||
r, err := render.New(web.Templates, renderOpts...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("web Handler: failed to create renderer: %w", err)
|
||||
}
|
||||
|
@ -165,6 +156,8 @@ func New(
|
|||
},
|
||||
}
|
||||
|
||||
flashHelper := weberrs.NewFlashHelper(cookieStore, locHelper)
|
||||
|
||||
authWithPassword, err := auth.NewHandler(dbs.AuthFallback,
|
||||
auth.SetStore(cookieStore),
|
||||
auth.SetErrorHandler(func(rw http.ResponseWriter, req *http.Request, err error, code int) {
|
||||
|
@ -238,6 +231,7 @@ func New(
|
|||
netInfo.Domain,
|
||||
r,
|
||||
roomState,
|
||||
flashHelper,
|
||||
admin.Databases{
|
||||
Aliases: dbs.Aliases,
|
||||
Config: dbs.Config,
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -14,7 +13,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/webassert"
|
||||
|
@ -24,22 +22,18 @@ import (
|
|||
func TestInviteShowAcceptForm(t *testing.T) {
|
||||
ts := setup(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
t.Run("token doesnt exist", func(t *testing.T) {
|
||||
a, r := assert.New(t), require.New(t)
|
||||
|
||||
testToken := "nonexistant-test-token"
|
||||
acceptURL404 := urlTo(router.CompleteInviteFacade, "token", testToken)
|
||||
acceptURL404 := ts.URLTo(router.CompleteInviteFacade, "token", testToken)
|
||||
r.NotNil(acceptURL404)
|
||||
|
||||
// prep the mocked db for http:404
|
||||
ts.InvitesDB.GetByTokenReturns(roomdb.Invite{}, roomdb.ErrNotFound)
|
||||
|
||||
// request the form
|
||||
acceptForm := acceptURL404.String()
|
||||
t.Log(acceptForm)
|
||||
doc, resp := ts.Client.GetHTML(acceptForm)
|
||||
doc, resp := ts.Client.GetHTML(acceptURL404)
|
||||
// 500 until https://github.com/ssb-ngi-pointer/go-ssb-room/issues/66 is fixed
|
||||
a.Equal(http.StatusInternalServerError, resp.Code)
|
||||
|
||||
|
@ -62,17 +56,14 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
|||
a, r := assert.New(t), require.New(t)
|
||||
|
||||
testToken := "existing-test-token"
|
||||
facadeURL := urlTo(router.CompleteInviteFacade, "token", testToken)
|
||||
r.NotNil(facadeURL)
|
||||
validAcceptURL := ts.URLTo(router.CompleteInviteFacade, "token", testToken)
|
||||
|
||||
// prep the mocked db for http:200
|
||||
fakeExistingInvite := roomdb.Invite{ID: 1234}
|
||||
ts.InvitesDB.GetByTokenReturns(fakeExistingInvite, nil)
|
||||
|
||||
// request the form
|
||||
validAcceptForm := facadeURL.String()
|
||||
t.Log(validAcceptForm)
|
||||
doc, resp := ts.Client.GetHTML(validAcceptForm)
|
||||
doc, resp := ts.Client.GetHTML(validAcceptURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
// check database calls
|
||||
|
@ -91,9 +82,10 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
|||
a.True(ok)
|
||||
|
||||
// Fallback URL in data-href-fallback
|
||||
fallbackURL := urlTo(router.CompleteInviteFacadeFallback, "token", testToken)
|
||||
fallbackURL := ts.URLTo(router.CompleteInviteFacadeFallback, "token", testToken)
|
||||
want := fallbackURL.Path + "?" + fallbackURL.RawQuery
|
||||
joinDataHrefFallback, ok := doc.Find("#join-room-uri").Attr("data-href-fallback")
|
||||
a.Equal(fallbackURL.String(), joinDataHrefFallback)
|
||||
a.Equal(want, joinDataHrefFallback)
|
||||
a.True(ok)
|
||||
|
||||
// ssb-uri in data-href
|
||||
|
@ -112,10 +104,7 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
|||
a.Equal(testToken, inviteParam)
|
||||
|
||||
postTo := params.Get("postTo")
|
||||
expectedConsumeInviteURL, err := ts.Router.Get(router.CompleteInviteConsume).URL()
|
||||
expectedConsumeInviteURL.Scheme = "https"
|
||||
expectedConsumeInviteURL.Host = "localhost"
|
||||
r.NoError(err)
|
||||
expectedConsumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||
a.Equal(expectedConsumeInviteURL.String(), postTo)
|
||||
})
|
||||
}
|
||||
|
@ -123,41 +112,30 @@ func TestInviteShowAcceptForm(t *testing.T) {
|
|||
func TestInviteConsumeInviteHTTP(t *testing.T) {
|
||||
ts := setup(t)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
testToken := "existing-test-token-2"
|
||||
validAcceptURL := urlTo(router.CompleteInviteInsertID, "token", testToken)
|
||||
r.NotNil(validAcceptURL)
|
||||
validAcceptURL.Host = "localhost"
|
||||
validAcceptURL.Scheme = "https"
|
||||
|
||||
validAcceptURL := ts.URLTo(router.CompleteInviteInsertID, "token", testToken)
|
||||
testInvite := roomdb.Invite{ID: 4321}
|
||||
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
||||
|
||||
// request the form (for a valid csrf token)
|
||||
validAcceptForm := validAcceptURL.String()
|
||||
t.Log(validAcceptForm)
|
||||
doc, resp := ts.Client.GetHTML(validAcceptForm)
|
||||
doc, resp := ts.Client.GetHTML(validAcceptURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
form := doc.Find("form#consume")
|
||||
r.Equal(1, form.Length())
|
||||
|
||||
consumeInviteURLString, has := form.Attr("action")
|
||||
a.True(has, "form should have an action attribute")
|
||||
expectedConsumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||
a.Equal(expectedConsumeInviteURL.Path, consumeInviteURLString)
|
||||
|
||||
webassert.CSRFTokenPresent(t, form)
|
||||
webassert.ElementsInForm(t, form, []webassert.FormElement{
|
||||
{Name: "invite", Type: "hidden", Value: testToken},
|
||||
{Name: "id", Type: "text"},
|
||||
})
|
||||
|
||||
// we need a functional jar to unpack the Set-Cookie response for the csrf token
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
|
||||
// update the jar
|
||||
csrfCookie := resp.Result().Cookies()
|
||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
||||
jar.SetCookies(validAcceptURL, csrfCookie)
|
||||
|
||||
// get the corresponding token from the page
|
||||
csrfTokenElem := doc.Find("input[name='gorilla.csrf.Token']")
|
||||
a.Equal(1, csrfTokenElem.Length())
|
||||
|
@ -179,31 +157,18 @@ func TestInviteConsumeInviteHTTP(t *testing.T) {
|
|||
}
|
||||
|
||||
// construct the consume endpoint url
|
||||
consumeInviteURLString, has := form.Attr("action")
|
||||
a.True(has, "form should have an action attribute")
|
||||
expectedConsumeInviteURL, err := ts.Router.Get(router.CompleteInviteConsume).URL()
|
||||
r.Nil(err)
|
||||
a.Equal(expectedConsumeInviteURL.String(), consumeInviteURLString)
|
||||
consumeInviteURL, err := url.Parse(consumeInviteURLString)
|
||||
r.Nil(err)
|
||||
consumeInviteURL.Host = "localhost"
|
||||
consumeInviteURL.Scheme = "https"
|
||||
consumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||
|
||||
// construct the header with Referer and Cookie
|
||||
// construct the header with the Referer or csrf check
|
||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
||||
cs := jar.Cookies(consumeInviteURL)
|
||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
||||
theCookie := cs[0].String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
csrfCookieHeader.Set("Cookie", theCookie)
|
||||
ts.Client.SetHeaders(csrfCookieHeader)
|
||||
|
||||
// prepare the mock
|
||||
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
||||
|
||||
// send the POST
|
||||
resp = ts.Client.PostForm(consumeInviteURL.String(), consumeVals)
|
||||
resp = ts.Client.PostForm(consumeInviteURL, consumeVals)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
||||
|
||||
// check how consume was called
|
||||
|
@ -216,11 +181,8 @@ func TestInviteConsumeInviteHTTP(t *testing.T) {
|
|||
func TestInviteConsumeInviteJSON(t *testing.T) {
|
||||
ts := setup(t)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
|
||||
testToken := "existing-test-token-2"
|
||||
validAcceptURL := urlTo(router.CompleteInviteFacade, "token", testToken)
|
||||
r.NotNil(validAcceptURL)
|
||||
|
||||
testInvite := roomdb.Invite{ID: 4321}
|
||||
ts.InvitesDB.GetByTokenReturns(testInvite, nil)
|
||||
|
@ -236,14 +198,14 @@ func TestInviteConsumeInviteJSON(t *testing.T) {
|
|||
consume.ID = testNewMember
|
||||
|
||||
// construct the consume endpoint url
|
||||
consumeInviteURL := urlTo(router.CompleteInviteConsume)
|
||||
consumeInviteURL := ts.URLTo(router.CompleteInviteConsume)
|
||||
r.NotNil(consumeInviteURL)
|
||||
|
||||
// prepare the mock
|
||||
ts.InvitesDB.ConsumeReturns(testInvite, nil)
|
||||
|
||||
// send the POST
|
||||
resp := ts.Client.SendJSON(consumeInviteURL.String(), consume)
|
||||
resp := ts.Client.SendJSON(consumeInviteURL, consume)
|
||||
a.Equal(http.StatusOK, resp.Code, "wrong HTTP status code for sign in")
|
||||
|
||||
// check how consume was called
|
||||
|
|
|
@ -2,15 +2,12 @@ package handlers
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestNoticeSmokeTest ensures the most basic notice serving is working
|
||||
|
@ -25,7 +22,8 @@ func TestNoticeSmokeTest(t *testing.T) {
|
|||
|
||||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||
|
||||
html, res := ts.Client.GetHTML("/notice/show?id=1")
|
||||
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", "1")
|
||||
html, res := ts.Client.GetHTML(noticeURL)
|
||||
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
||||
a.Equal("Welcome!", html.Find("title").Text())
|
||||
}
|
||||
|
@ -47,7 +45,8 @@ Hello world!
|
|||
|
||||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||
|
||||
html, res := ts.Client.GetHTML("/notice/show?id=1")
|
||||
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", "1")
|
||||
html, res := ts.Client.GetHTML(noticeURL)
|
||||
a.Equal(http.StatusOK, res.Code, "wrong HTTP status code")
|
||||
a.Equal("Welcome!", html.Find("title").Text())
|
||||
a.Equal("The loveliest of rooms is here", html.Find("h2").Text())
|
||||
|
@ -57,9 +56,7 @@ Hello world!
|
|||
// then we log in as an admin and see that the edit links are there.
|
||||
func TestNoticesEditButtonVisible(t *testing.T) {
|
||||
ts := setup(t)
|
||||
a, r := assert.New(t), require.New(t)
|
||||
|
||||
urlTo := web.NewURLTo(ts.Router)
|
||||
a := assert.New(t)
|
||||
|
||||
ts.AliasesDB.ResolveReturns(roomdb.Alias{}, roomdb.ErrNotFound)
|
||||
|
||||
|
@ -71,12 +68,11 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
|||
ts.NoticeDB.GetByIDReturns(noticeData, nil)
|
||||
|
||||
// first, we confirm that the button is missing when not logged in
|
||||
noticeURL := urlTo(router.CompleteNoticeShow, "id", 42)
|
||||
noticeURL.Host = "localhost"
|
||||
noticeURL.Scheme = "https"
|
||||
noticeURL := ts.URLTo(router.CompleteNoticeShow, "id", 42)
|
||||
|
||||
editButtonSelector := `#edit-notice`
|
||||
|
||||
doc, resp := ts.Client.GetHTML(noticeURL.String())
|
||||
doc, resp := ts.Client.GetHTML(noticeURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
// empty selection <=> we have no link
|
||||
|
@ -84,25 +80,17 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
|||
|
||||
// start preparing the ~login dance~
|
||||
// TODO: make this code reusable and share it with the login => /dashboard http:200 test
|
||||
// cookiejar: a very cheap client session
|
||||
// TODO: refactor login dance for re-use in testing / across tests
|
||||
jar, err := cookiejar.New(nil)
|
||||
r.NoError(err)
|
||||
|
||||
// when dealing with cookies we also need to have an Host and URL-Scheme
|
||||
// for the jar to save and load them correctly
|
||||
formEndpoint := urlTo(router.AuthFallbackLogin)
|
||||
r.NotNil(formEndpoint)
|
||||
formEndpoint.Host = "localhost"
|
||||
formEndpoint.Scheme = "https"
|
||||
formEndpoint := ts.URLTo(router.AuthFallbackLogin)
|
||||
|
||||
doc, resp = ts.Client.GetHTML(formEndpoint.String())
|
||||
doc, resp = ts.Client.GetHTML(formEndpoint)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
|
||||
csrfCookie := resp.Result().Cookies()
|
||||
a.Len(csrfCookie, 1, "should have one cookie for CSRF protection validation")
|
||||
t.Log(csrfCookie)
|
||||
jar.SetCookies(formEndpoint, csrfCookie)
|
||||
a.True(len(csrfCookie) > 0, "should have one cookie for CSRF protection validation")
|
||||
|
||||
csrfTokenElem := doc.Find("input[type=hidden]")
|
||||
a.Equal(1, csrfTokenElem.Length())
|
||||
|
@ -125,44 +113,19 @@ func TestNoticesEditButtonVisible(t *testing.T) {
|
|||
ts.AuthFallbackDB.CheckReturns(testUser.ID, nil)
|
||||
ts.MembersDB.GetByIDReturns(testUser, nil)
|
||||
|
||||
postEndpoint, err := ts.Router.Get(router.AuthFallbackFinalize).URL()
|
||||
r.Nil(err)
|
||||
postEndpoint.Host = "localhost"
|
||||
postEndpoint.Scheme = "https"
|
||||
postEndpoint := ts.URLTo(router.AuthFallbackFinalize)
|
||||
|
||||
// construct HTTP Header with Referer and Cookie
|
||||
var csrfCookieHeader = http.Header(map[string][]string{})
|
||||
csrfCookieHeader.Set("Referer", "https://localhost")
|
||||
cs := jar.Cookies(postEndpoint)
|
||||
r.Len(cs, 1, "expecting one cookie for csrf")
|
||||
theCookie := cs[0].String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
csrfCookieHeader.Set("Cookie", theCookie)
|
||||
ts.Client.SetHeaders(csrfCookieHeader)
|
||||
var refererHeader = make(http.Header)
|
||||
refererHeader.Set("Referer", "https://localhost")
|
||||
ts.Client.SetHeaders(refererHeader)
|
||||
|
||||
resp = ts.Client.PostForm(postEndpoint.String(), loginVals)
|
||||
resp = ts.Client.PostForm(postEndpoint, loginVals)
|
||||
a.Equal(http.StatusSeeOther, resp.Code, "wrong HTTP status code for sign in")
|
||||
|
||||
sessionCookie := resp.Result().Cookies()
|
||||
jar.SetCookies(postEndpoint, sessionCookie)
|
||||
|
||||
var sessionHeader = http.Header(map[string][]string{})
|
||||
cs = jar.Cookies(noticeURL)
|
||||
// TODO: why doesnt this return the csrf cookie?!
|
||||
r.NotEqual(len(cs), 0, "expecting a cookie!")
|
||||
for _, c := range cs {
|
||||
theCookie := c.String()
|
||||
a.NotEqual("", theCookie, "should have a new cookie")
|
||||
sessionHeader.Add("Cookie", theCookie)
|
||||
}
|
||||
|
||||
// update headers
|
||||
ts.Client.ClearHeaders()
|
||||
ts.Client.SetHeaders(sessionHeader)
|
||||
|
||||
cnt := ts.MembersDB.GetByIDCallCount()
|
||||
// now we are logged in, anchor tag should be there
|
||||
doc, resp = ts.Client.GetHTML(noticeURL.String())
|
||||
doc, resp = ts.Client.GetHTML(noticeURL)
|
||||
a.Equal(http.StatusOK, resp.Code)
|
||||
a.Equal(cnt+1, ts.MembersDB.GetByIDCallCount())
|
||||
|
||||
|
|
|
@ -5,15 +5,12 @@ package handlers
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/gorilla/mux"
|
||||
"go.mindeco.de/http/tester"
|
||||
"go.mindeco.de/logging/logtest"
|
||||
|
||||
|
@ -24,7 +21,8 @@ import (
|
|||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomdb/mockdb"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/roomstate"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n/i18ntesting"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/router"
|
||||
refs "go.mindeco.de/ssb-refs"
|
||||
)
|
||||
|
@ -32,7 +30,7 @@ import (
|
|||
type testSession struct {
|
||||
Mux *http.ServeMux
|
||||
Client *tester.Tester
|
||||
Router *mux.Router
|
||||
URLTo web.URLMaker
|
||||
|
||||
// mocked dbs
|
||||
AuthDB *mockdb.FakeAuthWithSSBService
|
||||
|
@ -55,8 +53,6 @@ type testSession struct {
|
|||
NetworkInfo network.ServerEndpointDetails
|
||||
}
|
||||
|
||||
var testI18N = justTheKeys()
|
||||
|
||||
func setup(t *testing.T) *testSession {
|
||||
t.Parallel()
|
||||
var ts testSession
|
||||
|
@ -65,12 +61,7 @@ func setup(t *testing.T) *testSession {
|
|||
os.RemoveAll(testRepoPath)
|
||||
testRepo := repo.New(testRepoPath)
|
||||
|
||||
testOverride := filepath.Join(testRepo.GetPath("i18n"), "active.en.toml")
|
||||
os.MkdirAll(filepath.Dir(testOverride), 0700)
|
||||
err := ioutil.WriteFile(testOverride, testI18N, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i18ntesting.WriteReplacement(t)
|
||||
|
||||
ts.AuthDB = new(mockdb.FakeAuthWithSSBService)
|
||||
ts.AuthFallbackDB = new(mockdb.FakeAuthFallbackService)
|
||||
|
@ -107,7 +98,18 @@ func setup(t *testing.T) *testSession {
|
|||
ctx := context.TODO()
|
||||
ts.RoomState = roomstate.NewManager(ctx, log)
|
||||
|
||||
ts.Router = router.CompleteApp()
|
||||
// instantiate the urlTo helper (constructs urls for us!)
|
||||
// the cookiejar in our custom http/tester needs a non-empty domain and scheme
|
||||
urlTo := web.NewURLTo(router.CompleteApp())
|
||||
ts.URLTo = func(name string, vals ...interface{}) *url.URL {
|
||||
testURL := urlTo(name, vals...)
|
||||
if testURL == nil {
|
||||
t.Fatalf("no URL for %s", name)
|
||||
}
|
||||
testURL.Host = ts.NetworkInfo.Domain
|
||||
testURL.Scheme = "https" // fake
|
||||
return testURL
|
||||
}
|
||||
|
||||
ts.SignalBridge = signinwithssb.NewSignalBridge()
|
||||
|
||||
|
@ -140,55 +142,3 @@ func setup(t *testing.T) *testSession {
|
|||
|
||||
return &ts
|
||||
}
|
||||
|
||||
// auto generate from defaults a list of Label = "Label"
|
||||
// must keep order of input intact
|
||||
// (at least all the globals before starting with nested plurals)
|
||||
// also replaces 'one' and 'other' in plurals
|
||||
func justTheKeys() []byte {
|
||||
f, err := i18n.Defaults.Open("active.en.toml")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
justAMap := make(map[string]interface{})
|
||||
md, err := toml.DecodeReader(f, &justAMap)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
// if we don't produce the same order as the input
|
||||
// (in go maps are ALWAYS random access when ranged over)
|
||||
// nested keys (such as plural form) will mess up the global level...
|
||||
for _, k := range md.Keys() {
|
||||
key := k.String()
|
||||
val, has := justAMap[key]
|
||||
if !has {
|
||||
// fmt.Println("i18n test warning:", key, "not unmarshaled")
|
||||
continue
|
||||
}
|
||||
|
||||
switch tv := val.(type) {
|
||||
|
||||
case string:
|
||||
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
||||
|
||||
case map[string]interface{}:
|
||||
// fmt.Println("i18n test warning: custom map for ", key)
|
||||
|
||||
fmt.Fprintf(buf, "\n[%s]\n", key)
|
||||
// replace "one" and "other" keys
|
||||
// with Label and LabelPlural
|
||||
tv["one"] = key + "Singular"
|
||||
tv["other"] = key + "Plural"
|
||||
toml.NewEncoder(buf).Encode(tv)
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled toml structure under %s: %T\n", key, val))
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ Settings = "Settings"
|
|||
AdminDeniedKeysTitle = "Banned"
|
||||
AdminDeniedKeysWelcome = "This page can be used to ban SSB IDs so that they can't access the room any more."
|
||||
AdminDeniedKeysAdd = "Add"
|
||||
AdminDeniedKeysAdded = "Key was added to the list."
|
||||
AdminDeniedKeysRemove = "Remove"
|
||||
AdminDeniedKeysComment = "Comment"
|
||||
AdminDeniedKeysCommentDescription = "The person who added this ban, added the following comment"
|
||||
|
@ -112,6 +113,10 @@ AdminMemberDetailsAliasRevoke = "Revoke"
|
|||
AdminMemberDetailsExclusion = "Exclusion from this room"
|
||||
AdminMemberDetailsRemove = "Remove member"
|
||||
|
||||
AdminMemberAdded = "Member added successfully."
|
||||
AdminMemberUpdated = "Member updated."
|
||||
AdminMemberRemoved = "Member removed."
|
||||
|
||||
# invite dashboard
|
||||
##################
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"go.mindeco.de/http/render"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
|
@ -116,11 +117,26 @@ func New(r repo.Interface) (*Helper, error) {
|
|||
return &Helper{bundle: bundle}, nil
|
||||
}
|
||||
|
||||
func (h Helper) GetRenderFuncs() []render.Option {
|
||||
var opts = []render.Option{
|
||||
render.InjectTemplateFunc("i18npl", func(r *http.Request) interface{} {
|
||||
loc := h.FromRequest(r)
|
||||
return loc.LocalizePlurals
|
||||
}),
|
||||
|
||||
render.InjectTemplateFunc("i18n", func(r *http.Request) interface{} {
|
||||
loc := h.FromRequest(r)
|
||||
return loc.LocalizeSimple
|
||||
}),
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
type Localizer struct {
|
||||
loc *i18n.Localizer
|
||||
}
|
||||
|
||||
func (h Helper) NewLocalizer(lang string, accept ...string) *Localizer {
|
||||
func (h Helper) newLocalizer(lang string, accept ...string) *Localizer {
|
||||
var langs = []string{lang}
|
||||
langs = append(langs, accept...)
|
||||
var l Localizer
|
||||
|
@ -128,6 +144,15 @@ func (h Helper) NewLocalizer(lang string, accept ...string) *Localizer {
|
|||
return &l
|
||||
}
|
||||
|
||||
// FromRequest returns a new Localizer for the passed helper,
|
||||
// using form value 'lang' and Accept-Language http header from the passed request.
|
||||
// TODO: user settings/cookie values?
|
||||
func (h Helper) FromRequest(r *http.Request) *Localizer {
|
||||
lang := r.FormValue("lang")
|
||||
accept := r.Header.Get("Accept-Language")
|
||||
return h.newLocalizer(lang, accept)
|
||||
}
|
||||
|
||||
func (l Localizer) LocalizeSimple(messageID string) string {
|
||||
msg, err := l.loc.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: messageID,
|
||||
|
@ -178,12 +203,3 @@ func (l Localizer) LocalizePluralsWithData(messageID string, pluralCount int, tp
|
|||
|
||||
panic(fmt.Sprintf("i18n/error: failed to localize label %s: %s", messageID, err))
|
||||
}
|
||||
|
||||
// LocalizerFromRequest returns a new Localizer for the passed helper,
|
||||
// using form value 'lang' and Accept-Language http header from the passed request.
|
||||
// TODO: user settings/cookie values?
|
||||
func LocalizerFromRequest(helper *Helper, r *http.Request) *Localizer {
|
||||
lang := r.FormValue("lang")
|
||||
accept := r.Header.Get("Accept-Language")
|
||||
return helper.NewLocalizer(lang, accept)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package i18ntesting
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/internal/repo"
|
||||
"github.com/ssb-ngi-pointer/go-ssb-room/web/i18n"
|
||||
)
|
||||
|
||||
// justTheKeys auto generates from the defaults a list of Label = "Label"
|
||||
// must keep order of input intact
|
||||
// (at least all the globals before starting with nested plurals)
|
||||
// also replaces 'one' and 'other' in plurals
|
||||
func justTheKeys(t *testing.T) []byte {
|
||||
f, err := i18n.Defaults.Open("active.en.toml")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
justAMap := make(map[string]interface{})
|
||||
md, err := toml.DecodeReader(f, &justAMap)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var buf = &bytes.Buffer{}
|
||||
|
||||
// if we don't produce the same order as the input
|
||||
// (in go maps are ALWAYS random access when ranged over)
|
||||
// nested keys (such as plural form) will mess up the global level...
|
||||
for _, k := range md.Keys() {
|
||||
key := k.String()
|
||||
val, has := justAMap[key]
|
||||
if !has {
|
||||
// fmt.Println("i18n test warning:", key, "not unmarshaled")
|
||||
continue
|
||||
}
|
||||
|
||||
switch tv := val.(type) {
|
||||
|
||||
case string:
|
||||
fmt.Fprintf(buf, "%s = \"%s\"\n", key, key)
|
||||
|
||||
case map[string]interface{}:
|
||||
// fmt.Println("i18n test warning: custom map for ", key)
|
||||
|
||||
fmt.Fprintf(buf, "\n[%s]\n", key)
|
||||
// replace "one" and "other" keys
|
||||
// with Label and LabelPlural
|
||||
tv["one"] = key + "Singular"
|
||||
tv["other"] = key + "Plural"
|
||||
toml.NewEncoder(buf).Encode(tv)
|
||||
fmt.Fprintln(buf)
|
||||
|
||||
default:
|
||||
t.Fatalf("unhandled toml structure under %s: %T\n", key, val)
|
||||
}
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func WriteReplacement(t *testing.T) {
|
||||
r := repo.New(filepath.Join("testrun", t.Name()))
|
||||
|
||||
testOverride := filepath.Join(r.GetPath("i18n"), "active.en.toml")
|
||||
t.Log(testOverride)
|
||||
os.MkdirAll(filepath.Dir(testOverride), 0700)
|
||||
|
||||
content := justTheKeys(t)
|
||||
|
||||
err := ioutil.WriteFile(testOverride, content, 0700)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
<p id="welcome" class="my-2">{{i18n "AdminDeniedKeysWelcome"}}</p>
|
||||
|
||||
{{ template "flashes" . }}
|
||||
|
||||
<p
|
||||
id="DeniedKeysCount"
|
||||
class="text-lg font-bold my-2"
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
|
||||
<p id="welcome" class="my-2">{{i18n "AdminMembersWelcome"}}</p>
|
||||
|
||||
{{ template "flashes" . }}
|
||||
|
||||
<form
|
||||
id="add-entry"
|
||||
action="{{urlTo "admin:members:add"}}"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{{ define "flashes" }}
|
||||
{{if .Flashes}}
|
||||
<ul id="flashes-list">
|
||||
{{range .Flashes}}
|
||||
<li
|
||||
class="{{if eq .Kind 1}}text-red-600{{else}}text-green-600{{end}}"
|
||||
>{{.Message}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
{{ end }}
|
|
@ -34,12 +34,14 @@ func TemplateFuncs(m *mux.Router) template.FuncMap {
|
|||
}
|
||||
}
|
||||
|
||||
type URLMaker func(string, ...interface{}) *url.URL
|
||||
|
||||
// NewURLTo returns a template helper function for a router.
|
||||
// It is usually called with one parameter, the route name, which should be defined in the router package.
|
||||
// If it's called with more then one, it has a to be a pair of two values. (1, 3, 5, 7, etc.)
|
||||
// The first value of such a pair is the placeholder name in the router (i.e. in '/our/routes/{id:[0-9]+}/test' it would be id )
|
||||
// and the 2nd value is the actual value that should be put in place of the placeholder.
|
||||
func NewURLTo(appRouter *mux.Router) func(string, ...interface{}) *url.URL {
|
||||
func NewURLTo(appRouter *mux.Router) URLMaker {
|
||||
l := logging.Logger("helper.URLTo") // TOOD: inject in a scoped way
|
||||
return func(routeName string, ps ...interface{}) *url.URL {
|
||||
route := appRouter.Get(routeName)
|
||||
|
|
Loading…
Reference in New Issue