Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for notices view #64

Merged
merged 21 commits into from
Mar 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# vim
.*.sw[a-z]

# the binaries
cmd/server/server
cmd/insert-user/insert-user
Expand All @@ -11,4 +14,4 @@ roomdb/sqlite/testrun
# build artifacts from node.js project web/styles
node_modules
web/assets/styles/compiled.*
web/assets/style.css
web/assets/style.css
5 changes: 4 additions & 1 deletion roomdb/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ import (

// AuthFallbackService might be helpful for scenarios where one lost access to his ssb device or key
type AuthFallbackService interface {

// Check receives the username and password (in clear) and checks them accordingly.
// If it's a valid combination it returns the user ID, or an error if they are not.
auth.Auther

Create(ctx context.Context, user string, password []byte) (int64, error)
Expand Down Expand Up @@ -98,7 +101,7 @@ type InviteService interface {
// PinnedNoticesService allows an admin to assign Notices to specific placeholder pages.
// like updates, privacy policy, code of conduct
type PinnedNoticesService interface {
// List returns a list of all the pinned notices with their corrosponding notices and languges
// List returns a list of all the pinned notices with their corresponding notices and languages
List(context.Context) (PinnedNotices, error)

// Set assigns a fixed page name to an page ID and a language to allow for multiple translated versions of the same page.
Expand Down
2 changes: 2 additions & 0 deletions roomdb/sqlite/auth_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type AuthFallback struct {
db *sql.DB
}

// Check receives the username and password (in clear) and checks them accordingly.
// If it's a valid combination it returns the user ID, or an error if they are not.
func (af AuthFallback) Check(name, password string) (interface{}, error) {
ctx := context.Background()
found, err := models.AuthFallbacks(qm.Where("name = ?", name)).One(ctx, af.db)
Expand Down
5 changes: 2 additions & 3 deletions roomdb/sqlite/notices_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ func TestPinnedNotices(t *testing.T) {
}

for i, tcase := range cases {
desc, has := allTheNotices[tcase.Name]
notices, has := allTheNotices[tcase.Name]
r.True(has, "case %d failed - notice %s not in map", i, tcase.Name)
r.Len(desc, tcase.Count, "case %d failed - wrong number of notices for %s", i, tcase.Name)
r.Len(notices, tcase.Count, "case %d failed - wrong number of notices for %s", i, tcase.Name)
}
})

Expand All @@ -130,7 +130,6 @@ func TestPinnedNotices(t *testing.T) {
notice.Title = "política de privacidad"
notice.Content = "solo una prueba"
notice.Language = "es"

// save the new notice
err = db.Notices.Save(ctx, &notice)
r.NoError(err)
Expand Down
2 changes: 1 addition & 1 deletion web/handlers/admin/aliases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func TestAliasesRevokeConfirmation(t *testing.T) {

a.Equal(addURL.String(), action)

webassert.InputsInForm(t, form, []webassert.InputElement{
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "name", Type: "hidden", Value: testEntry.Name},
})
}
Expand Down
4 changes: 2 additions & 2 deletions web/handlers/admin/allow_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestAllowListAdd(t *testing.T) {

a.Equal(addURL.String(), action)

webassert.InputsInForm(t, formSelection, []webassert.InputElement{
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
{Name: "pub_key", Type: "text"},
})

Expand Down Expand Up @@ -181,7 +181,7 @@ func TestAllowListRemoveConfirmation(t *testing.T) {

a.Equal(addURL.String(), action)

webassert.InputsInForm(t, form, []webassert.InputElement{
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "id", Type: "hidden", Value: "666"},
})
}
Expand Down
3 changes: 2 additions & 1 deletion web/handlers/admin/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ func newSession(t *testing.T) *testSession {
testFuncs["current_page_is"] = func(routeName string) bool {
return true
}
testFuncs["is_logged_in"] = func() *roomdb.User { return nil }

testFuncs["is_logged_in"] = func() *roomdb.User { return ts.User }
testFuncs["urlToNotice"] = func(name string) string { return "" }

r, err := render.New(web.Templates,
Expand Down
2 changes: 1 addition & 1 deletion web/handlers/admin/invites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestInvitesCreateForm(t *testing.T) {

a.Equal(addURL.String(), action)

webassert.InputsInForm(t, formSelection, []webassert.InputElement{
webassert.ElementsInForm(t, formSelection, []webassert.FormElement{
{Name: "alias_suggestion", Type: "text"},
})
}
Expand Down
26 changes: 26 additions & 0 deletions web/handlers/admin/notices.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func (h noticeHandler) addTranslation(rw http.ResponseWriter, req *http.Request)
return
}

// reply with 405 error: Method not allowed
if req.Method != "POST" {
err := weberrors.ErrBadRequest{Where: "http method type", Details: fmt.Errorf("add translation only accepts POST requests, sorry!")}
h.r.Error(rw, req, http.StatusMethodNotAllowed, err)
}

pinnedName := roomdb.PinnedNoticeName(req.FormValue("name"))
if !pinnedName.Valid() {
err := weberrors.ErrBadRequest{Where: "name", Details: fmt.Errorf("invalid pinned notice name")}
Expand All @@ -54,6 +60,11 @@ func (h noticeHandler) addTranslation(rw http.ResponseWriter, req *http.Request)

var n roomdb.Notice
n.Title = req.FormValue("title")
if n.Title == "" {
err = weberrors.ErrBadRequest{Where: "title", Details: fmt.Errorf("title can't be empty")}
h.r.Error(rw, req, http.StatusInternalServerError, err)
return
}

// TODO: validate languages properly
n.Language = req.FormValue("language")
Expand All @@ -64,6 +75,11 @@ func (h noticeHandler) addTranslation(rw http.ResponseWriter, req *http.Request)
}

n.Content = req.FormValue("content")
if n.Content == "" {
err = weberrors.ErrBadRequest{Where: "content", Details: fmt.Errorf("content can't be empty")}
h.r.Error(rw, req, http.StatusInternalServerError, err)
return
}
// https://github.com/russross/blackfriday/issues/575
n.Content = strings.Replace(n.Content, "\r\n", "\n", -1)

Expand Down Expand Up @@ -137,6 +153,11 @@ func (h noticeHandler) save(rw http.ResponseWriter, req *http.Request) {
}

n.Title = req.FormValue("title")
if n.Title == "" {
err = weberrors.ErrBadRequest{Where: "title", Details: fmt.Errorf("title can't be empty")}
h.r.Error(rw, req, http.StatusInternalServerError, err)
return
}

// TODO: validate languages properly
n.Language = req.FormValue("language")
Expand All @@ -147,6 +168,11 @@ func (h noticeHandler) save(rw http.ResponseWriter, req *http.Request) {
}

n.Content = req.FormValue("content")
if n.Content == "" {
err = weberrors.ErrBadRequest{Where: "content", Details: fmt.Errorf("content can't be empty")}
h.r.Error(rw, req, http.StatusInternalServerError, err)
return
}
// https://github.com/russross/blackfriday/issues/575
n.Content = strings.Replace(n.Content, "\r\n", "\n", -1)

Expand Down
153 changes: 153 additions & 0 deletions web/handlers/admin/notices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package admin

import (
"fmt"
"net/http"
"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/ssb-ngi-pointer/go-ssb-room/web/webassert"
"github.com/stretchr/testify/assert"
)

// Verifies that the notice.go save handler is like, actually, called.
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"}
content := []string{"Absolutely Thrilling Content"}
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)
formValues := url.Values{"id": id, "title": title, "content": content, "language": language}
resp := ts.Client.PostForm(u.String(), formValues)
a.Equal(http.StatusSeeOther, resp.Code, "POST should work")
a.Equal(1, ts.NoticeDB.SaveCallCount(), "noticedb should have saved after POST completed")
}

// Verifies that the notices.go:save handler refuses requests missing required parameters
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"}
title := []string{"SSB Breaking News: This Test Is Great"}
content := []string{"Absolutely Thrilling Content"}
language := []string{"pt"}

/* save without id */
u := urlTo(router.AdminNoticeSave)
emptyParams := url.Values{}
resp := ts.Client.PostForm(u.String(), 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)
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)
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)
a.Equal(http.StatusInternalServerError, resp.Code, "saving without language should not work")

a.Equal(0, ts.NoticeDB.SaveCallCount(), "noticedb should never save incomplete requests")
}

// Verifies that /translation/add only accepts POST requests
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)

// verify that a GET request is no bueno
u := urlTo(router.AdminNoticeAddTranslation, "name", roomdb.NoticeNews.String())
_, resp := ts.Client.GetHTML(u.String())
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:
id := []string{"1"}
title := []string{"Bom Dia! SSB Breaking News: This Test Is Great"}
content := []string{"conteúdo muito bom"}
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)
a.Equal(http.StatusTemporaryRedirect, resp.Code)
}

// Verifies that the "add a translation" page contains all the required form fields (id/title/content/language)
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)

// to test translations we first need to add a notice to the notice mockdb
notice := roomdb.Notice{
ID: 1,
Title: "News",
Content: "Breaking News: This Room Has News",
Language: "en-GB",
}
// 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(&notice, nil)

u := urlTo(router.AdminNoticeDraftTranslation, "name", roomdb.NoticeNews.String())
html, resp := ts.Client.GetHTML(u.String())
form := html.Find("form")
a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
// FormElement defaults to input if tag omitted
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "title"},
{Name: "language"},
{Tag: "textarea", Name: "content"},
})
}

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)

// Create mock notice data to operate on
notice := roomdb.Notice{
ID: 1,
Title: "News",
Content: "Breaking News: This Room Has News",
Language: "en-GB",
}
ts.NoticeDB.GetByIDReturns(notice, nil)

u := urlTo(router.AdminNoticeEdit, "id", 1)
html, resp := ts.Client.GetHTML(u.String())
form := html.Find("form")

a.Equal(http.StatusOK, resp.Code, "Wrong HTTP status code")
// check for all the form elements & verify their initial contents are set correctly
// FormElement defaults to input if tag omitted
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "title", Value: notice.Title},
{Name: "language", Value: notice.Language},
{Name: "id", Value: fmt.Sprintf("%d", notice.ID), Type: "hidden"},
{Tag: "textarea", Name: "content"},
})
}
4 changes: 2 additions & 2 deletions web/handlers/invites_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func TestInviteShowAcceptForm(t *testing.T) {

webassert.CSRFTokenPresent(t, form)

webassert.InputsInForm(t, form, []webassert.InputElement{
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "token", Type: "hidden", Value: testToken},
{Name: "alias", Type: "text", Value: fakeExistingInvite.AliasSuggestion},
{Name: "new_member", Type: "text", Placeholder: wantNewMemberPlaceholder},
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestInviteShowAcceptForm(t *testing.T) {
r.Equal(1, form.Length())

webassert.CSRFTokenPresent(t, form)
webassert.InputsInForm(t, form, []webassert.InputElement{
webassert.ElementsInForm(t, form, []webassert.FormElement{
{Name: "token", Type: "hidden", Value: testToken},
{Name: "alias", Type: "text", Placeholder: "[email protected]"},
{Name: "new_member", Type: "text", Placeholder: wantNewMemberPlaceholder},
Expand Down
Loading