mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 04:12:16 +01:00
more tests + small refactoring (no more App struct)
This commit is contained in:
62
app/app.go
62
app/app.go
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net/http"
|
"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
|
type key uint32
|
||||||
@@ -6,9 +6,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
gfile "github.com/gulien/gotenberg/app/handlers/converter/file"
|
gfile "github.com/gulien/gotenberg/app/converter/file"
|
||||||
"github.com/gulien/gotenberg/app/handlers/converter/process"
|
"github.com/gulien/gotenberg/app/converter/process"
|
||||||
ghttp "github.com/gulien/gotenberg/app/handlers/http"
|
ghttp "github.com/gulien/gotenberg/app/http"
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
ghttp "github.com/gulien/gotenberg/app/handlers/http"
|
ghttp "github.com/gulien/gotenberg/app/http"
|
||||||
|
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
)
|
)
|
||||||
52
app/converter/file/file_test.go
Normal file
52
app/converter/file/file_test.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gulien/gotenberg/app/config"
|
"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
|
var commandsConfig *config.CommandsConfig
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// Package handlers implements all functions on which a request will pass through.
|
// Package app implements all functions on which a request will pass through.
|
||||||
package handlers
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gulien/gotenberg/app/handlers/context"
|
"github.com/gulien/gotenberg/app/context"
|
||||||
"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"
|
||||||
"github.com/gulien/gotenberg/app/logger"
|
"github.com/gulien/gotenberg/app/logger"
|
||||||
|
|
||||||
"github.com/justinas/alice"
|
"github.com/justinas/alice"
|
||||||
108
app/handlers_test.go
Normal file
108
app/handlers_test.go
Normal 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
35
main.go
@@ -9,13 +9,18 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gulien/gotenberg/app"
|
"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/gulien/gotenberg/app/logger"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,29 +33,47 @@ const defaultConfigurationFilePath = "gotenberg.yml"
|
|||||||
// main initializes the application, starts it, and handles
|
// main initializes the application, starts it, and handles
|
||||||
// graceful shutdown.
|
// graceful shutdown.
|
||||||
func main() {
|
func main() {
|
||||||
a, err := app.NewApp(version, defaultConfigurationFilePath)
|
c, err := config.NewAppConfig(defaultConfigurationFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.SetLevel(logrus.InfoLevel)
|
logger.SetLevel(logrus.InfoLevel)
|
||||||
logger.Fatal(err)
|
logger.Fatal(err)
|
||||||
os.Exit(1)
|
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.
|
// runs our server in a goroutine so that it doesn't block.
|
||||||
go func() {
|
go func() {
|
||||||
if err = a.Run(); err != nil {
|
if err = s.ListenAndServe(); err != nil {
|
||||||
logger.SetLevel(logrus.InfoLevel)
|
logger.SetLevel(logrus.InfoLevel)
|
||||||
logger.Panic(err)
|
logger.Panic(err)
|
||||||
os.Exit(1)
|
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)
|
// we'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
|
||||||
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
|
// 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.
|
// blocks until we receive our signal.
|
||||||
<-c
|
<-quit
|
||||||
|
|
||||||
// creates a deadline to wait for.
|
// creates a deadline to wait for.
|
||||||
var wait time.Duration
|
var wait time.Duration
|
||||||
@@ -59,7 +82,7 @@ func main() {
|
|||||||
|
|
||||||
// doesn't block if no connections, but will otherwise wait
|
// doesn't block if no connections, but will otherwise wait
|
||||||
// until the timeout deadline.
|
// until the timeout deadline.
|
||||||
a.Server.Shutdown(ctx)
|
s.Shutdown(ctx)
|
||||||
logger.SetLevel(logrus.InfoLevel)
|
logger.SetLevel(logrus.InfoLevel)
|
||||||
|
|
||||||
logger.Info("Bye!")
|
logger.Info("Bye!")
|
||||||
|
|||||||
Reference in New Issue
Block a user