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

Phillip/test fileupload #55

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func main() {
server := http.AppServer{
Host: c.Host,
Static: c.StaticDir,
Files: c.FilesDir,
Logger: log.New(logDst, "", log.LstdFlags),
UserService: userService.New(repository.UserRepository),
UserRepository: repository.UserRepository,
Expand Down
7 changes: 7 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type config struct {
MongoUser,
MongoPass,
StaticDir,
FilesDir,
LogFile string
}

Expand Down Expand Up @@ -49,6 +50,11 @@ func GetConfig() config {
staticDir = "./static"
}

filesDir, ok := os.LookupEnv("FILES_DIR")
if !ok {
filesDir = "./files"
}

logFile, ok := os.LookupEnv("LOGFILE")
if !ok {
logFile = ""
Expand All @@ -62,6 +68,7 @@ func GetConfig() config {
MongoUser: mongoUser,
MongoPass: mongPass,
StaticDir: staticDir,
FilesDir: filesDir,
LogFile: logFile,
}
}
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func TestGetConfig(t *testing.T) {
{"MONGO_USER", "testuser"},
{"MONGO_PASS", "testpass"},
{"STATIC_DIR", "testdir"},
{"FILES_DIR", "testfilesdir"},
{"LOGFILE", "backend.log"},
}

Expand All @@ -35,6 +36,7 @@ func TestGetConfig(t *testing.T) {
"testuser",
"testpass",
"testdir",
"testfilesdir",
"backend.log"}},
{"unset", true,
config{
Expand All @@ -45,6 +47,7 @@ func TestGetConfig(t *testing.T) {
"",
"",
"./static",
"./files",
""}},
}

Expand Down
1 change: 1 addition & 0 deletions courseEntry.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type CourseEntryDeleter interface {

type CourseEntryService interface {
StoreCourseEntry(entry *CourseEntry, cfu CourseFindUpdater) (err error, courseEntry *CourseEntry)
StoreCourseEntryFiles(files [][]byte, id string, date time.Time) (paths []string, err error)
UpdateCourseEntry(*CourseEntry) (*CourseEntry, error)
DeleteCourseEntry(entryID string, courseID string, updater CourseUpdater) error
}
14 changes: 11 additions & 3 deletions http/courseEntryHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (a *AppServer) PostCourseEntryHandler() httprouter.Handle {
type request struct {
Date time.Time `json:"date"`
Message string `json:"message"`
Pictures []string `json:"pictures"`
Pictures [][]byte `json:"pictures"`
Published bool `json:"published"`
}
type response struct {
Expand Down Expand Up @@ -43,9 +43,17 @@ func (a *AppServer) PostCourseEntryHandler() httprouter.Handle {
return
}

pURLs, err := url.URLifyStrings(request.Pictures)
paths, err := a.CourseEntryService.StoreCourseEntryFiles(request.Pictures, id, request.Date)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
a.Logger.Printf("error storing files: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

pURLs, err := url.URLifyStrings(paths)
if err != nil {
a.Logger.Printf("error parsing urls: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

Expand Down
20 changes: 18 additions & 2 deletions http/courseEntryHandler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"strings"
"testing"
"time"
)

func TestAppServer_PostCourseEntryHandler(t *testing.T) {
Expand All @@ -21,10 +22,11 @@ func TestAppServer_PostCourseEntryHandler(t *testing.T) {
invokeStore bool
status int
}{
{"success", `{"message": "success"}"`, "5b23bbdc2bfa844c41a9f135", true, 200},
{"success", `{"message": "success", "pictures": ["c3VwZXJzZWNyZXQ=\n"]}"`, "5b23bbdc2bfa844c41a9f135", true, 200},
{"bad json", `{"message":`, "5b23bbdc2bfa844c41a9f135", false, 400},
{"bad objectid", `{"message": "success"}"`, "5b23bbdc2bfa844c41a9f35", false, 400},
{"bad urls", `{"message": "success", "pictures": ["htttp\\:.orgcom"]}"`, "5b23bbdc2bfa844c41a9f135", false, 400},
{"unparsable url", `{"message": "success", "pictures": ["c3VwZXJzZWNyZXQ=\n", "c3VwZXJzZWNyZXQ=\n"]}"`, "5b23bbdc2bfa844c41a9f135", false, 500},
{"error storing files", `{"message": "success", "pictures": ["c3VwZXJzZWNyZXQ=\n", "c3VwZXJzZWNyZXQ=\n", "c3VwZXJzZWNyZXQ=\n"]}"`, "5b23bbdc2bfa844c41a9f135", false, 500},
{"error storing", `{"message": "success"}"`, "5b23bbdc2bfa844c41a9f136", true, 500},
}

Expand All @@ -35,6 +37,20 @@ func TestAppServer_PostCourseEntryHandler(t *testing.T) {
}
return nil, entry
}

service.StoreCourseEntryFilesFn = func(files [][]byte, id string, date time.Time) ([]string, error) {
switch len(files) {
case 0:
return []string{}, nil
case 1:
return []string{"/test/test/1.jpg"}, nil
case 2:
return []string{":test.com/cant/parse/this//"}, nil
default:
return []string{}, errors.New("invalid something error while storing files")
}
}

a := AppServer{CourseEntryService: &service, Logger: log.New(os.Stdout, "", 0)}

for _, v := range testCases {
Expand Down
3 changes: 3 additions & 0 deletions http/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
type AppServer struct {
Host string
Static string
Files string
Logger *log.Logger
UserService eduboard.UserService
UserRepository eduboard.UserRepository
Expand All @@ -27,11 +28,13 @@ func (a *AppServer) initialize() {
privateChain := Chain(protected, Logger(a.Logger), CORS, NewAuthMiddleware(a.UserService))
publicChain := Chain(public, Logger(a.Logger), CORS)
staticChain := Chain(http.FileServer(http.Dir(a.Static)), Logger(a.Logger), CORS)
filesChain := Chain(http.StripPrefix("/files", http.FileServer(http.Dir(a.Files))), Logger(a.Logger), CORS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be Chain(http.FileServer(http.Dir(a.Files))), http.StripPrefix("/files")...? The first argument of Chain is the final handler.

Copy link
Contributor Author

@phiphi282 phiphi282 Jun 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

http.StripPrefix takes in a string that will be removed from the beginning from the handlers path
So it needs to take in both arguments


mux := http.NewServeMux()
mux.Handle("/api/v1/", privateChain)
mux.Handle("/api/", publicChain)
mux.Handle("/", staticChain)
mux.Handle("/files/", filesChain)

a.httpServer = &http.Server{
Addr: a.Host,
Expand Down
9 changes: 9 additions & 0 deletions mock/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mock

import (
"github.com/eduboard/backend"
"time"
)

type CourseService struct {
Expand Down Expand Up @@ -60,6 +61,9 @@ type CourseEntryService struct {
StoreCourseEntryFn func(entry *eduboard.CourseEntry, cfu eduboard.CourseFindUpdater) (err error, courseEntry *eduboard.CourseEntry)
StoreCourseEntryFnInvoked bool

StoreCourseEntryFilesFn func(files [][]byte, id string, date time.Time) ([]string, error)
StoreCourseEntryFilesFnInvoked bool

UpdateCourseEntryFn func(*eduboard.CourseEntry) (*eduboard.CourseEntry, error)
UpdateCourseEntryFnInvoked bool

Expand All @@ -72,6 +76,11 @@ func (cSM *CourseEntryService) StoreCourseEntry(entry *eduboard.CourseEntry, cfu
return cSM.StoreCourseEntryFn(entry, cfu)
}

func (cSM *CourseEntryService) StoreCourseEntryFiles(files [][]byte, id string, date time.Time) ([]string, error) {
cSM.StoreCourseEntryFilesFnInvoked = true
return cSM.StoreCourseEntryFilesFn(files, id, date)
}

func (cSM *CourseEntryService) UpdateCourseEntry(e *eduboard.CourseEntry) (*eduboard.CourseEntry, error) {
cSM.UpdateCourseEntryFnInvoked = true
return cSM.UpdateCourseEntryFn(e)
Expand Down
11 changes: 11 additions & 0 deletions mock/upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mock

type Uploader struct {
UploadFileFn func(file []byte, course string, filename string) (string, error)
UploadFileFnInvoked bool
}

func (u *Uploader) UploadFile(file []byte, course string, filename string) (string, error) {
u.UploadFileFnInvoked = true
return u.UploadFileFn(file, course, filename)
}
21 changes: 21 additions & 0 deletions service/courseEntryService/courseEntryService.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ package courseEntryService

import (
"github.com/eduboard/backend"
"github.com/eduboard/backend/upload"
"github.com/pkg/errors"
"gopkg.in/mgo.v2/bson"
"strconv"
"time"
)

func New(repository eduboard.CourseEntryRepository) CourseEntryService {
return CourseEntryService{
ER: repository,
u: &upload.Uploader{},
}
}

type Uploader interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this Interface used for? Wasn't the file uploaded to this server?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The interface is used for mocking the upload function in our tests

UploadFile(file []byte, course string, filename string) (string, error)
}

type CourseEntryService struct {
ER eduboard.CourseEntryRepository
u Uploader
}

func (cES CourseEntryService) StoreCourseEntry(entry *eduboard.CourseEntry, cfu eduboard.CourseFindUpdater) (error, *eduboard.CourseEntry) {
Expand All @@ -39,6 +48,18 @@ func (cES CourseEntryService) StoreCourseEntry(entry *eduboard.CourseEntry, cfu
return nil, entry
}

func (cES CourseEntryService) StoreCourseEntryFiles(files [][]byte, id string, date time.Time) ([]string, error) {
paths := []string{}
for key, file := range files {
url, err := cES.u.UploadFile(file, id, date.String()+"_"+strconv.Itoa(key))
if err != nil {
return []string{}, err
}
paths = append(paths, url)
}
return paths, nil
}

func (cES CourseEntryService) UpdateCourseEntry(*eduboard.CourseEntry) (*eduboard.CourseEntry, error) {
return &eduboard.CourseEntry{}, nil
}
Expand Down
35 changes: 35 additions & 0 deletions service/courseEntryService/courseEntryService_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"gopkg.in/mgo.v2/bson"
"testing"
"time"
)

func TestNew(t *testing.T) {
Expand Down Expand Up @@ -72,6 +73,40 @@ func TestCourseEntryService_StoreCourseEntry(t *testing.T) {
}
}

func TestCourseEntryService_StoreCourseEntryFiles(t *testing.T) {
var testCases = []struct {
name string
error bool
files [][]byte
course string
}{
{"success", false, [][]byte{[]byte("test")}, "success"},
{"error", true, [][]byte{[]byte("moep")}, "failure"},
}
uploader := mock.Uploader{}
uploader.UploadFileFn = func(file []byte, course string, filename string) (string, error) {
if course == "success" {
return filename, nil
}
return "", errors.New("error uploading files")
}
service := CourseEntryService{u: &uploader}

for _, v := range testCases {
t.Run(v.name, func(t *testing.T) {
uploader.UploadFileFnInvoked = false

_ , err:= service.StoreCourseEntryFiles(v.files, v.course, time.Now())
if v.error {
assert.NotNil(t, err, "error nil")
return
}
assert.Nil(t, err, "error not nil")

})
}
}

func TestCourseEntryService_DeleteCourseEntry(t *testing.T) {
successEntry := "5b23c8d5382d33000150681e"
failureEntry := "5b23c8d5382d33000150681f"
Expand Down
35 changes: 35 additions & 0 deletions upload/upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package upload

import (
"errors"
"io/ioutil"
"net/http"
"os"
"strings"
)

type Uploader struct{}

func (u *Uploader) UploadFile(file []byte, course string, filename string) (string, error) {
dir := string("./files/" + course + "/")
path := string(dir + filename)
serverFile := string("/files/" + course + "/" + filename)

contentType := http.DetectContentType(file)

if strings.Split(contentType, "/")[0] != "image" {
return "", errors.New("filetype not supported")
}

if _, err := os.Stat(dir); os.IsNotExist(err) {
os.MkdirAll(dir, os.ModePerm)
}

err := ioutil.WriteFile(path, file, 0644)
if err != nil {
panic(err)
return "", err
}

return serverFile, nil
}