more tests + small refactoring (no more App struct)

This commit is contained in:
Julien Neuhart
2018-04-04 17:28:21 +02:00
parent ec2bb72db5
commit ab745ca409
11 changed files with 201 additions and 110 deletions

View File

@@ -1,62 +0,0 @@
// Package app is the entry point of the application.
package app
import (
"fmt"
"net/http"
"github.com/gulien/gotenberg/app/config"
"github.com/gulien/gotenberg/app/handlers"
"github.com/gulien/gotenberg/app/handlers/converter/process"
"github.com/gulien/gotenberg/app/logger"
"github.com/gorilla/mux"
)
// App gathers all data required for the application.
type App struct {
// version is the application version as defined in the main package.
version string
// config is the application configuration.
// It's populated thanks to the gotenberg.yml file.
config *config.AppConfig
// Server is the instance of http.Server used by the application.
Server *http.Server
}
// NewApp instantiates the application by loading the configuration from the
// gotenberg.yml file.
func NewApp(version string, configurationFilePath string) (*App, error) {
c, err := config.NewAppConfig(configurationFilePath)
if err != nil {
return nil, err
}
a := &App{}
a.version = version
a.config = c
// defines our application logging.
logger.SetLevel(a.config.Logs.Level)
logger.SetFormatter(a.config.Logs.Formatter)
// defines our application router.
r := mux.NewRouter()
r.Handle("/", handlers.GetHandlersChain())
a.Server = &http.Server{
Addr: fmt.Sprintf(":%s", a.config.Port),
Handler: r,
}
return a, nil
}
// Run starts the server.
func (a *App) Run() error {
process.Load(a.config.CommandsConfig)
logger.Infof("Starting Gotenberg version %s", a.version)
logger.Infof("Listening on port %s", a.config.Port)
return a.Server.ListenAndServe()
}

View File

@@ -1,30 +0,0 @@
package app
import (
"path/filepath"
"testing"
)
func TestNewApp(t *testing.T) {
// case 1: uses an empty configuration file path.
if _, err := NewApp("tests", ""); err == nil {
t.Error("App should not have been instantiated!")
}
// case 2: uses an correct configuration file.
path, _ := filepath.Abs("../_tests/configurations/gotenberg.yml")
if _, err := NewApp("tests", path); err != nil {
t.Error("App should have been instantiated!")
}
}
func TestRun(t *testing.T) {
path, _ := filepath.Abs("../_tests/configurations/gotenberg.yml")
a, _ := NewApp("tests", path)
quit := make(chan bool, 1)
go func() {
a.Run()
}()
quit <- true
}

View File

@@ -6,9 +6,9 @@ import (
"context"
"net/http"
"github.com/gulien/gotenberg/app/handlers/converter"
"github.com/gulien/gotenberg/app/converter"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
ghttp "github.com/gulien/gotenberg/app/http"
)
type key uint32

View File

@@ -6,9 +6,9 @@ import (
"net/http"
"os"
gfile "github.com/gulien/gotenberg/app/handlers/converter/file"
"github.com/gulien/gotenberg/app/handlers/converter/process"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
gfile "github.com/gulien/gotenberg/app/converter/file"
"github.com/gulien/gotenberg/app/converter/process"
ghttp "github.com/gulien/gotenberg/app/http"
"github.com/satori/go.uuid"
)

View File

@@ -6,7 +6,7 @@ import (
"io"
"os"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
ghttp "github.com/gulien/gotenberg/app/http"
"github.com/satori/go.uuid"
)

View File

@@ -0,0 +1,52 @@
package file
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestNewFile(t *testing.T) {
workingDir := "test"
os.Mkdir(workingDir, 0666)
// case 1: uses an empty reader.
if _, err := NewFile(workingDir, new(bytes.Buffer)); err == nil {
t.Error("File should not have been instantiated!")
}
// case 2: uses a reader from a wrong file type.
path, _ := filepath.Abs("../../../_tests/configurations/gotenberg.yml")
r, _ := os.Open(path)
defer r.Close()
if _, err := NewFile(workingDir, r); err == nil {
t.Error("File should not have been instantiated!")
}
// case 3: uses a reader from a correct file type.
path, _ = filepath.Abs("../../../_tests/file.pdf")
r, _ = os.Open(path)
defer r.Close()
if _, err := NewFile(workingDir, r); err != nil {
t.Error("File should have been instantiated!")
}
os.RemoveAll(workingDir)
}
func TestReworkFilePath(t *testing.T) {
workingDir := "test"
os.Mkdir(workingDir, 0666)
f := &File{
Path: MakeFilePath(workingDir),
Type: 999,
}
if _, err := reworkFilePath(workingDir, f); err == nil {
t.Error("It should not have been able to found the file extension!")
}
os.RemoveAll(workingDir)
}

View File

@@ -9,7 +9,7 @@ import (
"time"
"github.com/gulien/gotenberg/app/config"
gfile "github.com/gulien/gotenberg/app/handlers/converter/file"
gfile "github.com/gulien/gotenberg/app/converter/file"
)
var commandsConfig *config.CommandsConfig

View File

@@ -1,5 +1,5 @@
// Package handlers implements all functions on which a request will pass through.
package handlers
// Package app implements all functions on which a request will pass through.
package app
import (
"fmt"
@@ -7,9 +7,9 @@ import (
"net/http"
"os"
"github.com/gulien/gotenberg/app/handlers/context"
"github.com/gulien/gotenberg/app/handlers/converter"
ghttp "github.com/gulien/gotenberg/app/handlers/http"
"github.com/gulien/gotenberg/app/context"
"github.com/gulien/gotenberg/app/converter"
ghttp "github.com/gulien/gotenberg/app/http"
"github.com/gulien/gotenberg/app/logger"
"github.com/justinas/alice"

108
app/handlers_test.go Normal file
View File

@@ -0,0 +1,108 @@
package app
import (
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"github.com/gulien/gotenberg/app/context"
ghttp "github.com/gulien/gotenberg/app/http"
"github.com/justinas/alice"
)
func fakeSuccessHandler(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func makeBody(filesPaths ...string) io.Reader {
if len(filesPaths) == 1 {
file, _ := os.Open(filesPaths[0])
defer file.Close()
return file
}
// TODO
return nil
}
func TestEnforceContentLengthHandler(t *testing.T) {
h := alice.New(enforceContentLengthHandler).ThenFunc(fakeSuccessHandler)
// case 1: sends an empty request.
req := httptest.NewRequest(http.MethodPost, "/", nil)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}
// case 2: sends a body.
path, _ := filepath.Abs("../../_tests/file.docx")
req = httptest.NewRequest(http.MethodPost, "/", makeBody(path))
rr = httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
}
func TestEnforceContentTypeHandler(t *testing.T) {
h := alice.New(enforceContentTypeHandler).ThenFunc(fakeSuccessHandler)
// case 1: sends a wrong content type.
req := httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("Content-Type", string(ghttp.PDFContentType))
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusUnsupportedMediaType {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusUnsupportedMediaType)
}
// case 2: sends a good content type.
req = httptest.NewRequest(http.MethodPost, "/", nil)
req.Header.Set("Content-Type", string(ghttp.OctetStreamContentType))
rr = httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
}
func TestConvertHandler(t *testing.T) {
h := alice.New(convertHandler).ThenFunc(fakeSuccessHandler)
// case 1: sends a request without a content type in its context.
req := httptest.NewRequest(http.MethodPost, "/", nil)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusInternalServerError {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusInternalServerError)
}
// case 2: sends a request as without body.
req = context.WithContentType(httptest.NewRequest(http.MethodPost, "/", nil), ghttp.OctetStreamContentType)
rr = httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusInternalServerError {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusInternalServerError)
}
// case 3: sends a request as "multipart/form-data" without "files" key.
/*req = context.WithContentType(httptest.NewRequest(http.MethodPost, "/", nil), ghttp.MultipartFormDataContentType)
rr = httptest.NewRecorder()
h.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}*/
}

35
main.go
View File

@@ -9,13 +9,18 @@ package main
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
"github.com/gulien/gotenberg/app"
"github.com/gulien/gotenberg/app/config"
"github.com/gulien/gotenberg/app/converter/process"
"github.com/gulien/gotenberg/app/logger"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
@@ -28,29 +33,47 @@ const defaultConfigurationFilePath = "gotenberg.yml"
// main initializes the application, starts it, and handles
// graceful shutdown.
func main() {
a, err := app.NewApp(version, defaultConfigurationFilePath)
c, err := config.NewAppConfig(defaultConfigurationFilePath)
if err != nil {
logger.SetLevel(logrus.InfoLevel)
logger.Fatal(err)
os.Exit(1)
}
// defines our application logging.
logger.SetLevel(c.Logs.Level)
logger.SetFormatter(c.Logs.Formatter)
// defines our application router.
r := mux.NewRouter()
r.Handle("/", app.GetHandlersChain()).Methods(http.MethodPost)
// defines our server.
s := &http.Server{
Addr: fmt.Sprintf(":%s", c.Port),
Handler: r,
}
process.Load(c.CommandsConfig)
logger.Infof("Starting Gotenberg version %s", version)
logger.Infof("Listening on port %s", c.Port)
// runs our server in a goroutine so that it doesn't block.
go func() {
if err = a.Run(); err != nil {
if err = s.ListenAndServe(); err != nil {
logger.SetLevel(logrus.InfoLevel)
logger.Panic(err)
os.Exit(1)
}
}()
c := make(chan os.Signal, 1)
quit := make(chan os.Signal, 1)
// we'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
signal.Notify(quit, os.Interrupt)
// blocks until we receive our signal.
<-c
<-quit
// creates a deadline to wait for.
var wait time.Duration
@@ -59,7 +82,7 @@ func main() {
// doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
a.Server.Shutdown(ctx)
s.Shutdown(ctx)
logger.SetLevel(logrus.InfoLevel)
logger.Info("Bye!")