From f0f48f4ddfd21e96181b4e357c6199f156095a47 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Fri, 6 Dec 2019 17:23:17 +0100 Subject: [PATCH] adding ROOT_PATH + tests --- Makefile | 5 +- build/lint/Dockerfile | 2 +- internal/app/xhttp/handler.go | 42 ++++++++++------ internal/app/xhttp/handler_test.go | 32 +++++++------ internal/app/xhttp/middleware.go | 6 +-- internal/app/xhttp/xhttp.go | 15 +++--- internal/app/xhttp/xhttp_test.go | 74 +++++++++++++++++++++-------- internal/pkg/conf/conf.go | 21 ++++++++ internal/pkg/conf/conf_test.go | 24 ++++++++++ internal/pkg/xassert/string.go | 61 ++++++++++++++++++++++++ internal/pkg/xassert/string_test.go | 24 ++++++++++ 11 files changed, 244 insertions(+), 62 deletions(-) diff --git a/Makefile b/Makefile index ece45530..8fece350 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ VERSION=snapshot DOCKER_USER= DOCKER_PASSWORD= DOCKER_REPOSITORY=thecodingmachine -GOLANGCI_LINT_VERSION=1.19.1 +GOLANGCI_LINT_VERSION=1.20.1 CODE_COVERAGE=0 TINI_VERSION=0.18.0 MAXIMUM_WAIT_TIMEOUT=30.0 @@ -15,6 +15,7 @@ DEFAULT_LISTEN_PORT=3000 DISABLE_GOOGLE_CHROME=0 DISABLE_UNOCONV=0 LOG_LEVEL=INFO +ROOT_PATH=/ DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=1048576 # build the base Docker image. @@ -55,7 +56,7 @@ image: # start the API using previously built Docker image. gotenberg: - docker run -it --rm -e MAXIMUM_WAIT_TIMEOUT=$(MAXIMUM_WAIT_TIMEOUT) -e MAXIMUM_WAIT_DELAY=$(MAXIMUM_WAIT_DELAY) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_WEBHOOK_URL_TIMEOUT=$(DEFAULT_WEBHOOK_URL_TIMEOUT) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_LISTEN_PORT=$(DEFAULT_LISTEN_PORT) -e DISABLE_GOOGLE_CHROME=$(DISABLE_GOOGLE_CHROME) -e DISABLE_UNOCONV=$(DISABLE_UNOCONV) -e LOG_LEVEL=$(LOG_LEVEL) -e DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REPOSITORY)/gotenberg:$(VERSION) + docker run -it --rm -e MAXIMUM_WAIT_TIMEOUT=$(MAXIMUM_WAIT_TIMEOUT) -e MAXIMUM_WAIT_DELAY=$(MAXIMUM_WAIT_DELAY) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_WEBHOOK_URL_TIMEOUT=$(DEFAULT_WEBHOOK_URL_TIMEOUT) -e MAXIMUM_WEBHOOK_URL_TIMEOUT=$(MAXIMUM_WEBHOOK_URL_TIMEOUT) -e DEFAULT_LISTEN_PORT=$(DEFAULT_LISTEN_PORT) -e DISABLE_GOOGLE_CHROME=$(DISABLE_GOOGLE_CHROME) -e DISABLE_UNOCONV=$(DISABLE_UNOCONV) -e LOG_LEVEL=$(LOG_LEVEL) -e ROOT_PATH=$(ROOT_PATH) -e DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE=$(DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE) -p "$(DEFAULT_LISTEN_PORT):$(DEFAULT_LISTEN_PORT)" $(DOCKER_REPOSITORY)/gotenberg:$(VERSION) # publish Gotenberg images according to version. publish: diff --git a/build/lint/Dockerfile b/build/lint/Dockerfile index fd08c9c5..42a8c054 100644 --- a/build/lint/Dockerfile +++ b/build/lint/Dockerfile @@ -33,4 +33,4 @@ RUN go mod download &&\ # Copy our code source. COPY --chown=gotenberg:gotenberg . . -CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen" ] \ No newline at end of file +CMD ["golangci-lint", "run" ,"--tests=false", "--enable-all", "--disable=dupl", "--disable=funlen", "--disable=wsl", "--disable=gocognit" ] \ No newline at end of file diff --git a/internal/app/xhttp/handler.go b/internal/app/xhttp/handler.go index 7c819bd2..0972a480 100644 --- a/internal/app/xhttp/handler.go +++ b/internal/app/xhttp/handler.go @@ -15,31 +15,45 @@ import ( "github.com/thecodingmachine/gotenberg/internal/pkg/xtime" ) -const ( - pingEndpoint string = "/ping" - mergeEndpoint string = "/merge" - convertGroupEndpoint string = "/convert" - htmlEndpoint string = "/html" - urlEndpoint string = "/url" - markdownEndpoint string = "/markdown" - officeEndpoint string = "/office" -) +func pingEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "ping") +} + +func mergeEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "merge") +} + +func htmlEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "convert/html") +} + +func urlEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "convert/url") +} + +func markdownEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "convert/markdown") +} + +func officeEndpoint(config conf.Config) string { + return fmt.Sprintf("%s%s", config.RootPath(), "convert/office") +} func isMultipartFormDataEndpoint(config conf.Config, path string) bool { var multipartFormDataEndpoints []string - multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint) + multipartFormDataEndpoints = append(multipartFormDataEndpoints, mergeEndpoint(config)) if !config.DisableGoogleChrome() { multipartFormDataEndpoints = append( multipartFormDataEndpoints, - fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), - fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), - fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), + htmlEndpoint(config), + urlEndpoint(config), + markdownEndpoint(config), ) } if !config.DisableUnoconv() { multipartFormDataEndpoints = append( multipartFormDataEndpoints, - fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), + officeEndpoint(config), ) } for _, endpoint := range multipartFormDataEndpoints { diff --git a/internal/app/xhttp/handler_test.go b/internal/app/xhttp/handler_test.go index 5602d90b..8b1f5ee7 100644 --- a/internal/app/xhttp/handler_test.go +++ b/internal/app/xhttp/handler_test.go @@ -19,49 +19,51 @@ func TestPingHandler(t *testing.T) { // should return 200. config := conf.DefaultConfig() srv := New(config) - req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + endpoint := pingEndpoint(config) + req := httptest.NewRequest(http.MethodGet, endpoint, nil) test.AssertStatusCode(t, http.StatusOK, srv, req) // should return 405 as Method is wrong. - req = httptest.NewRequest(http.MethodPost, pingEndpoint, nil) + req = httptest.NewRequest(http.MethodPost, endpoint, nil) test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req) } func TestMergeHandler(t *testing.T) { config := conf.DefaultConfig() srv := New(config) + endpoint := mergeEndpoint(config) // should return 200. body, contentType := test.MergeMultipartForm(t, nil) - req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req := httptest.NewRequest(http.MethodPost, endpoint, body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // should return 405 as Method is wrong. - req = httptest.NewRequest(http.MethodGet, mergeEndpoint, nil) + req = httptest.NewRequest(http.MethodGet, endpoint, nil) test.AssertStatusCode(t, http.StatusMethodNotAllowed, srv, req) // should return 415 as Content-Type is wrong. body, _ = test.MergeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, endpoint, body) test.AssertStatusCode(t, http.StatusUnsupportedMediaType, srv, req) // should return 400 as "waitTimeout" form field // value is < 0. body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "-1"}) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, endpoint, body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusBadRequest, srv, req) // should return 400 as "waitTimeout" form field // value is is > config.MaximumWaitTimeout(). body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "31"}) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, endpoint, body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusBadRequest, srv, req) // should return 400 as "waitTimeout" form field // value is invalid. body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "not a float"}) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, endpoint, body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusBadRequest, srv, req) // should return 504. body, contentType = test.MergeMultipartForm(t, map[string]string{string(resource.WaitTimeoutArgKey): "0"}) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, endpoint, body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusGatewayTimeout, srv, req) } @@ -69,7 +71,7 @@ func TestMergeHandler(t *testing.T) { func TestHTMLHandler(t *testing.T) { config := conf.DefaultConfig() srv := New(config) - endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint) + endpoint := htmlEndpoint(config) // should return 200. body, contentType := test.HTMLMultipartForm(t, nil) req := httptest.NewRequest(http.MethodPost, endpoint, body) @@ -224,7 +226,7 @@ func TestHTMLHandler(t *testing.T) { func TestURLHandler(t *testing.T) { config := conf.DefaultConfig() srv := New(config) - endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint) + endpoint := urlEndpoint(config) // should return 200. body, contentType := test.URLMultipartForm(t, nil) req := httptest.NewRequest(http.MethodPost, endpoint, body) @@ -379,7 +381,7 @@ func TestURLHandler(t *testing.T) { func TestMarkdownHandler(t *testing.T) { config := conf.DefaultConfig() srv := New(config) - endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint) + endpoint := markdownEndpoint(config) // should return 200. body, contentType := test.MarkdownMultipartForm(t, nil) req := httptest.NewRequest(http.MethodPost, endpoint, body) @@ -534,7 +536,7 @@ func TestMarkdownHandler(t *testing.T) { func TestOfficeHandler(t *testing.T) { config := conf.DefaultConfig() srv := New(config) - endpoint := fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint) + endpoint := officeEndpoint(config) // should return 200. body, contentType := test.OfficeMultipartForm(t, nil) req := httptest.NewRequest(http.MethodPost, endpoint, body) @@ -605,7 +607,7 @@ func TestWebhook(t *testing.T) { srv := New(config) // our custom server should receive the PDF. body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.WebhookURLArgKey): "http://localhost:3001/foo"}) - req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) err := <-status @@ -616,7 +618,7 @@ func TestResultFilename(t *testing.T) { config := conf.DefaultConfig() srv := New(config) body, contentType := test.MergeMultipartForm(t, map[string]string{string(resource.ResultFilenameArgKey): "foo.pdf"}) - req := httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req := httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) rec := httptest.NewRecorder() srv.ServeHTTP(rec, req) diff --git a/internal/app/xhttp/middleware.go b/internal/app/xhttp/middleware.go index ff722c6a..4049b9ac 100644 --- a/internal/app/xhttp/middleware.go +++ b/internal/app/xhttp/middleware.go @@ -30,7 +30,7 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc { // there is no need to create a Resource. if !isMultipartFormDataEndpoint(config, ctx.Path()) { // validate method for healthcheck endpoint. - if ctx.Path() == pingEndpoint && ctx.Request().Method != http.MethodGet { + if ctx.Path() == pingEndpoint(config) && ctx.Request().Method != http.MethodGet { err := doErr(ctx, echo.NewHTTPError(http.StatusMethodNotAllowed)) return ctx.LogRequestResult(err, false) } @@ -60,14 +60,14 @@ func contextMiddleware(config conf.Config) echo.MiddlewareFunc { } // loggerMiddleware logs the result of a request. -func loggerMiddleware() echo.MiddlewareFunc { +func loggerMiddleware(config conf.Config) echo.MiddlewareFunc { return func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { ctx := context.MustCastFromEchoContext(c) err := next(ctx) // we do not want to log healthcheck requests if // log level is not set to DEBUG. - isDebug := ctx.Path() == pingEndpoint + isDebug := ctx.Path() == pingEndpoint(config) return ctx.LogRequestResult(err, isDebug) } } diff --git a/internal/app/xhttp/xhttp.go b/internal/app/xhttp/xhttp.go index 7324997e..02db10e1 100644 --- a/internal/app/xhttp/xhttp.go +++ b/internal/app/xhttp/xhttp.go @@ -11,22 +11,21 @@ func New(config conf.Config) *echo.Echo { srv.HideBanner = true srv.HidePort = true srv.Use(contextMiddleware(config)) - srv.Use(loggerMiddleware()) + srv.Use(loggerMiddleware(config)) srv.Use(cleanupMiddleware()) srv.Use(errorMiddleware()) - srv.GET(pingEndpoint, pingHandler) - srv.POST(mergeEndpoint, mergeHandler) + srv.GET(pingEndpoint(config), pingHandler) + srv.POST(mergeEndpoint(config), mergeHandler) if config.DisableGoogleChrome() && config.DisableUnoconv() { return srv } - g := srv.Group(convertGroupEndpoint) if !config.DisableGoogleChrome() { - g.POST(htmlEndpoint, htmlHandler) - g.POST(urlEndpoint, urlHandler) - g.POST(markdownEndpoint, markdownHandler) + srv.POST(htmlEndpoint(config), htmlHandler) + srv.POST(urlEndpoint(config), urlHandler) + srv.POST(markdownEndpoint(config), markdownHandler) } if !config.DisableUnoconv() { - g.POST(officeEndpoint, officeHandler) + srv.POST(officeEndpoint(config), officeHandler) } return srv } diff --git a/internal/app/xhttp/xhttp_test.go b/internal/app/xhttp/xhttp_test.go index 801407ec..f4fe3df0 100644 --- a/internal/app/xhttp/xhttp_test.go +++ b/internal/app/xhttp/xhttp_test.go @@ -1,7 +1,6 @@ package xhttp import ( - "fmt" "net/http" "net/http/httptest" "os" @@ -28,31 +27,31 @@ func TestDisableChromeEndpoints(t *testing.T) { assert.Nil(t, err) srv := New(config) // Ping endpoint should return 200. - req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil) test.AssertStatusCode(t, http.StatusOK, srv, req) // Merge endpoint should return 200. body, contentType := test.MergeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // HTML endpoint should return 404. body, contentType = test.HTMLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // URL endpoint should return 404. body, contentType = test.URLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // Markdown endpoint should return 404. body, contentType = test.MarkdownMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body) + req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // Office endpoint should return 200. body, contentType = test.OfficeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body) + req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // finally... @@ -65,31 +64,31 @@ func TestDisableUnoconvEndpoints(t *testing.T) { assert.Nil(t, err) srv := New(config) // Ping endpoint should return 200. - req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil) test.AssertStatusCode(t, http.StatusOK, srv, req) // Merge endpoint should return 200. body, contentType := test.MergeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // HTML endpoint should return 200. body, contentType = test.HTMLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // URL endpoint should return 200. body, contentType = test.URLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // Markdown endpoint should return 200. body, contentType = test.MarkdownMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body) + req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // Office endpoint should return 404. body, contentType = test.OfficeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body) + req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // finally... @@ -102,34 +101,71 @@ func TestDisableChromeAndUnoconvEndpoints(t *testing.T) { assert.Nil(t, err) srv := New(config) // Ping endpoint should return 200. - req := httptest.NewRequest(http.MethodGet, pingEndpoint, nil) + req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil) test.AssertStatusCode(t, http.StatusOK, srv, req) // Merge endpoint should return 200. body, contentType := test.MergeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, mergeEndpoint, body) + req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusOK, srv, req) // HTML endpoint should return 404. body, contentType = test.HTMLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, htmlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // URL endpoint should return 404. body, contentType = test.URLMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, urlEndpoint), body) + req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // Markdown endpoint should return 404. body, contentType = test.MarkdownMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, markdownEndpoint), body) + req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // Office endpoint should return 404. body, contentType = test.OfficeMultipartForm(t, nil) - req = httptest.NewRequest(http.MethodPost, fmt.Sprintf("%s%s", convertGroupEndpoint, officeEndpoint), body) + req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body) req.Header.Set(echo.HeaderContentType, contentType) test.AssertStatusCode(t, http.StatusNotFound, srv, req) // finally... os.Setenv(conf.DisableGoogleChromeEnvVar, "0") os.Setenv(conf.DisableUnoconvEnvVar, "0") } + +func TestCustomRootPath(t *testing.T) { + os.Setenv(conf.RootPathEnvVar, "/foo/") + config, err := conf.FromEnv() + assert.Nil(t, err) + srv := New(config) + // Ping endpoint should return 200. + req := httptest.NewRequest(http.MethodGet, pingEndpoint(config), nil) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // Merge endpoint should return 200. + body, contentType := test.MergeMultipartForm(t, nil) + req = httptest.NewRequest(http.MethodPost, mergeEndpoint(config), body) + req.Header.Set(echo.HeaderContentType, contentType) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // HTML endpoint should return 200. + body, contentType = test.HTMLMultipartForm(t, nil) + req = httptest.NewRequest(http.MethodPost, htmlEndpoint(config), body) + req.Header.Set(echo.HeaderContentType, contentType) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // URL endpoint should return 200. + body, contentType = test.URLMultipartForm(t, nil) + req = httptest.NewRequest(http.MethodPost, urlEndpoint(config), body) + req.Header.Set(echo.HeaderContentType, contentType) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // Markdown endpoint should return 200. + body, contentType = test.MarkdownMultipartForm(t, nil) + req = httptest.NewRequest(http.MethodPost, markdownEndpoint(config), body) + req.Header.Set(echo.HeaderContentType, contentType) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // Office endpoint should return 200. + body, contentType = test.OfficeMultipartForm(t, nil) + req = httptest.NewRequest(http.MethodPost, officeEndpoint(config), body) + req.Header.Set(echo.HeaderContentType, contentType) + test.AssertStatusCode(t, http.StatusOK, srv, req) + // finally... + os.Setenv(conf.RootPathEnvVar, "/") +} diff --git a/internal/pkg/conf/conf.go b/internal/pkg/conf/conf.go index 89100c73..45d1bce1 100644 --- a/internal/pkg/conf/conf.go +++ b/internal/pkg/conf/conf.go @@ -34,6 +34,9 @@ const ( // LogLevelEnvVar contains the name // of the environment variable "LOG_LEVEL". LogLevelEnvVar string = "LOG_LEVEL" + // RootPathEnvVar contains the name + // of the environment variable "ROOT_PATH". + RootPathEnvVar string = "ROOT_PATH" // DefaultGoogleChromeRpccBufferSizeEnvVar contains the name // of the environment variable "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE". DefaultGoogleChromeRpccBufferSizeEnvVar string = "DEFAULT_GOOGLE_CHROME_RPCC_BUFFER_SIZE" @@ -51,6 +54,7 @@ type Config struct { disableGoogleChrome bool disableUnoconv bool logLevel xlog.Level + rootPath string maximumGoogleChromeRpccBufferSize int64 defaultGoogleChromeRpccBufferSize int64 } @@ -68,6 +72,7 @@ func DefaultConfig() Config { disableGoogleChrome: false, disableUnoconv: false, logLevel: xlog.InfoLevel, + rootPath: "/", maximumGoogleChromeRpccBufferSize: 104857600, // ~100 MB defaultGoogleChromeRpccBufferSize: 1048576, // 1 MB } @@ -163,6 +168,16 @@ func FromEnv() (Config, error) { if err != nil { return c, err } + rootPath, err := xassert.StringFromEnv( + RootPathEnvVar, + c.rootPath, + xassert.StringStartWith("/"), + xassert.StringEndWith("/"), + ) + c.rootPath = rootPath + if err != nil { + return c, err + } defaultGoogleChromeRpccBufferSize, err := xassert.Int64FromEnv( DefaultGoogleChromeRpccBufferSizeEnvVar, c.defaultGoogleChromeRpccBufferSize, @@ -242,6 +257,12 @@ func (c Config) LogLevel() xlog.Level { return c.logLevel } +// RootPath returns the rooth path from +// the configuration. +func (c Config) RootPath() string { + return c.rootPath +} + // MaximumGoogleChromeRpccBufferSize returns the maximum // Google Chrome rpcc buffer size from the configuration. func (c Config) MaximumGoogleChromeRpccBufferSize() int64 { diff --git a/internal/pkg/conf/conf_test.go b/internal/pkg/conf/conf_test.go index 45ed01da..678ac2aa 100644 --- a/internal/pkg/conf/conf_test.go +++ b/internal/pkg/conf/conf_test.go @@ -320,6 +320,29 @@ func TestLogLevelFromEnv(t *testing.T) { os.Unsetenv(LogLevelEnvVar) } +func TestRootPathFromEnv(t *testing.T) { + var ( + expected Config + result Config + err error + ) + // ROOT_PATH correctly set. + os.Setenv(RootPathEnvVar, "/foo/") + expected = DefaultConfig() + expected.rootPath = "/foo/" + result, err = FromEnv() + assert.Nil(t, err) + assert.Equal(t, expected, result) + os.Unsetenv(RootPathEnvVar) + // ROOT_PATH wrongly set. + os.Setenv(RootPathEnvVar, "foo") + expected = DefaultConfig() + result, err = FromEnv() + test.AssertError(t, err) + assert.Equal(t, expected, result) + os.Unsetenv(RootPathEnvVar) +} + func TestDefaultGoogleChromeRpccBufferSizeFromEnv(t *testing.T) { var ( expected Config @@ -368,6 +391,7 @@ func TestGetters(t *testing.T) { assert.Equal(t, result.disableGoogleChrome, result.DisableGoogleChrome()) assert.Equal(t, result.disableUnoconv, result.DisableUnoconv()) assert.Equal(t, result.logLevel, result.LogLevel()) + assert.Equal(t, result.rootPath, result.RootPath()) assert.Equal(t, result.maximumGoogleChromeRpccBufferSize, result.MaximumGoogleChromeRpccBufferSize()) assert.Equal(t, result.defaultGoogleChromeRpccBufferSize, result.DefaultGoogleChromeRpccBufferSize()) } diff --git a/internal/pkg/xassert/string.go b/internal/pkg/xassert/string.go index 540eee9b..d59bea8a 100644 --- a/internal/pkg/xassert/string.go +++ b/internal/pkg/xassert/string.go @@ -2,6 +2,7 @@ package xassert import ( "fmt" + "strings" "github.com/thecodingmachine/gotenberg/internal/pkg/xerror" ) @@ -54,7 +55,67 @@ func StringOneOf(values []string) RuleString { } } +type ruleStringStartWith struct { + *baseRuleString + startWith string +} + +func (r ruleStringStartWith) validate() error { + const op string = "xassert.ruleStringStartWith.validate" + if strings.HasPrefix(r.value, r.startWith) { + return nil + } + return xerror.Invalid( + op, + fmt.Sprintf("'%s' should start with '%s', got '%s'", r.key, r.startWith, r.value), + nil, + ) +} + +/* +StringStartWith returns a RuleString for +validating that a string starts with +given string. +*/ +func StringStartWith(startWith string) RuleString { + return ruleStringStartWith{ + &baseRuleString{}, + startWith, + } +} + +type ruleStringEndWith struct { + *baseRuleString + endWith string +} + +func (r ruleStringEndWith) validate() error { + const op string = "xassert.ruleStringEndWith.validate" + if strings.HasSuffix(r.value, r.endWith) { + return nil + } + return xerror.Invalid( + op, + fmt.Sprintf("'%s' should end with '%s', got '%s'", r.key, r.endWith, r.value), + nil, + ) +} + +/* +StringEndWith returns a RuleString for +validating that a string ends with +given string. +*/ +func StringEndWith(endWith string) RuleString { + return ruleStringEndWith{ + &baseRuleString{}, + endWith, + } +} + // Compile-time checks to ensure type implements desired interfaces. var ( _ = RuleString(new(ruleStringOneOf)) + _ = RuleString(new(ruleStringStartWith)) + _ = RuleString(new(ruleStringEndWith)) ) diff --git a/internal/pkg/xassert/string_test.go b/internal/pkg/xassert/string_test.go index 87ad8065..fb1c0d4e 100644 --- a/internal/pkg/xassert/string_test.go +++ b/internal/pkg/xassert/string_test.go @@ -18,3 +18,27 @@ func TestStringOfOne(t *testing.T) { err = rule.validate() test.AssertError(t, err) } + +func TestStringStartWith(t *testing.T) { + rule := StringStartWith("foo") + // should be OK. + rule.with("FOO", "foobarfoo") + err := rule.validate() + assert.Nil(t, err) + // should not be OK. + rule.with("FOO", "qux") + err = rule.validate() + test.AssertError(t, err) +} + +func TestStringEndWith(t *testing.T) { + rule := StringEndWith("foo") + // should be OK. + rule.with("FOO", "foobarfoo") + err := rule.validate() + assert.Nil(t, err) + // should not be OK. + rule.with("FOO", "qux") + err = rule.validate() + test.AssertError(t, err) +}