package image_test import ( "bytes" "fmt" "io/ioutil" "log" "mime/multipart" "net/http" "net/http/httptest" "net/textproto" "os" "path" "path/filepath" "runtime" "strings" "testing" "github.com/go-chi/chi" "github.com/gomodule/redigo/redis" "github.com/rafaeljusto/redigomock" _ "github.com/stretchr/testify/mock" db "git.ofmax.li/iserv/internal/db/redis" "git.ofmax.li/iserv/internal/image" "go.ofmax.li/tmpl" ) var ( projectRoot string emptyBody *bytes.Buffer = bytes.NewBuffer([]byte(``)) ) func init() { _, filename, _, _ := runtime.Caller(0) projectRoot := path.Join(path.Dir(filename), "../..") err := os.Chdir(projectRoot) if err != nil { panic(err) } } // from go lang src https://golang.org/src/mime/multipart/writer.go // there seems to be no way to set the content type. var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") func escapeQuotes(s string) string { return quoteEscaper.Replace(s) } // end go lang src func createHeader(fieldname, filename, contentType string) textproto.MIMEHeader { h := make(textproto.MIMEHeader) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, escapeQuotes(fieldname), escapeQuotes(filename))) h.Set("Content-Type", escapeQuotes(contentType)) return h } func prepareRequest(t *testing.T, filename, mimeType string) (*multipart.Writer, *bytes.Buffer, error) { fileDir, _ := os.Getwd() fileName := filename filePath := path.Join(fileDir, fileName) body := &bytes.Buffer{} writer := multipart.NewWriter(body) header := createHeader("file", filePath, mimeType) part, err := writer.CreatePart(header) if err != nil { t.Errorf("wtf %s", err) } file, err := os.Open(filePath) if err != nil { t.Errorf("couldnt open: %s", err) } defer file.Close() fileBytes, err := ioutil.ReadAll(file) if err != nil { t.Errorf("couldn't read buffer: %s", fileBytes) } part.Write(fileBytes) writer.Close() return writer, body, err } func prepareImage() (*redigomock.Conn, image.Handler) { // setup redis for tests conn := redigomock.NewConn() pool := &redis.Pool{ // Return the same connection mock for each Get() call. Dial: func() (redis.Conn, error) { return conn, nil }, MaxIdle: 10, } repo := db.NewRedisImageRepo(pool) // setup image package imageBuildDir, err := ioutil.TempDir("", "test-images") if err != nil { log.Fatal(err) } cwd, _ := os.Getwd() base := filepath.Join(cwd, "templates") renderer, err := tmpl.NewHTMLTmpl(base) if err != nil { log.Fatal(err) } defer os.RemoveAll(imageBuildDir) imageService := image.NewService(repo, imageBuildDir, renderer) imageHandler := image.NewHandler(imageService) return conn, imageHandler } func TestPostImage(t *testing.T) { // setup redis for tests conn := redigomock.NewConn() pool := &redis.Pool{ // Return the same connection mock for each Get() call. Dial: func() (redis.Conn, error) { return conn, nil }, MaxIdle: 10, } conn.Command("HMSET", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()) repo := db.NewRedisImageRepo(pool) // setup image package imageBuildDir, err := ioutil.TempDir("", "test-images") if err != nil { t.Fatal(err) } cwd, _ := os.Getwd() base := filepath.Join(cwd, "templates") renderer, err := tmpl.NewHTMLTmpl(base) if err != nil { log.Fatal(err) } defer os.RemoveAll(imageBuildDir) imageService := image.NewService(repo, imageBuildDir, renderer) imageHandler := image.NewHandler(imageService) handler := http.HandlerFunc(imageHandler.PostImage) // prep test with png testImagePath := filepath.Join(projectRoot, "internal/image/t.png") writer, body, err := prepareRequest(t, testImagePath, "image/png") if err != nil { t.Fatal(err) } w := httptest.NewRecorder() r, err := http.NewRequest("POST", "http://example.com", body) if err != nil { t.Errorf("%s", err) } r.Header.Add("Content-Type", writer.FormDataContentType()) handler.ServeHTTP(w, r) response := w.Result() if response.StatusCode != http.StatusCreated { t.Errorf("image create status 201") } // incorrect mime testImagePDF := filepath.Join(projectRoot, "internal/image/t.pdf") writer, body, err = prepareRequest(t, testImagePDF, "application/pdf") if err != nil { t.Fatal(err) } w = httptest.NewRecorder() r, err = http.NewRequest("POST", "http://example.com", body) if err != nil { t.Fatal(err) } r.Header.Add("Content-Type", writer.FormDataContentType()) handler.ServeHTTP(w, r) response = w.Result() if response.StatusCode != http.StatusBadRequest { t.Errorf("image create not 400") } // sneaky mimetype check writer, body, err = prepareRequest(t, testImagePath, "image/jpeg") if err != nil { t.Fatal(err) } w = httptest.NewRecorder() r, err = http.NewRequest("POST", "http://example.com", body) if err != nil { t.Errorf("%s", err) } r.Header.Add("Content-Type", writer.FormDataContentType()) handler.ServeHTTP(w, r) response = w.Result() if response.StatusCode != http.StatusBadRequest { t.Errorf("image create not 400") } // test image write path && ext pngs, err := filepath.Glob(filepath.Join(imageBuildDir)) if err != nil { t.Fatal(err) } if len(pngs) != 1 { t.Error("expected only 1 image to be found") } } func TestGetImage(t *testing.T) { conn, imageHandler := prepareImage() getImageResults := []string{"file_path", "imge/name.jpg", "created_at", "'feb 12'", "user_id", "111", "mime_type", "image/jpg", "title", "mine", "desc", "'hoo har'"} conn.Command("HGETALL", "v1imagepost:fooo").ExpectSlice(getImageResults) m := chi.NewRouter() m.Get("/i/{fileName}", imageHandler.GetImage) srv := httptest.NewServer(m) defer srv.Close() // TODO https://github.com/go-chi/chi/blob/39504566046f2c13f4d059bccfe0fe06ed67aab3/mux_test.go#L1662 r, err := http.NewRequest("GET", fmt.Sprintf("%s/i/fooo", srv.URL), emptyBody) if err != nil { t.Errorf("%s", err) } resp, err := http.DefaultClient.Do(r) if err != nil { t.Errorf("%s", err) } body, err := ioutil.ReadAll(resp.Body) if err != nil { t.Errorf("%s", err) } fmt.Printf("hello: %s", body) }