package hook_test
import (
"errors"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"git.sr.ht/~evanj/cms/internal/c"
"git.sr.ht/~evanj/cms/internal/c/hook"
spaceT "git.sr.ht/~evanj/cms/internal/m/space"
userT "git.sr.ht/~evanj/cms/internal/m/user"
"github.com/bmizerany/assert"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
)
var client = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
func TestNoUser(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
db = hook.NewMockdber(ctrl)
l *log.Logger
s = hook.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
methods = []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
)
for _, method := range methods {
req, _ := http.NewRequest(method, ts.URL, nil)
res, _ := client.Do(req)
bytes, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, true, strings.Contains(string(bytes), c.ErrNoLogin.Error()))
}
}
type FakeHook struct{ id, url string }
func (u FakeHook) ID() string { return u.id }
func (u FakeHook) URL() string { return u.url }
func TestHookHappyPath(t *testing.T) {
t.Parallel()
var (
uItem = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
sItem = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
hItem = FakeHook{uuid.New().String(), uuid.New().String()}
)
type spec struct {
expect int
form url.Values
mock func(db *hook.Mockdber)
}
tests := []spec{
{
http.StatusFound,
url.Values{
"url": {hItem.URL()},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookNew(uItem, sItem, hItem.URL()).Return(hItem, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
},
},
{
http.StatusFound,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
db.EXPECT().HookDelete(uItem, sItem, hItem).Return(nil).AnyTimes()
},
},
}
for _, test := range tests {
var (
ctrl = gomock.NewController(t)
db = hook.NewMockdber(ctrl)
l *log.Logger
s = hook.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
)
test.mock(db)
req, _ := http.NewRequest("POST", ts.URL,
strings.NewReader(test.form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(uItem.Name(), uItem.Pass())
req.Header.Set("Accept", "text/html")
res, _ := client.Do(req)
assert.Equal(t, test.expect, res.StatusCode)
}
}
func TestHookBadPaths(t *testing.T) {
t.Parallel()
var (
uItem = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
sItem = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
hItem = FakeHook{uuid.New().String(), uuid.New().String()}
err = errors.New("bogus")
)
type spec struct {
expect int
err error
form url.Values
mock func(db *hook.Mockdber)
}
tests := []spec{
// POST
{
http.StatusBadRequest,
hook.ErrNoSpace,
url.Values{
"url": {hItem.URL()},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(nil, err).AnyTimes()
// db.EXPECT().HookNew(uItem, sItem, hItem.URL()).Return(hItem, nil).AnyTimes()
},
},
{
http.StatusInternalServerError,
hook.ErrFailedCreate,
url.Values{
"url": {hItem.URL()},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookNew(uItem, sItem, hItem.URL()).Return(nil, err).AnyTimes()
},
},
// GET
{
http.StatusBadRequest,
hook.ErrNoSpace,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(nil, err).AnyTimes()
// db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
},
},
{
http.StatusBadRequest,
hook.ErrNoHook,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(nil, err).AnyTimes()
},
},
// DELETE
{
http.StatusBadRequest,
hook.ErrNoSpace,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(nil, err).AnyTimes()
// db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
// db.EXPECT().HookDelete(uItem, sItem, hItem).Return(nil).AnyTimes()
},
},
{
http.StatusBadRequest,
hook.ErrNoHook,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(nil, err).AnyTimes()
// db.EXPECT().HookDelete(uItem, sItem, hItem).Return(nil).AnyTimes()
},
},
{
http.StatusInternalServerError,
hook.ErrFailedDelete,
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
db.EXPECT().HookDelete(uItem, sItem, hItem).Return(err).AnyTimes()
},
},
// PATCH / NOT FOUND
{
http.StatusNotFound,
errors.New("404 page not found"),
url.Values{
"hook": {hItem.ID()},
"space": {sItem.ID()},
"method": {"PATCH"}, // By default net/http doesn't parse body on DELETE.
},
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
db.EXPECT().HookDelete(uItem, sItem, hItem).Return(err).AnyTimes()
},
},
}
for _, test := range tests {
var (
ctrl = gomock.NewController(t)
db = hook.NewMockdber(ctrl)
l *log.Logger
s = hook.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
)
test.mock(db)
req, _ := http.NewRequest("POST", ts.URL,
strings.NewReader(test.form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(uItem.Name(), uItem.Pass())
req.Header.Set("Accept", "text/html")
res, _ := client.Do(req)
byt, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, test.expect, res.StatusCode)
assert.Equal(t, true, strings.Contains(string(byt), test.err.Error()))
}
}
func TestHookWeirdPath(t *testing.T) {
t.Parallel()
var (
uItem = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
sItem = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
hItem = FakeHook{uuid.New().String(), uuid.New().String()}
)
type spec struct {
expect int
mock func(db *hook.Mockdber)
}
tests := []spec{
{
http.StatusOK,
func(db *hook.Mockdber) {
db.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
db.EXPECT().HookGet(uItem, sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
},
},
}
for _, test := range tests {
var (
ctrl = gomock.NewController(t)
db = hook.NewMockdber(ctrl)
l *log.Logger
s = hook.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
)
test.mock(db)
req, _ := http.NewRequest("GET", ts.URL+"/hook/"+sItem.ID()+"/"+hItem.ID(), nil)
req.SetBasicAuth(uItem.Name(), uItem.Pass())
res, _ := client.Do(req)
assert.Equal(t, test.expect, res.StatusCode)
}
}