~evanj/cms

41280ccb1c4f3d67aedeede9b12e3569289381e8 — Evan M Jones 1 year, 7 days ago 988b752
Fix(testing): Refactor controller tests for new space and user
interface.
M internal/c/c_test.go => internal/c/c_test.go +8 -16
@@ 3,7 3,6 @@ package c_test
import (
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"


@@ 15,6 14,7 @@ import (

	"git.sr.ht/~evanj/cms/internal/c"
	mock_c "git.sr.ht/~evanj/cms/internal/c/mock"
	userT "git.sr.ht/~evanj/cms/internal/m/user"
	"github.com/bmizerany/assert"
	"github.com/golang/mock/gomock"
	"github.com/google/uuid"


@@ 158,16 158,15 @@ func TestRedirect(t *testing.T) {

	for _, test := range tests {
		var (
			s   = server3{c.New(l, db, true, "test"), test}
			ts  = httptest.NewServer(s)
			val = url.Values{"url": {test}}
			s  = server3{c.New(l, db, true, "test"), test}
			ts = httptest.NewServer(s)
		)

		req, _ := http.NewRequest("GET", ts.URL+test, nil)
		req.Header.Set("Accept", "text/html")
		res, _ := http.DefaultClient.Do(req)
		assert.Equal(t, res.StatusCode, http.StatusTemporaryRedirect)
		assert.Equal(t, "/redirect?"+val.Encode(), res.Header.Get("Location"))
		assert.Equal(t, res.StatusCode, http.StatusFound)
		assert.Equal(t, test, res.Header.Get("Location"))
	}

	for _, test := range tests {


@@ 175,7 174,7 @@ func TestRedirect(t *testing.T) {
			s  = server3{c.New(l, db, true, "test"), test}
			ts = httptest.NewServer(s)
		)
		buf, _ := json.Marshal(map[string]string{"redirectURL": test})
		buf, _ := json.Marshal(test)
		res, _ := http.Get(ts.URL + test)
		bod, _ := ioutil.ReadAll(res.Body)
		assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 197,13 196,6 @@ func (s server4) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
}

type FakeUser struct{ u, p string }

func (u FakeUser) ID() string        { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u FakeUser) Name() string      { return u.u }
func (u FakeUser) Token() string     { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u FakeUser) OrgID() (r string) { return }

func TestUserFromBasicAuth(t *testing.T) {
	t.Parallel()



@@ 216,7 208,7 @@ func TestUserFromBasicAuth(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)
	)

	db.EXPECT().UserGet(uname, upass).Return(u, nil).AnyTimes()


@@ 241,7 233,7 @@ func TestUserFromFromCookie(t *testing.T) {
		uname  = uuid.New().String()
		upass  = uuid.New().String()
		cookie = uuid.New().String()
		u      = FakeUser{uname, upass}
		u      = userT.NewMock(uname, upass)
	)

	db.EXPECT().UserGetFromToken(cookie).Return(u, nil).AnyTimes()

M internal/c/content/content_test.go => internal/c/content/content_test.go +52 -66
@@ 5,7 5,6 @@ import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"


@@ 18,6 17,8 @@ import (
	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/c/content"
	contentmodel "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/value"
	"git.sr.ht/~evanj/cms/internal/m/valuetype"
	"git.sr.ht/~evanj/cms/internal/s/db"


@@ 111,19 112,6 @@ func (pm umatcher) String() string {
	return fmt.Sprintf("%v", pm.to)
}

type fakeuser struct{ u, p string }

func (u fakeuser) ID() string        { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u fakeuser) Name() string      { return u.u }
func (u fakeuser) Token() string     { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u fakeuser) OrgID() (r string) { return }

type fakespace struct{ n, d string }

func (u fakespace) ID() string   { return fmt.Sprintf("id-%s-%s", u.n, u.d) }
func (u fakespace) Name() string { return u.n }
func (u fakespace) Desc() string { return u.d }

type fakecontenttype struct {
	id, name string
	fields   int


@@ 177,8 165,8 @@ func TestCreateGood(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 191,7 179,7 @@ func TestCreateGood(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentNew(s, ct, nmatcher{newParams}).Return(c, nil).AnyTimes()


@@ 201,7 189,7 @@ func TestCreateGood(t *testing.T) {
	bod, headerCT := bodyWithFile("File-image", form)
	req, _ := http.NewRequest("POST", ts.URL, bod)
	req.Header.Set("Content-Type", headerCT)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 231,8 219,8 @@ func TestUpdateGood(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 248,7 236,7 @@ func TestUpdateGood(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()


@@ 259,7 247,7 @@ func TestUpdateGood(t *testing.T) {
	bod, headerCT := bodyWithFile("File-image", form)
	req, _ := http.NewRequest("POST", ts.URL, bod)
	req.Header.Set("Content-Type", headerCT)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 289,8 277,8 @@ func TestUpdateGood2(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 306,7 294,7 @@ func TestUpdateGood2(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()


@@ 317,7 305,7 @@ func TestUpdateGood2(t *testing.T) {
	bod, headerCT := bodyWithFile("value_update_File-4-image", form)
	req, _ := http.NewRequest("POST", ts.URL, bod)
	req.Header.Set("Content-Type", headerCT)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 334,8 322,8 @@ func TestServeGood(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 347,7 335,7 @@ func TestServeGood(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()


@@ 356,7 344,7 @@ func TestServeGood(t *testing.T) {
	req, _ := http.NewRequest("POST", ts.URL,
		strings.NewReader(form.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 373,8 361,8 @@ func TestServeGood2(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 385,14 373,14 @@ func TestServeGood2(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()
	h.EXPECT().Do(s, c, webhook.HookNew).Return().AnyTimes()

	req, _ := http.NewRequest("GET", ts.URL+"?"+form.Encode(), nil)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())
	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)
}


@@ 408,20 396,20 @@ func TestServeGood3(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()
	h.EXPECT().Do(s, c, webhook.HookNew).Return().AnyTimes()

	req, _ := http.NewRequest("GET", fmt.Sprintf("%s/content/%s/%s/%s", ts.URL, s.ID(), ct.ID(), c.ID()), nil)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())
	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)
}


@@ 437,8 425,8 @@ func TestDeleteGood(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		c  = fakecontent{uuid.New().String(), ct.ID(), nil}



@@ 449,7 437,7 @@ func TestDeleteGood(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentGet(s, ct, c.ID()).Return(c, nil).AnyTimes()


@@ 458,7 446,7 @@ func TestDeleteGood(t *testing.T) {

	req, _ := http.NewRequest("DELETE", ts.URL+"?"+form.Encode(), nil)
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 475,8 463,8 @@ func TestSearchGood(t *testing.T) {
		l    = log.New(os.Stdout, "", 0)
		ts   = httptest.NewServer(content.New(c.New(l, db, true, "test"), l, db, e3, h, ""))

		u  = fakeuser{uuid.New().String(), uuid.New().String()}
		s  = fakespace{uuid.New().String(), uuid.New().String()}
		u  = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s  = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
		cl = fakecontentlist{}



@@ 488,13 476,13 @@ func TestSearchGood(t *testing.T) {
		}
	)

	db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
	db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
	db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
	db.EXPECT().ContentTypeGet(s, ct.ID()).Return(ct, nil).AnyTimes()
	db.EXPECT().ContentSearch(s, ct, "name", "garfield", 0).Return(cl, nil).AnyTimes()

	req, _ := http.NewRequest("GET", ts.URL+"?"+form.Encode(), nil)
	req.SetBasicAuth(u.Name(), u.p)
	req.SetBasicAuth(u.Name(), u.Pass())

	res, _ := http.DefaultClient.Do(req)
	assert.Equal(t, res.StatusCode, http.StatusOK)


@@ 505,8 493,8 @@ func TestBad(t *testing.T) {

	var (
		err = errors.New("placeholder")
		u   = fakeuser{uuid.New().String(), uuid.New().String()}
		s   = fakespace{uuid.New().String(), uuid.New().String()}
		u   = userT.NewMock(uuid.New().String(), uuid.New().String()).SetPass(uuid.New().String())
		s   = spaceT.NewMock(uuid.New().String(), uuid.New().String(), uuid.New().String())
		ct  = fakecontenttype{uuid.New().String(), uuid.New().String(), 3}
	// cl = fakecontentlist{}
	)


@@ 527,7 515,7 @@ func TestBad(t *testing.T) {
				"method": {"GET"},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(nil, err).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(nil, err).AnyTimes()
			},
		},
		{


@@ 538,7 526,7 @@ func TestBad(t *testing.T) {
				"query":  {"placeholder"},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(nil, err).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(nil, err).AnyTimes()
			},
		},
		{


@@ 548,7 536,7 @@ func TestBad(t *testing.T) {
				"method": {"PATCH"},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(nil, err).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(nil, err).AnyTimes()
			},
		},
		{


@@ 558,7 546,7 @@ func TestBad(t *testing.T) {
				"method": {"DELETE"},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(nil, err).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(nil, err).AnyTimes()
			},
		},
		{


@@ 568,7 556,7 @@ func TestBad(t *testing.T) {
				"method": {"POST"},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(nil, err).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(nil, err).AnyTimes()
			},
		},
		// No space.


@@ 580,7 568,7 @@ func TestBad(t *testing.T) {
				"space":  {s.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(nil, err).AnyTimes()
			},
		},


@@ 593,7 581,7 @@ func TestBad(t *testing.T) {
				"space":  {s.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(nil, err).AnyTimes()
			},
		},


@@ 605,7 593,7 @@ func TestBad(t *testing.T) {
				"space":  {s.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(nil, err).AnyTimes()
			},
		},


@@ 617,7 605,7 @@ func TestBad(t *testing.T) {
				"space":  {s.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(nil, err).AnyTimes()
			},
		},


@@ 629,7 617,7 @@ func TestBad(t *testing.T) {
				"space":  {s.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(nil, err).AnyTimes()
			},
		},


@@ 643,7 631,7 @@ func TestBad(t *testing.T) {
				"contenttype": {ct.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
				db.EXPECT().ContentTypeGet(s, ct.ID()).Return(nil, err).AnyTimes()
			},


@@ 658,7 646,7 @@ func TestBad(t *testing.T) {
				"contenttype": {ct.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
				db.EXPECT().ContentTypeGet(s, ct.ID()).Return(nil, err).AnyTimes()
			},


@@ 672,7 660,7 @@ func TestBad(t *testing.T) {
				"contenttype": {ct.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
				db.EXPECT().ContentTypeGet(s, ct.ID()).Return(nil, err).AnyTimes()
			},


@@ 686,7 674,7 @@ func TestBad(t *testing.T) {
				"contenttype": {ct.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
				db.EXPECT().ContentTypeGet(s, ct.ID()).Return(nil, err).AnyTimes()
			},


@@ 700,7 688,7 @@ func TestBad(t *testing.T) {
				"contenttype": {ct.ID()},
			},
			func(db *content.MockDBer, e3 *content.MockE3er, h *content.MockHooker) {
				db.EXPECT().UserGet(u.Name(), u.p).Return(u, nil).AnyTimes()
				db.EXPECT().UserGet(u.Name(), u.Pass()).Return(u, nil).AnyTimes()
				db.EXPECT().SpaceGet(u, s.ID()).Return(s, nil).AnyTimes()
				db.EXPECT().ContentTypeGet(s, ct.ID()).Return(nil, err).AnyTimes()
			},


@@ 722,11 710,9 @@ func TestBad(t *testing.T) {
		req, _ := http.NewRequest("POST", ts.URL,
			strings.NewReader(test.form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.SetBasicAuth(u.Name(), u.p)
		req.SetBasicAuth(u.Name(), u.Pass())

		res, _ := http.DefaultClient.Do(req)
		byt, _ := ioutil.ReadAll(res.Body)
		assert.Equal(t, true, strings.Contains(test.err.Error(), string(byt)))
		assert.Equal(t, res.StatusCode, test.sc)
	}
}

M internal/c/contenttype/contenttype_test.go => internal/c/contenttype/contenttype_test.go +21 -33
@@ 2,7 2,6 @@ package contenttype_test

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"


@@ 15,6 14,8 @@ import (
	"git.sr.ht/~evanj/cms/internal/c/contenttype"
	mock_contenttype "git.sr.ht/~evanj/cms/internal/c/contenttype/mock"
	"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"


@@ 42,19 43,6 @@ func TestNoUser(t *testing.T) {
	}
}

type FakeUser struct{ u, p string }

func (u FakeUser) ID() string        { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u FakeUser) Name() string      { return u.u }
func (u FakeUser) Token() string     { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u FakeUser) OrgID() (r string) { return }

type FakeSpace struct{ n, d string }

func (u FakeSpace) ID() string   { return fmt.Sprintf("id-%s-%s", u.n, u.d) }
func (u FakeSpace) Name() string { return u.n }
func (u FakeSpace) Desc() string { return u.d }

type FakeContentType struct {
	id, name string
	fields   int


@@ 75,8 63,8 @@ func TestContentTypeHappyPath(t *testing.T) {
	t.Parallel()

	var (
		uItem        = FakeUser{uuid.New().String(), uuid.New().String()}
		sItem        = FakeSpace{uuid.New().String(), uuid.New().String()}
		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}



@@ 104,7 92,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				newParams := []db.ContentTypeNewParam{
					{"name", string(valuetype.StringSmall)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeNew(sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
			},


@@ 129,7 117,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				updateParams := []db.ContentTypeUpdateParam{
					{"1234", "desc", string(valuetype.StringBig)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeUpdate(sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()


@@ 143,7 131,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				"method":      {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(m *mock_contenttype.Mockdber) {
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
				m.EXPECT().ContentPerContentType(sItem, ctItem, 0, db.OrderAsc, "name").Return(cl, nil).AnyTimes()


@@ 157,7 145,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(m *mock_contenttype.Mockdber) {
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
				m.EXPECT().ContentTypeSearch(sItem, "post", 0).Return(ctl, nil).AnyTimes()


@@ 172,7 160,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				"method":      {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(m *mock_contenttype.Mockdber) {
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
				m.EXPECT().ContentPerContentType(sItem, ctItem, 0, db.OrderDesc, "name").Return(cl, nil).AnyTimes()


@@ 186,7 174,7 @@ func TestContentTypeHappyPath(t *testing.T) {
				"method":      {"DELETE"}, // By default net/http doesn't parse body on DELETE.
			},
			func(m *mock_contenttype.Mockdber) {
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItem.ID()).Return(ctItem, nil).AnyTimes()
				m.EXPECT().ContentTypeDelete(sItem, ctItem).Return(nil).AnyTimes()


@@ 209,7 197,7 @@ func TestContentTypeHappyPath(t *testing.T) {
			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.p)
		req.SetBasicAuth(uItem.Name(), uItem.Pass())
		res, err := http.DefaultClient.Do(req)
		assert.Equal(t, err, nil)
		if test.expect != res.StatusCode {


@@ 224,8 212,8 @@ func TestContentTypeBadPath(t *testing.T) {
	t.Parallel()

	var (
		uItem        = FakeUser{uuid.New().String(), uuid.New().String()}
		sItem        = FakeSpace{uuid.New().String(), uuid.New().String()}
		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")


@@ 251,7 239,7 @@ func TestContentTypeBadPath(t *testing.T) {
				newParams := []db.ContentTypeNewParam{
					{"name", string(valuetype.StringSmall)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeNew(sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
			},


@@ 270,7 258,7 @@ func TestContentTypeBadPath(t *testing.T) {
				newParams := []db.ContentTypeNewParam{
					{"name", string(valuetype.StringSmall)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeNew(sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
			},


@@ 289,7 277,7 @@ func TestContentTypeBadPath(t *testing.T) {
				newParams := []db.ContentTypeNewParam{
					{"name", string(valuetype.StringSmall)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeNew(sItem, ctItem.Name(), newParams).Return(ctItem, nil).AnyTimes()
			},


@@ 308,7 296,7 @@ func TestContentTypeBadPath(t *testing.T) {
				newParams := []db.ContentTypeNewParam{
					{"name", string(valuetype.StringSmall)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeNew(sItem, ctItem.Name(), newParams).Return(nil, err).AnyTimes()
			},


@@ 334,7 322,7 @@ func TestContentTypeBadPath(t *testing.T) {
				updateParams := []db.ContentTypeUpdateParam{
					{"1234", "desc", string(valuetype.StringBig)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeUpdate(sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()


@@ 361,7 349,7 @@ func TestContentTypeBadPath(t *testing.T) {
				updateParams := []db.ContentTypeUpdateParam{
					{"1234", "desc", string(valuetype.StringBig)},
				}
				m.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
				m.EXPECT().UserGet(uItem.Name(), uItem.Pass()).Return(uItem, nil).AnyTimes()
				m.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				m.EXPECT().ContentTypeUpdate(sItem, ctItemUpdate, ctItemUpdate.Name(), newParams, updateParams).Return(ctItemUpdate, nil).AnyTimes()
				m.EXPECT().ContentTypeGet(sItem, ctItemUpdate.ID()).Return(ctItemUpdate, nil).AnyTimes()


@@ 383,10 371,10 @@ func TestContentTypeBadPath(t *testing.T) {
		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.p)
		req.SetBasicAuth(uItem.Name(), uItem.Pass())
		res, _ := http.DefaultClient.Do(req)
		byt, _ := ioutil.ReadAll(res.Body)
		assert.Equal(t, true, strings.Contains(test.err.Error(), string(byt)))
		assert.Equal(t, true, strings.Contains(string(byt), test.err.Error()))
		assert.Equal(t, test.expect, res.StatusCode)
	}
}

M internal/c/doc/doc_test.go => internal/c/doc/doc_test.go +3 -10
@@ 8,9 8,9 @@ import (

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/c/doc"
	userT "git.sr.ht/~evanj/cms/internal/m/user"
	"github.com/bmizerany/assert"
	gomock "github.com/golang/mock/gomock"
	"github.com/google/uuid"
)

func TestWithoutUser(t *testing.T) {


@@ 28,13 28,6 @@ func TestWithoutUser(t *testing.T) {
	assert.Equal(t, res.StatusCode, http.StatusOK)
}

type FakeUser struct{}

func (u FakeUser) ID() string        { return "98" }
func (u FakeUser) Name() string      { return "Spike" }
func (u FakeUser) Token() string     { return uuid.New().String() }
func (u FakeUser) OrgID() (r string) { return }

func TestWithUser(t *testing.T) {
	t.Parallel()



@@ 45,7 38,7 @@ func TestWithUser(t *testing.T) {
		ts   = httptest.NewServer(s)
	)

	db.EXPECT().UserGet("user", "pass").Return(FakeUser{}, nil).AnyTimes()
	db.EXPECT().UserGet("user", "pass").Return(userT.NewMock("", ""), nil).AnyTimes()

	req, _ := http.NewRequest("GET", ts.URL+"/page/doc", nil)
	req.SetBasicAuth("user", "pass")


@@ 61,7 54,7 @@ func TestWithInvalidUserToken(t *testing.T) {
		db   = doc.NewMockdber(ctrl)
		s    = doc.New(c.New(nil, db, true, "test"), nil, db)
		ts   = httptest.NewServer(s)
		u    = FakeUser{}
		u    = userT.NewMock("", "")
		tok  = u.Token()
		err  = errors.New("placeholder")
	)

M internal/c/hook/hook_test.go => internal/c/hook/hook_test.go +54 -61
@@ 2,7 2,6 @@ package hook_test

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"


@@ 13,18 12,25 @@ import (

	"git.sr.ht/~evanj/cms/internal/c"
	"git.sr.ht/~evanj/cms/internal/c/hook"
	mock_hook "git.sr.ht/~evanj/cms/internal/c/hook/mock"
	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      = mock_hook.NewMockdber(ctrl)
		db      = hook.NewMockdber(ctrl)
		l       *log.Logger
		s       = hook.New(c.New(l, db, true, "test"), l, db)
		ts      = httptest.NewServer(s)


@@ 33,25 39,12 @@ func TestNoUser(t *testing.T) {

	for _, method := range methods {
		req, _ := http.NewRequest(method, ts.URL, nil)
		res, _ := http.DefaultClient.Do(req)
		res, _ := client.Do(req)
		bytes, _ := ioutil.ReadAll(res.Body)
		assert.Equal(t, true, strings.Contains(string(bytes), c.ErrNoLogin.Error()))
	}
}

type FakeUser struct{ u, p string }

func (u FakeUser) ID() string        { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u FakeUser) Name() string      { return u.u }
func (u FakeUser) Token() string     { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u FakeUser) OrgID() (r string) { return }

type FakeSpace struct{ n, d string }

func (u FakeSpace) ID() string   { return fmt.Sprintf("id-%s-%s", u.n, u.d) }
func (u FakeSpace) Name() string { return u.n }
func (u FakeSpace) Desc() string { return u.d }

type FakeHook struct{ id, url string }

func (u FakeHook) ID() string  { return u.id }


@@ 61,27 54,27 @@ func TestHookHappyPath(t *testing.T) {
	t.Parallel()

	var (
		uItem = FakeUser{uuid.New().String(), uuid.New().String()}
		sItem = FakeSpace{uuid.New().String(), uuid.New().String()}
		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 *mock_hook.Mockdber)
		mock   func(db *hook.Mockdber)
	}

	tests := []spec{
		{
			http.StatusTemporaryRedirect,
			http.StatusFound,
			url.Values{
				"url":    {hItem.URL()},
				"space":  {sItem.ID()},
				"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.URL()).Return(hItem, nil).AnyTimes()
			},


@@ 93,21 86,21 @@ func TestHookHappyPath(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
			},
		},
		{
			http.StatusTemporaryRedirect,
			http.StatusFound,
			url.Values{
				"hook":   {hItem.ID()},
				"space":  {sItem.ID()},
				"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
				db.EXPECT().HookDelete(sItem, hItem).Return(nil).AnyTimes()


@@ 118,7 111,7 @@ func TestHookHappyPath(t *testing.T) {
	for _, test := range tests {
		var (
			ctrl = gomock.NewController(t)
			db   = mock_hook.NewMockdber(ctrl)
			db   = hook.NewMockdber(ctrl)
			l    *log.Logger
			s    = hook.New(c.New(l, db, true, "test"), l, db)
			ts   = httptest.NewServer(s)


@@ 129,9 122,9 @@ func TestHookHappyPath(t *testing.T) {
		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.p)
		req.SetBasicAuth(uItem.Name(), uItem.Pass())
		req.Header.Set("Accept", "text/html")
		res, _ := http.DefaultClient.Do(req)
		res, _ := client.Do(req)

		assert.Equal(t, test.expect, res.StatusCode)
	}


@@ 141,8 134,8 @@ func TestHookBadPaths(t *testing.T) {
	t.Parallel()

	var (
		uItem = FakeUser{uuid.New().String(), uuid.New().String()}
		sItem = FakeSpace{uuid.New().String(), uuid.New().String()}
		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")


@@ 152,7 145,7 @@ func TestHookBadPaths(t *testing.T) {
		expect int
		err    error
		form   url.Values
		mock   func(db *mock_hook.Mockdber)
		mock   func(db *hook.Mockdber)
	}

	tests := []spec{


@@ 165,8 158,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.URL()).Return(hItem, nil).AnyTimes()
			},


@@ 179,8 172,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"POST"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.URL()).Return(nil, err).AnyTimes()
			},


@@ 194,8 187,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
			},


@@ 208,8 201,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"GET"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(nil, err).AnyTimes()
			},


@@ 223,8 216,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
				// db.EXPECT().HookDelete(sItem, hItem).Return(nil).AnyTimes()


@@ 238,8 231,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(nil, err).AnyTimes()
				// db.EXPECT().HookDelete(sItem, hItem).Return(nil).AnyTimes()


@@ 253,8 246,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"DELETE"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
				db.EXPECT().HookDelete(sItem, hItem).Return(err).AnyTimes()


@@ 269,8 262,8 @@ func TestHookBadPaths(t *testing.T) {
				"space":  {sItem.ID()},
				"method": {"PATCH"}, // By default net/http doesn't parse body on DELETE.
			},
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
				db.EXPECT().HookDelete(sItem, hItem).Return(err).AnyTimes()


@@ 281,7 274,7 @@ func TestHookBadPaths(t *testing.T) {
	for _, test := range tests {
		var (
			ctrl = gomock.NewController(t)
			db   = mock_hook.NewMockdber(ctrl)
			db   = hook.NewMockdber(ctrl)
			l    *log.Logger
			s    = hook.New(c.New(l, db, true, "test"), l, db)
			ts   = httptest.NewServer(s)


@@ 292,9 285,9 @@ func TestHookBadPaths(t *testing.T) {
		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.p)
		req.SetBasicAuth(uItem.Name(), uItem.Pass())
		req.Header.Set("Accept", "text/html")
		res, _ := http.DefaultClient.Do(req)
		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()))


@@ 305,21 298,21 @@ func TestHookWeirdPath(t *testing.T) {
	t.Parallel()

	var (
		uItem = FakeUser{uuid.New().String(), uuid.New().String()}
		sItem = FakeSpace{uuid.New().String(), uuid.New().String()}
		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 *mock_hook.Mockdber)
		mock   func(db *hook.Mockdber)
	}

	tests := []spec{
		{
			http.StatusOK,
			func(db *mock_hook.Mockdber) {
				db.EXPECT().UserGet(uItem.Name(), uItem.p).Return(uItem, nil).AnyTimes()
			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(sItem, hItem.ID()).Return(hItem, nil).AnyTimes()
			},


@@ 329,7 322,7 @@ func TestHookWeirdPath(t *testing.T) {
	for _, test := range tests {
		var (
			ctrl = gomock.NewController(t)
			db   = mock_hook.NewMockdber(ctrl)
			db   = hook.NewMockdber(ctrl)
			l    *log.Logger
			s    = hook.New(c.New(l, db, true, "test"), l, db)
			ts   = httptest.NewServer(s)


@@ 338,8 331,8 @@ func TestHookWeirdPath(t *testing.T) {
		test.mock(db)

		req, _ := http.NewRequest("GET", ts.URL+"/hook/"+sItem.ID()+"/"+hItem.ID(), nil)
		req.SetBasicAuth(uItem.Name(), uItem.p)
		res, _ := http.DefaultClient.Do(req)
		req.SetBasicAuth(uItem.Name(), uItem.Pass())
		res, _ := client.Do(req)
		assert.Equal(t, test.expect, res.StatusCode)
	}
}

R internal/c/hook/mock/mock_hook.go => internal/c/hook/mock.go +2 -2
@@ 1,8 1,8 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: hook.go

// Package mock_hook is a generated GoMock package.
package mock_hook
// Package hook is a generated GoMock package.
package hook

import (
	hook "git.sr.ht/~evanj/cms/internal/m/hook"

M internal/c/space/space_test.go => internal/c/space/space_test.go +22 -30
@@ 2,7 2,6 @@ package space_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"


@@ 17,34 16,27 @@ import (
	mock_space "git.sr.ht/~evanj/cms/internal/c/space/mock"
	"git.sr.ht/~evanj/cms/internal/m/contenttype"
	"git.sr.ht/~evanj/cms/internal/m/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 emptyreq(ts *httptest.Server, method string) (*http.Response, error) {
	req, err := newrequest(method, ts.URL, nil)
	if err != nil {
		return nil, err
	}
	return http.DefaultClient.Do(req)
	return client.Do(req)
}

type FakeUser struct{ u, p string }

func fakeuser(u, p string) FakeUser { return FakeUser{u, p} }
func (u FakeUser) ID() string       { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u FakeUser) Name() string     { return u.u }
func (u FakeUser) Token() string    { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u FakeUser) OrgID() string    { return fmt.Sprintf("org-id-%s-%s", u.u, u.p) }

type FakeSpace struct{ n, d string }

func fakespace(n, d string) FakeSpace { return FakeSpace{n, d} }
func (u FakeSpace) ID() string        { return fmt.Sprintf("id-%s-%s", u.n, u.d) }
func (u FakeSpace) Name() string      { return u.n }
func (u FakeSpace) Desc() string      { return u.d }

type FakeContentTypeList struct{}

func fakecontenttypelist() FakeContentTypeList                    { return FakeContentTypeList{} }


@@ 99,19 91,19 @@ func TestAll(t *testing.T) {
	var (
		uname = "test"
		upass = "test"
		uItem = fakeuser(uname, upass)
		uItem = userT.NewMock(uname, upass)

		sname = uuid.New().String()
		sdesc = uuid.New().String()
		sItem = fakespace(sname, sdesc)
		sItem = spaceT.NewMock(sname, sdesc, "")

		snameUpdate = uuid.New().String()
		sdescUpdate = uuid.New().String()
		sItemUpdate = fakespace(snameUpdate, sdescUpdate)
		sItemUpdate = spaceT.NewMock(snameUpdate, sdescUpdate, "")

		snameCopy = uuid.New().String()
		sdescCopy = uuid.New().String()
		sItemCopy = fakespace(snameCopy, sdescCopy)
		sItemCopy = spaceT.NewMock(snameCopy, sdescCopy, "")

		contentTypeList = fakecontenttypelist()
		hookList        = fakehooklist()


@@ 133,7 125,7 @@ func TestAll(t *testing.T) {
	tests := []SpaceTest{

		SpaceTest{
			createSC: http.StatusTemporaryRedirect,
			createSC: http.StatusFound,
			create: func(db *mock_space.Mockdber) {
				db.EXPECT().UserGet(uname, upass).Return(uItem, nil).AnyTimes()
				db.EXPECT().SpaceNew(uItem, sname, sdesc).Return(sItem, nil).AnyTimes()


@@ 145,19 137,19 @@ func TestAll(t *testing.T) {
				db.EXPECT().ContentTypesPerSpace(sItem, 0).Return(contentTypeList, nil).AnyTimes()
				db.EXPECT().HooksPerSpace(sItem, 0).Return(hookList, nil).AnyTimes()
			},
			updateSC: http.StatusTemporaryRedirect,
			updateSC: http.StatusFound,
			update: func(db *mock_space.Mockdber) {
				db.EXPECT().UserGet(uname, upass).Return(uItem, nil).AnyTimes()
				db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()
				db.EXPECT().SpaceUpdate(uItem, sItem, snameUpdate, sdescUpdate).Return(sItemUpdate, nil).AnyTimes()
			},
			copySC: http.StatusTemporaryRedirect,
			copySC: http.StatusFound,
			copy: func(db *mock_space.Mockdber) {
				db.EXPECT().UserGet(uname, upass).Return(uItem, nil).AnyTimes()
				db.EXPECT().SpaceGet(uItem, sItemUpdate.ID()).Return(sItemUpdate, nil).AnyTimes()
				db.EXPECT().SpaceCopy(uItem, sItemUpdate, snameCopy, sdescCopy).Return(sItemCopy, nil).AnyTimes()
			},
			deleteSC: http.StatusTemporaryRedirect,
			deleteSC: http.StatusFound,
			delete: func(db *mock_space.Mockdber) {
				db.EXPECT().UserGet(uname, upass).Return(uItem, nil).AnyTimes()
				db.EXPECT().SpaceGet(uItem, sItem.ID()).Return(sItem, nil).AnyTimes()


@@ 238,7 230,7 @@ func TestAll(t *testing.T) {
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Accept", "text/html")

			res, _ := http.DefaultClient.Do(req)
			res, _ := client.Do(req)
			assert.Equal(t, res.StatusCode, test.createSC)
		}



@@ 251,7 243,7 @@ func TestAll(t *testing.T) {
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Accept", "text/html")

			res, _ := http.DefaultClient.Do(req)
			res, _ := client.Do(req)
			assert.Equal(t, res.StatusCode, test.getSC)
		}



@@ 269,7 261,7 @@ func TestAll(t *testing.T) {
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Accept", "text/html")

			res, _ := http.DefaultClient.Do(req)
			res, _ := client.Do(req)
			assert.Equal(t, res.StatusCode, test.updateSC)
		}



@@ 286,7 278,7 @@ func TestAll(t *testing.T) {
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Accept", "text/html")

			res, _ := http.DefaultClient.Do(req)
			res, _ := client.Do(req)
			assert.Equal(t, res.StatusCode, test.copySC)
		}



@@ 304,7 296,7 @@ func TestAll(t *testing.T) {
			req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
			req.Header.Set("Accept", "text/html")

			res, _ := http.DefaultClient.Do(req)
			res, _ := client.Do(req)
			assert.Equal(t, res.StatusCode, test.deleteSC)
		}
	}

M internal/c/user/user_test.go => internal/c/user/user_test.go +6 -13
@@ 1,7 1,6 @@
package user_test

import (
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"


@@ 12,18 11,12 @@ import (
	"git.sr.ht/~evanj/cms/internal/c/user"
	spaceType "git.sr.ht/~evanj/cms/internal/m/space"
	tier "git.sr.ht/~evanj/cms/internal/m/tier"
	userT "git.sr.ht/~evanj/cms/internal/m/user"
	"github.com/bmizerany/assert"
	"github.com/golang/mock/gomock"
	"github.com/google/uuid"
)

type FakeUser struct{ u, p string }

func (u FakeUser) ID() string        { return fmt.Sprintf("id-%s-%s", u.u, u.p) }
func (u FakeUser) Name() string      { return u.u }
func (u FakeUser) Token() string     { return fmt.Sprintf("token-%s-%s", u.u, u.p) }
func (u FakeUser) OrgID() (r string) { return }

type FakeSpaceList struct{}

func (_ FakeSpaceList) List() (r []spaceType.Space) { return }


@@ 43,7 36,7 @@ func TestCreate(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)

		form = url.Values{
			"username": {uname},


@@ 72,7 65,7 @@ func TestLogin(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)

		form = url.Values{
			"username": {uname},


@@ 115,7 108,7 @@ func TestHome(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)

		form = url.Values{
			"username": {uname},


@@ 208,7 201,7 @@ func TestCreatePaid(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)

		form = url.Values{
			"username": {uname},


@@ 238,7 231,7 @@ func TestHomeNoUser(t *testing.T) {

		uname = uuid.New().String()
		upass = uuid.New().String()
		u     = FakeUser{uname, upass}
		u     = userT.NewMock(uname, upass)

		form = url.Values{
			"username": {uname},

A internal/m/space/mock.go => internal/m/space/mock.go +32 -0
@@ 0,0 1,32 @@
package space

import (
	"git.sr.ht/~evanj/cms/internal/m/org"
	"git.sr.ht/~evanj/cms/internal/m/tier"
)

type _space struct {
	id, name, desc string
	org            _org
}

type _org struct {
	id       string
	payment  bool
	customer string
	tier     tier.Tier
}

func NewMock(id, name, desc string) _space {
	return _space{id, name, desc, _org{}}
}

func (s _space) ID() string   { return s.id }
func (s _space) Name() string { return s.name }
func (s _space) Desc() string { return s.desc }
func (s _space) Org() org.Org { return s.org }

func (o _org) ID() string                { return o.id }
func (o _org) HasPaymentCustomer() bool  { return o.payment }
func (o _org) PaymentCustomerID() string { return o.customer }
func (o _org) Tier() tier.Tier           { return o.tier }

A internal/m/user/mock.go => internal/m/user/mock.go +39 -0
@@ 0,0 1,39 @@
package user

import (
	"git.sr.ht/~evanj/cms/internal/m/org"
	"git.sr.ht/~evanj/cms/internal/m/tier"
	"github.com/google/uuid"
)

type _user struct {
	id, name, tok, email string
	org                  _org
	pass                 string
}

type _org struct {
	id string
}

func NewMock(id, name string) _user {
	return _user{id, name, uuid.New().String(), "", _org{id}, ""}
}

func NewMockWithEmail(id, name, email string) _user {
	return _user{id, name, uuid.New().String(), email, _org{id}, ""}
}

func (u _user) ID() string             { return u.id }
func (u _user) Name() string           { return u.name }
func (u _user) Token() string          { return u.tok }
func (u _user) HasEmail() bool         { return u.email != "" }
func (u _user) Email() string          { return u.email }
func (u _user) Org() org.Org           { return u.org }
func (u _user) SetPass(p string) _user { u.pass = p; return u }
func (u _user) Pass() string           { return u.pass }

func (o _org) ID() string                { return o.id }
func (o _org) HasPaymentCustomer() bool  { return false }
func (o _org) PaymentCustomerID() string { return "" }
func (o _org) Tier() tier.Tier           { return tier.Free }