package contenttype_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/contenttype"
"git.sr.ht/~evanj/cms/internal/m/content"
spaceT "git.sr.ht/~evanj/cms/internal/m/space"
userT "git.sr.ht/~evanj/cms/internal/m/user"
"git.sr.ht/~evanj/cms/internal/m/valuetype"
"git.sr.ht/~evanj/cms/internal/s/db"
"github.com/bmizerany/assert"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
)
func TestNoUser(t *testing.T) {
t.Parallel()
var (
ctrl = gomock.NewController(t)
db = contenttype.NewMockdber(ctrl)
l *log.Logger
s = contenttype.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
methods = []string{"GET", "POST", "PATCH", "DELETE"}
)
for _, method := range methods {
req, _ := http.NewRequest(method, ts.URL, nil)
res, _ := http.DefaultClient.Do(req)
bytes, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, true, strings.Contains(string(bytes), c.ErrNoLogin.Error()))
}
}
type FakeContentType struct {
id, name string
fields int
}
func (u FakeContentType) ID() string { return u.id }
func (u FakeContentType) Name() string { return u.name }
func (u FakeContentType) Fields() (r []valuetype.ValueType) {
for i := 0; i < u.fields; i++ {
var val valuetype.ValueType
r = append(r, val)
}
return
}
func (u FakeContentType) FieldsWithRefCount() (r int) { return }
func TestContentTypeHappyPath(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())
ctItem = FakeContentType{uuid.New().String(), uuid.New().String(), 0}
ctItemUpdate = FakeContentType{uuid.New().String(), uuid.New().String(), 1}
cl content.ContentList
ctl content.ContentList
)
type spec struct {
expect int
form url.Values
mock func(db *contenttype.Mockdber)
}
tests := []spec{
{
http.StatusOK,
url.Values{
"name": {ctItem.Name()},
"field_name_1": {"name"},
"field_type_1": {string(valuetype.StringSmall)},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeNew(uItem, sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"name": {ctItemUpdate.Name()},
"contenttype": {ctItemUpdate.ID()},
"field_name_2": {"name"},
"field_type_2": {string(valuetype.StringSmall)},
"field_update_id_1": {"1234"},
"field_update_name_1": {"desc"},
"field_update_type_1": {string(valuetype.StringBig)},
"space": {sItem.ID()},
"method": {"PATCH"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
updateParams := []db.ContentTypeUpdateParam{
{"1234", "desc", string(valuetype.StringBig)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeUpdate(uItem, sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"contenttype": {ctItem.ID()},
"space": {sItem.ID()},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
m.EXPECT().ContentPerContentType(uItem, sItem, ctItem, 0, db.OrderAsc, "name").Return(cl, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"query": {"post"},
"space": {sItem.ID()},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
m.EXPECT().ContentTypeSearch(uItem, sItem, "post", 0).Return(ctl, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"contenttype": {ctItem.ID()},
"space": {sItem.ID()},
"order": {"desc"},
"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
m.EXPECT().ContentPerContentType(uItem, sItem, ctItem, 0, db.OrderDesc, "name").Return(cl, nil).AnyTimes()
},
},
{
http.StatusOK,
url.Values{
"contenttype": {ctItem.ID()},
"space": {sItem.ID()},
"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
m.EXPECT().ContentTypeDelete(uItem, sItem, ctItem).Return(nil).AnyTimes()
},
},
}
for _, test := range tests {
var (
ctrl = gomock.NewController(t)
db = contenttype.NewMockdber(ctrl)
l *log.Logger
s = contenttype.New(c.New(l, db, true, "test"), l, db)
ts = httptest.NewServer(s)
)
test.mock(db)
req, err := http.NewRequest("POST", ts.URL,
strings.NewReader(test.form.Encode()))
assert.Equal(t, err, nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.SetBasicAuth(uItem.Name(), uItem.Pass())
res, err := http.DefaultClient.Do(req)
assert.Equal(t, err, nil)
if test.expect != res.StatusCode {
byt, _ := ioutil.ReadAll(res.Body)
panic(string(byt))
}
assert.Equal(t, test.expect, res.StatusCode)
}
}
func TestContentTypeBadPath(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())
ctItem = FakeContentType{uuid.New().String(), uuid.New().String(), 0}
ctItemUpdate = FakeContentType{uuid.New().String(), uuid.New().String(), 1}
err = errors.New("placeholder")
)
type spec struct {
expect int
err error
form url.Values
mock func(db *contenttype.Mockdber)
}
tests := []spec{
{
http.StatusBadRequest,
contenttype.ErrNoFields,
url.Values{
"name": {ctItem.Name()},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeNew(uItem, sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
},
},
{
http.StatusBadRequest,
contenttype.ErrNoNameField,
url.Values{
"name": {ctItem.Name()},
"field_name_1": {"desc"},
"field_type_1": {string(valuetype.StringSmall)},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeNew(uItem, sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
},
},
{
http.StatusBadRequest,
contenttype.ErrBadForm,
url.Values{
"name": {ctItem.Name()},
"field_name_2": {"desc"},
"field_type_1": {string(valuetype.StringSmall)},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeNew(uItem, sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
},
},
{
http.StatusInternalServerError,
errors.New("failed to create contenttype"),
url.Values{
"name": {ctItem.Name()},
"field_name_1": {"name"},
"field_type_1": {string(valuetype.StringSmall)},
"space": {sItem.ID()},
"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeNew(uItem, sItem, ctItem.Name(), newParams).Return(nil, err).AnyTimes()
},
},
{
http.StatusBadRequest,
contenttype.ErrBadForm,
url.Values{
"name": {ctItemUpdate.Name()},
"contenttype": {ctItemUpdate.ID()},
"field_name_3": {"name"},
"field_type_2": {string(valuetype.StringSmall)},
"field_update_id_1": {"1234"},
"field_update_name_1": {"desc"},
"field_update_type_1": {string(valuetype.StringBig)},
"space": {sItem.ID()},
"method": {"PATCH"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
updateParams := []db.ContentTypeUpdateParam{
{"1234", "desc", string(valuetype.StringBig)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeUpdate(uItem, sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()
},
},
{
http.StatusBadRequest,
contenttype.ErrBadForm,
url.Values{
"name": {ctItemUpdate.Name()},
"contenttype": {ctItemUpdate.ID()},
"field_name_2": {"name"},
"field_type_2": {string(valuetype.StringSmall)},
"field_update_id_2": {"1234"},
"field_update_name_1": {"desc"},
"field_update_type_1": {string(valuetype.StringBig)},
"space": {sItem.ID()},
"method": {"PATCH"}, // By default net/http doesn't parse body on DELETE.
},
func(m *contenttype.Mockdber) {
newParams := []db.ContentTypeNewParam{
{"name", string(valuetype.StringSmall)},
}
updateParams := []db.ContentTypeUpdateParam{
{"1234", "desc", string(valuetype.StringBig)},
}
m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
m.EXPECT().ContentTypeUpdate(uItem, sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
m.EXPECT().ContentTypeGet(uItem, sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()
},
},
}
for _, test := range tests {
var (
ctrl = gomock.NewController(t)
db = contenttype.NewMockdber(ctrl)
l *log.Logger
s = contenttype.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())
res, _ := http.DefaultClient.Do(req)
byt, _ := ioutil.ReadAll(res.Body)
assert.Equal(t, true, strings.Contains(string(byt), test.err.Error()))
assert.Equal(t, test.expect, res.StatusCode)
}
}