file handler tests

This commit is contained in:
Lars Hoogestraat 2018-12-10 18:02:59 +01:00
parent 24a1f2555b
commit cde4f47f3e
5 changed files with 262 additions and 51 deletions

View File

@ -15,43 +15,44 @@ import (
"git.hoogi.eu/go-blog/models"
)
type FileHandler struct {
Context *middleware.AppContext
}
//FileGetHandler serves the file based on the url filename
func FileGetHandler(ctx *middleware.AppContext) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
rv := getVar(r, "filename")
func (fh FileHandler) FileGetHandler(w http.ResponseWriter, r *http.Request) {
rv := getVar(r, "filename")
f, err := ctx.FileService.GetByName(rv, nil)
f, err := fh.Context.FileService.GetByName(rv, nil)
if err != nil {
http.Error(w, "the file was not found", http.StatusNotFound)
return
}
loc := filepath.Join(ctx.ConfigService.Location, f.FullFilename)
w.Header().Set("Content-Type", f.ContentType)
w.Header().Set("Content-Disposition", "inline")
rf, err := os.Open(loc)
if err != nil {
if os.IsNotExist(err) {
logger.Log.Errorf("the file %s was not found - %v", loc, err)
http.Error(w, "404 page not found", http.StatusNotFound)
}
if os.IsPermission(err) {
logger.Log.Errorf("not permitted to read file %s - %v", loc, err)
http.Error(w, "404 page not found", http.StatusForbidden)
}
logger.Log.Errorf("an internal error while reading file %s - %v", loc, err)
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
}
defer rf.Close()
http.ServeContent(w, r, loc, f.LastModified, rf)
if err != nil {
http.Error(w, "the file was not found", http.StatusNotFound)
return
}
return http.HandlerFunc(fn)
loc := filepath.Join(fh.Context.ConfigService.Location, f.FullFilename)
w.Header().Set("Content-Type", f.ContentType)
w.Header().Set("Content-Disposition", "inline")
rf, err := os.Open(loc)
if err != nil {
if os.IsNotExist(err) {
logger.Log.Errorf("the file %s was not found - %v", loc, err)
http.Error(w, "404 page not found", http.StatusNotFound)
}
if os.IsPermission(err) {
logger.Log.Errorf("not permitted to read file %s - %v", loc, err)
http.Error(w, "404 page not found", http.StatusForbidden)
}
logger.Log.Errorf("an internal error while reading file %s - %v", loc, err)
http.Error(w, "500 Internal Server Error", http.StatusInternalServerError)
}
defer rf.Close()
http.ServeContent(w, r, loc, f.LastModified, rf)
}
//AdminListFilesHandler returns the template which lists alle uploaded files belonging to a user, admins will see all files

View File

@ -1,13 +1,149 @@
package controllers_test
func doAdminListFilesRequest(user reqUser) {
import (
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"git.hoogi.eu/go-blog/controllers"
"git.hoogi.eu/go-blog/models"
)
func TestFileWorkflow(t *testing.T) {
err := doAdminUploadFileRequest(rAdminUser, "testdata/color.png")
if err != nil {
t.Error(err)
}
files, err := doAdminListFilesRequest(rAdminUser)
if err != nil {
t.Error(err)
}
if len(files) != 1 {
t.Errorf("one file is uploaded; but list files returns %d file(s)", len(files))
}
rr, err := doAdminGetFileRequest(rGuest, files[0].FullFilename)
if err != nil {
t.Error(err)
}
if rr.Result().ContentLength != 1610 {
t.Errorf("expected 1610 bytes, but got %d", rr.Result().ContentLength)
}
if rr.Result().Header.Get("Content-Type") != "image/png" {
t.Errorf("expected image/png content type, but got %s", rr.Result().Header.Get("Content-Type"))
}
err = doAdminFileDeleteRequest(rAdminUser, files[0].ID)
if err != nil {
t.Error(err)
}
_, err = doAdminGetFileRequest(rGuest, files[0].FullFilename)
if err == nil {
t.Errorf("file should be removed, but file is there %s", files[0].FullFilename)
}
}
func doAdminUploadFileRequest(user reqUser) {
func doAdminListFilesRequest(user reqUser) ([]models.File, error) {
r := request{
url: "/admin/files",
user: user,
method: "GET",
}
rw := httptest.NewRecorder()
tpl := controllers.AdminListFilesHandler(ctx, rw, r.buildRequest())
if tpl.Err != nil {
return nil, tpl.Err
}
return tpl.Data["files"].([]models.File), nil
}
func doAdminUploadDeleteRequest(user reqUser) {
func doAdminGetFileRequest(user reqUser, filename string) (*httptest.ResponseRecorder, error) {
r := request{
url: "/file/" + filename,
user: user,
method: "GET",
pathVar: []pathVar{
pathVar{
key: "filename",
value: filename,
},
},
}
rw := httptest.NewRecorder()
fh := controllers.FileHandler{
Context: ctx,
}
fh.FileGetHandler(rw, r.buildRequest())
if rw.Result().StatusCode != http.StatusOK {
return rw, fmt.Errorf("got an invalid status code during file request /file/%s , code: %d, message %s", filename, rw.Result().StatusCode, rw.Result().Status)
}
return rw, nil
}
func doAdminUploadFileRequest(user reqUser, file string) error {
mp := []multipartRequest{
multipartRequest{
key: "file",
file: file,
},
}
r := request{
url: "/admin/file/upload",
user: user,
method: "POST",
multipartReq: mp,
}
rw := httptest.NewRecorder()
tpl := controllers.AdminUploadFilePostHandler(ctx, rw, r.buildRequest())
if tpl.Err != nil {
return tpl.Err
}
return nil
}
func doAdminFileDeleteRequest(user reqUser, fileID int) error {
r := request{
url: "/admin/file/delete/" + strconv.Itoa(fileID),
user: user,
method: "GET",
pathVar: []pathVar{
pathVar{
key: "fileID",
value: strconv.Itoa(fileID),
},
},
}
rw := httptest.NewRecorder()
tpl := controllers.AdminUploadDeletePostHandler(ctx, rw, r.buildRequest())
if tpl.Err != nil {
return tpl.Err
}
return nil
}

BIN
controllers/testdata/color.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -9,10 +9,15 @@ import (
"context"
"database/sql"
"fmt"
"io"
"io/ioutil"
"log"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"testing"
"git.hoogi.eu/go-blog/components/database"
@ -57,6 +62,8 @@ func setup(t *testing.T) {
t.Fatal(err)
}
cfg.File.Location = os.TempDir()
userService := models.UserService{
Datasource: models.SQLiteUserDatasource{
SQLConn: db,
@ -187,10 +194,6 @@ func setHeader(r *http.Request, key, value string) {
r.Header.Set("X-Unit-Testing-Value-"+key, value)
}
func addValue(m url.Values, key, value string) {
m.Add(key, value)
}
type MockSMTP struct{}
func (sm MockSMTP) Send(m mail.Mail) error {
@ -201,6 +204,10 @@ func (sm MockSMTP) SendAsync(m mail.Mail) error {
return nil
}
func addValue(m url.Values, key, value string) {
m.Add(key, value)
}
func addCheckboxValue(m url.Values, key string, value bool) {
if value {
m.Add(key, "on")
@ -208,20 +215,69 @@ func addCheckboxValue(m url.Values, key string, value bool) {
m.Add(key, "off")
}
func postMultipart(path string, mp []multipartRequest) (*http.Request, error) {
buf := &bytes.Buffer{}
mw := multipart.NewWriter(buf)
defer mw.Close()
for _, v := range mp {
fh, err := os.Open(v.file)
if err != nil {
return nil, err
}
defer fh.Close()
fw, err := mw.CreateFormFile(v.key, filepath.Base(fh.Name()))
if err != nil {
return nil, err
}
_, err = io.Copy(fw, fh)
if err != nil {
return nil, err
}
}
req, err := http.NewRequest("POST", path, buf)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", mw.FormDataContentType())
return req, nil
}
func post(path string, values url.Values) (*http.Request, error) {
var b bytes.Buffer
b.WriteString(values.Encode())
req, err := http.NewRequest("POST", path, &b)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return req, err
return req, nil
}
func get(path string, values url.Values) (*http.Request, error) {
var b bytes.Buffer
b.WriteString(values.Encode())
req, err := http.NewRequest("GET", path, &b)
return req, err
if err != nil {
return nil, err
}
return req, nil
}
//reqUser the user which should be added to the context
@ -239,11 +295,17 @@ const (
//url will not really considered as the requests are not send, the *http.Request is just passed directly to the controllers
//pathvar is an array of key/value pairs used as dynamic query parameters such as /article/{id}
type request struct {
url string
user reqUser
method string
values url.Values
pathVar []pathVar
url string
user reqUser
method string
values url.Values
pathVar []pathVar
multipartReq []multipartRequest
}
type multipartRequest struct {
key string
file string
}
type pathVar struct {
@ -253,11 +315,18 @@ type pathVar struct {
func (r request) buildRequest() *http.Request {
var req *http.Request
var err error
if r.method == http.MethodPost {
req, _ = post(r.url, r.values)
if len(r.multipartReq) > 0 {
req, err = postMultipart(r.url, r.multipartReq)
} else if r.method == http.MethodPost {
req, err = post(r.url, r.values)
} else {
req, _ = get(r.url, r.values)
req, err = get(r.url, r.values)
}
if err != nil {
log.Print(err)
}
if r.pathVar != nil {

View File

@ -147,6 +147,10 @@ func restrictedRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain)
}
func publicRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain) {
fh := c.FileHandler{
Context: ctx,
}
router.Handle("/", chain.Then(useTemplateHandler(ctx, c.ListArticlesHandler))).Methods("GET")
router.Handle("/articles/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
router.Handle("/articles/category/{categorySlug}/{page}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
@ -161,7 +165,8 @@ func publicRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain) {
router.Handle("/site/{site}", chain.Then(useTemplateHandler(ctx, c.GetSiteHandler))).Methods("GET")
router.Handle("/file/{filename}", chain.Then(c.FileGetHandler(ctx))).Methods("GET")
router.Handle("/file/{filename}", chain.ThenFunc(fh.FileGetHandler)).Methods("GET")
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, c.LoginHandler))).Methods("GET")
router.Handle("/admin", chain.Then(useTemplateHandler(ctx, c.LoginPostHandler))).Methods("POST")