chore: minor refactor of webhook module

This commit is contained in:
Julien Neuhart
2023-11-20 20:39:49 +01:00
parent 4d1a569269
commit 793e65bac0
6 changed files with 159 additions and 153 deletions

View File

@@ -16,9 +16,9 @@ import (
type client struct { type client struct {
url string url string
method string method string
errorURL string errorUrl string
errorMethod string errorMethod string
extraHTTPHeaders map[string]string extraHttpHeaders map[string]string
startTime time.Time startTime time.Time
client *retryablehttp.Client client *retryablehttp.Client
@@ -29,7 +29,7 @@ type client struct {
func (c client) send(body io.Reader, headers map[string]string, erroed bool) error { func (c client) send(body io.Reader, headers map[string]string, erroed bool) error {
URL := c.url URL := c.url
if erroed { if erroed {
URL = c.errorURL URL = c.errorUrl
} }
method := c.method method := c.method
@@ -45,7 +45,7 @@ func (c client) send(body io.Reader, headers map[string]string, erroed bool) err
req.Header.Set("User-Agent", "Gotenberg") req.Header.Set("User-Agent", "Gotenberg")
// Extra HTTP headers are the custom headers from the user. // Extra HTTP headers are the custom headers from the user.
for key, value := range c.extraHTTPHeaders { for key, value := range c.extraHttpHeaders {
req.Header.Set(key, value) req.Header.Set(key, value)
} }

View File

@@ -1,3 +1,3 @@
// Package webhook provides a module which adds a middleware for uploading // Package webhook provides a module which adds a middleware for uploading
// output files to any destinations in an asynchronous fashion. // output files to any destination in an asynchronous fashion.
package webhook package webhook

View File

@@ -20,15 +20,14 @@ import (
"github.com/gotenberg/gotenberg/v7/pkg/modules/api" "github.com/gotenberg/gotenberg/v7/pkg/modules/api"
) )
func webhookMiddleware(w Webhook) api.Middleware { func webhookMiddleware(w *Webhook) api.Middleware {
return api.Middleware{ return api.Middleware{
Stack: api.MultipartStack, Stack: api.MultipartStack,
Handler: func() echo.MiddlewareFunc { Handler: func() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
webhookURL := c.Request().Header.Get("Gotenberg-Webhook-Url") webhookUrl := c.Request().Header.Get("Gotenberg-Webhook-Url")
if webhookUrl == "" {
if webhookURL == "" {
// No webhook URL, call the next middleware in the chain. // No webhook URL, call the next middleware in the chain.
return next(c) return next(c)
} }
@@ -37,9 +36,8 @@ func webhookMiddleware(w Webhook) api.Middleware {
cancel := c.Get("cancel").(context.CancelFunc) cancel := c.Get("cancel").(context.CancelFunc)
// Do we have a webhook error URL in case of... error? // Do we have a webhook error URL in case of... error?
webhookErrorURL := c.Request().Header.Get("Gotenberg-Webhook-Error-Url") webhookErrorUrl := c.Request().Header.Get("Gotenberg-Webhook-Error-Url")
if webhookErrorUrl == "" {
if webhookErrorURL == "" {
return api.WrapError( return api.WrapError(
errors.New("empty webhook error URL"), errors.New("empty webhook error URL"),
api.NewSentinelHTTPError(http.StatusBadRequest, "Invalid 'Gotenberg-Webhook-Error-Url' header: empty value or header not provided"), api.NewSentinelHTTPError(http.StatusBadRequest, "Invalid 'Gotenberg-Webhook-Error-Url' header: empty value or header not provided"),
@@ -72,12 +70,12 @@ func webhookMiddleware(w Webhook) api.Middleware {
return nil return nil
} }
err := filter(webhookURL, "Gotenberg-Webhook-Url", w.allowList, w.denyList) err := filter(webhookUrl, "Gotenberg-Webhook-Url", w.allowList, w.denyList)
if err != nil { if err != nil {
return fmt.Errorf("filter webhook URL: %w", err) return fmt.Errorf("filter webhook URL: %w", err)
} }
err = filter(webhookErrorURL, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList) err = filter(webhookErrorUrl, "Gotenberg-Webhook-Error-Url", w.errorAllowList, w.errorDenyList)
if err != nil { if err != nil {
return fmt.Errorf("filter webhook error URL: %w", err) return fmt.Errorf("filter webhook error URL: %w", err)
} }
@@ -135,11 +133,11 @@ func webhookMiddleware(w Webhook) api.Middleware {
} }
client := &client{ client := &client{
url: webhookURL, url: webhookUrl,
method: webhookMethod, method: webhookMethod,
errorURL: webhookErrorURL, errorUrl: webhookErrorUrl,
errorMethod: webhookErrorMethod, errorMethod: webhookErrorMethod,
extraHTTPHeaders: extraHTTPHeaders, extraHttpHeaders: extraHTTPHeaders,
startTime: c.Get("startTime").(time.Time), startTime: c.Get("startTime").(time.Time),
client: &retryablehttp.Client{ client: &retryablehttp.Client{

View File

@@ -45,8 +45,8 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
return req return req
} }
buildWebhookModule := func() Webhook { buildWebhookModule := func() *Webhook {
return Webhook{ return &Webhook{
allowList: regexp.MustCompile(""), allowList: regexp.MustCompile(""),
denyList: regexp.MustCompile(""), denyList: regexp.MustCompile(""),
errorAllowList: regexp.MustCompile(""), errorAllowList: regexp.MustCompile(""),
@@ -58,238 +58,240 @@ func TestWebhookMiddlewareGuards(t *testing.T) {
} }
} }
for i, tc := range []struct { for _, tc := range []struct {
scenario string
request *http.Request request *http.Request
mod Webhook mod *Webhook
next echo.HandlerFunc next echo.HandlerFunc
expectErr bool expectError bool
expectHTTPErr bool expectHttpError bool
expectHTTPStatus int expectHttpStatus int
}{ }{
{ {
request: buildMultipartFormDataRequest(), scenario: "no webhook URL, skip middleware",
mod: buildWebhookModule(), request: buildMultipartFormDataRequest(),
mod: buildWebhookModule(),
next: func() echo.HandlerFunc { next: func() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
return nil return nil
} }
}(), }(),
expectError: false,
expectHttpError: false,
}, },
{ {
scenario: "no webhook error URL",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "webhook URL is not allowed",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
return req return req
}(), }(),
mod: func() Webhook { mod: func() *Webhook {
mod := buildWebhookModule() mod := buildWebhookModule()
mod.allowList = regexp.MustCompile("bar") mod.allowList = regexp.MustCompile("bar")
return mod return mod
}(), }(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusForbidden, expectHttpStatus: http.StatusForbidden,
}, },
{ {
scenario: "webhook URL is denied",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
return req return req
}(), }(),
mod: func() Webhook { mod: func() *Webhook {
mod := buildWebhookModule() mod := buildWebhookModule()
mod.denyList = regexp.MustCompile("foo") mod.denyList = regexp.MustCompile("foo")
return mod return mod
}(), }(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusForbidden, expectHttpStatus: http.StatusForbidden,
}, },
{ {
scenario: "webhook error URL is not allowed",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
return req return req
}(), }(),
mod: func() Webhook { mod: func() *Webhook {
mod := buildWebhookModule() mod := buildWebhookModule()
mod.errorAllowList = regexp.MustCompile("foo") mod.errorAllowList = regexp.MustCompile("foo")
return mod return mod
}(), }(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusForbidden, expectHttpStatus: http.StatusForbidden,
}, },
{ {
scenario: "webhook error URL is denied",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
return req return req
}(), }(),
mod: func() Webhook { mod: func() *Webhook {
mod := buildWebhookModule() mod := buildWebhookModule()
mod.errorDenyList = regexp.MustCompile("bar") mod.errorDenyList = regexp.MustCompile("bar")
return mod return mod
}(), }(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusForbidden, expectHttpStatus: http.StatusForbidden,
}, },
{ {
scenario: "invalid webhook method (GET)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Method", http.MethodGet) req.Header.Set("Gotenberg-Webhook-Method", http.MethodGet)
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "invalid webhook error method (GET)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "valid webhook method (POST) but invalid webhook error method (GET)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPost) req.Header.Set("Gotenberg-Webhook-Method", http.MethodPost)
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "valid webhook method (PATH) but invalid webhook error method (GET)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPatch) req.Header.Set("Gotenberg-Webhook-Method", http.MethodPatch)
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "valid webhook method (PUT) but invalid webhook error method (GET)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPut) req.Header.Set("Gotenberg-Webhook-Method", http.MethodPut)
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet) req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
{ {
scenario: "invalid webhook extra HTTP headers",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Webhook-Url", "foo") req.Header.Set("Gotenberg-Webhook-Url", "foo")
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar") req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", "foo") req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", "foo")
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
expectErr: true, expectError: true,
expectHTTPErr: true, expectHttpError: true,
expectHTTPStatus: http.StatusBadRequest, expectHttpStatus: http.StatusBadRequest,
}, },
} { } {
srv := echo.New() t.Run(tc.scenario, func(t *testing.T) {
srv.HideBanner = true srv := echo.New()
srv.HidePort = true srv.HideBanner = true
srv.HidePort = true
c := srv.NewContext(tc.request, httptest.NewRecorder()) c := srv.NewContext(tc.request, httptest.NewRecorder())
ctx := &api.ContextMock{Context: &api.Context{}} ctx := &api.ContextMock{Context: &api.Context{}}
ctx.SetEchoContext(c) ctx.SetEchoContext(c)
c.Set("context", ctx.Context) c.Set("context", ctx.Context)
c.Set("cancel", func() context.CancelFunc { c.Set("cancel", func() context.CancelFunc {
return func() { return func() {
return return
}
}())
err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
if tc.expectError && err == nil {
t.Fatal("expected error but got none", err)
} }
}())
err := webhookMiddleware(tc.mod).Handler(tc.next)(c) if !tc.expectError && err != nil {
t.Fatalf("expected no error but got: %v", err)
if tc.expectErr && err == nil {
t.Errorf("test %d: expected error but got: %v", i, err)
}
if !tc.expectErr && err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err)
}
var httpErr api.HTTPError
isHTTPErr := errors.As(err, &httpErr)
if tc.expectHTTPErr && !isHTTPErr {
t.Errorf("test %d: expected HTTP error but got: %v", i, err)
}
if !tc.expectHTTPErr && isHTTPErr {
t.Errorf("test %d: expected no HTTP error but got one: %v", i, httpErr)
}
if err != nil && tc.expectHTTPErr && isHTTPErr {
status, _ := httpErr.HTTPError()
if status != tc.expectHTTPStatus {
t.Errorf("test %d: expected %d HTTP status code but got %d", i, tc.expectHTTPStatus, status)
} }
}
var httpErr api.HTTPError
isHTTPErr := errors.As(err, &httpErr)
if tc.expectHttpError && !isHTTPErr {
t.Errorf("expected an HTTP error but got: %v", err)
}
if !tc.expectHttpError && isHTTPErr {
t.Errorf("expected no HTTP error but got one: %v", httpErr)
}
if err != nil && tc.expectHttpError && isHTTPErr {
status, _ := httpErr.HTTPError()
if status != tc.expectHttpStatus {
t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status)
}
}
})
} }
} }
@@ -316,8 +318,8 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
return req return req
} }
buildWebhookModule := func() Webhook { buildWebhookModule := func() *Webhook {
return Webhook{ return &Webhook{
allowList: regexp.MustCompile(""), allowList: regexp.MustCompile(""),
denyList: regexp.MustCompile(""), denyList: regexp.MustCompile(""),
errorAllowList: regexp.MustCompile(""), errorAllowList: regexp.MustCompile(""),
@@ -330,21 +332,23 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
} }
} }
for i, tc := range []struct { for _, tc := range []struct {
scenario string
request *http.Request request *http.Request
mod Webhook mod *Webhook
next echo.HandlerFunc next echo.HandlerFunc
expectWebhookContentType string expectWebhookContentType string
expectWebhookMethod string expectWebhookMethod string
expectWebhookExtraHTTPHeaders map[string]string expectWebhookExtraHttpHeaders map[string]string
expectWebhookFilename string expectWebhookFilename string
expectWebhookErrorStatus int expectWebhookErrorStatus int
expectWebhookErrorMessage string expectWebhookErrorMessage string
returnedError *echo.HTTPError returnedError *echo.HTTPError
}{ }{
{ {
request: buildMultipartFormDataRequest(), scenario: "next handler return an error",
mod: buildWebhookModule(), request: buildMultipartFormDataRequest(),
mod: buildWebhookModule(),
next: func() echo.HandlerFunc { next: func() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
return errors.New("foo") return errors.New("foo")
@@ -356,8 +360,9 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError), expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError),
}, },
{ {
request: buildMultipartFormDataRequest(), scenario: "next handler return an HTTP error",
mod: buildWebhookModule(), request: buildMultipartFormDataRequest(),
mod: buildWebhookModule(),
next: func() echo.HandlerFunc { next: func() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
return api.NewSentinelHTTPError(http.StatusBadRequest, http.StatusText(http.StatusBadRequest)) return api.NewSentinelHTTPError(http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
@@ -369,38 +374,36 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
expectWebhookErrorMessage: http.StatusText(http.StatusBadRequest), expectWebhookErrorMessage: http.StatusText(http.StatusBadRequest),
}, },
{ {
scenario: "success",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Output-Filename", "foo") req.Header.Set("Gotenberg-Output-Filename", "foo")
req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", `{ "foo": "bar" }`) req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", `{ "foo": "bar" }`)
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
next: func() echo.HandlerFunc { next: func() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
ctx := c.Get("context").(*api.Context) ctx := c.Get("context").(*api.Context)
return ctx.AddOutputPaths("/tests/test/testdata/api/sample2.pdf") return ctx.AddOutputPaths("/tests/test/testdata/api/sample2.pdf")
} }
}(), }(),
expectWebhookContentType: "application/pdf", expectWebhookContentType: "application/pdf",
expectWebhookMethod: http.MethodPost, expectWebhookMethod: http.MethodPost,
expectWebhookFilename: "foo", expectWebhookFilename: "foo",
expectWebhookExtraHTTPHeaders: map[string]string{"foo": "bar"}, expectWebhookExtraHttpHeaders: map[string]string{"foo": "bar"},
}, },
{ {
scenario: "success (return an error)",
request: func() *http.Request { request: func() *http.Request {
req := buildMultipartFormDataRequest() req := buildMultipartFormDataRequest()
req.Header.Set("Gotenberg-Output-Filename", "foo") req.Header.Set("Gotenberg-Output-Filename", "foo")
return req return req
}(), }(),
mod: buildWebhookModule(), mod: buildWebhookModule(),
next: func() echo.HandlerFunc { next: func() echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
ctx := c.Get("context").(*api.Context) ctx := c.Get("context").(*api.Context)
return ctx.AddOutputPaths("/tests/test/testdata/api/sample1.pdf") return ctx.AddOutputPaths("/tests/test/testdata/api/sample1.pdf")
} }
}(), }(),
@@ -452,24 +455,24 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
return func(c echo.Context) error { return func(c echo.Context) error {
contentType := c.Request().Header.Get(echo.HeaderContentType) contentType := c.Request().Header.Get(echo.HeaderContentType)
if contentType != tc.expectWebhookContentType { if contentType != tc.expectWebhookContentType {
t.Errorf("test %d: expected '%s' '%s' but got '%s'", i, echo.HeaderContentType, tc.expectWebhookContentType, contentType) t.Errorf("expected '%s' '%s' but got '%s'", echo.HeaderContentType, tc.expectWebhookContentType, contentType)
} }
trace := c.Request().Header.Get("Gotenberg-Trace") trace := c.Request().Header.Get("Gotenberg-Trace")
if trace != "foo" { if trace != "foo" {
t.Errorf("test %d: expected '%s' '%s' but got '%s'", i, "Gotenberg-Trace", "foo", trace) t.Errorf("expected '%s' '%s' but got '%s'", "Gotenberg-Trace", "foo", trace)
} }
method := c.Request().Method method := c.Request().Method
if method != tc.expectWebhookMethod { if method != tc.expectWebhookMethod {
t.Errorf("test %d: expected HTTP method '%s' but got '%s'", i, tc.expectWebhookMethod, method) t.Errorf("expected HTTP method '%s' but got '%s'", tc.expectWebhookMethod, method)
} }
for key, expect := range tc.expectWebhookExtraHTTPHeaders { for key, expect := range tc.expectWebhookExtraHttpHeaders {
actual := c.Request().Header.Get(key) actual := c.Request().Header.Get(key)
if actual != expect { if actual != expect {
t.Errorf("test %d: expected '%s' '%s' but got '%s'", i, key, expect, actual) t.Errorf("expected '%s' '%s' but got '%s'", key, expect, actual)
} }
} }
@@ -492,11 +495,11 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
} }
if result.Status != tc.expectWebhookErrorStatus { if result.Status != tc.expectWebhookErrorStatus {
t.Errorf("test %d: expected status %d from JSON but got %d", i, tc.expectWebhookErrorStatus, result.Status) t.Errorf("expected status %d from JSON but got %d", tc.expectWebhookErrorStatus, result.Status)
} }
if result.Message != tc.expectWebhookErrorMessage { if result.Message != tc.expectWebhookErrorMessage {
t.Errorf("test %d: expected message '%s' from JSON but got '%s'", i, tc.expectWebhookErrorMessage, result.Message) t.Errorf("expected message '%s' from JSON but got '%s'", tc.expectWebhookErrorMessage, result.Message)
} }
errChan <- nil errChan <- nil
@@ -505,12 +508,12 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
contentLength := c.Request().Header.Get(echo.HeaderContentLength) contentLength := c.Request().Header.Get(echo.HeaderContentLength)
if contentLength == "" { if contentLength == "" {
t.Errorf("test %d: expected non empty '%s'", i, echo.HeaderContentLength) t.Errorf("expected non empty '%s'", echo.HeaderContentLength)
} }
contentDisposition := c.Request().Header.Get(echo.HeaderContentDisposition) contentDisposition := c.Request().Header.Get(echo.HeaderContentDisposition)
if !strings.Contains(contentDisposition, tc.expectWebhookFilename) { if !strings.Contains(contentDisposition, tc.expectWebhookFilename) {
t.Errorf("test %d: expected '%s' '%s' to contain '%s'", i, echo.HeaderContentDisposition, contentDisposition, tc.expectWebhookFilename) t.Errorf("expected '%s' '%s' to contain '%s'", echo.HeaderContentDisposition, contentDisposition, tc.expectWebhookFilename)
} }
body, err := io.ReadAll(c.Request().Body) body, err := io.ReadAll(c.Request().Body)
@@ -520,7 +523,7 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
} }
if body == nil || len(body) == 0 { if body == nil || len(body) == 0 {
t.Errorf("test %d: expected non nil body", i) t.Error("expected non nil body")
} }
errChan <- nil errChan <- nil
@@ -537,25 +540,25 @@ func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
go func() { go func() {
err := webhook.Start(fmt.Sprintf(":%d", webhookPort)) err := webhook.Start(fmt.Sprintf(":%d", webhookPort))
if !errors.Is(err, http.ErrServerClosed) { if !errors.Is(err, http.ErrServerClosed) {
t.Errorf("test %d: expected no error but got: %v", i, err) t.Errorf("expected no error but got: %v", err)
} }
}() }()
defer func() { defer func() {
err := webhook.Shutdown(context.TODO()) err := webhook.Shutdown(context.TODO())
if err != nil { if err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err) t.Errorf("expected no error but got: %v", err)
} }
}() }()
err := webhookMiddleware(tc.mod).Handler(tc.next)(c) err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
if err != nil && !errors.Is(err, api.ErrAsyncProcess) { if err != nil && !errors.Is(err, api.ErrAsyncProcess) {
t.Errorf("test %d: expected no error but got: %v", i, err) t.Errorf("expected no error but got: %v", err)
} }
err = <-errChan err = <-errChan
if err != nil { if err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err) t.Errorf("expected no error but got: %v", err)
} }
}() }()
} }

View File

@@ -13,7 +13,7 @@ import (
) )
func init() { func init() {
gotenberg.MustRegisterModule(Webhook{}) gotenberg.MustRegisterModule(new(Webhook))
} }
// Webhook is a module which provides a middleware for uploading output files // Webhook is a module which provides a middleware for uploading output files
@@ -31,7 +31,7 @@ type Webhook struct {
} }
// Descriptor returns an [Webhook]'s module descriptor. // Descriptor returns an [Webhook]'s module descriptor.
func (Webhook) Descriptor() gotenberg.ModuleDescriptor { func (w *Webhook) Descriptor() gotenberg.ModuleDescriptor {
return gotenberg.ModuleDescriptor{ return gotenberg.ModuleDescriptor{
ID: "webhook", ID: "webhook",
FlagSet: func() *flag.FlagSet { FlagSet: func() *flag.FlagSet {
@@ -94,7 +94,7 @@ func (w *Webhook) Provision(ctx *gotenberg.Context) error {
} }
// Middlewares returns the middleware. // Middlewares returns the middleware.
func (w Webhook) Middlewares() ([]api.Middleware, error) { func (w *Webhook) Middlewares() ([]api.Middleware, error) {
if w.disable { if w.disable {
return nil, nil return nil, nil
} }

View File

@@ -8,7 +8,7 @@ import (
) )
func TestWebhook_Descriptor(t *testing.T) { func TestWebhook_Descriptor(t *testing.T) {
descriptor := Webhook{}.Descriptor() descriptor := new(Webhook).Descriptor()
actual := reflect.TypeOf(descriptor.New()) actual := reflect.TypeOf(descriptor.New())
expect := reflect.TypeOf(new(Webhook)) expect := reflect.TypeOf(new(Webhook))
@@ -34,15 +34,20 @@ func TestWebhook_Provision(t *testing.T) {
} }
func TestWebhook_Middlewares(t *testing.T) { func TestWebhook_Middlewares(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
expectMiddlewares int scenario string
disable bool disable bool
expectMiddlewares int
}{ }{
{ {
expectMiddlewares: 1, scenario: "webhook disabled",
disable: true,
expectMiddlewares: 0,
}, },
{ {
disable: true, scenario: "webhook enabled",
disable: false,
expectMiddlewares: 1,
}, },
} { } {
mod := new(Webhook) mod := new(Webhook)
@@ -50,11 +55,11 @@ func TestWebhook_Middlewares(t *testing.T) {
middlewares, err := mod.Middlewares() middlewares, err := mod.Middlewares()
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
if tc.expectMiddlewares != len(middlewares) { if tc.expectMiddlewares != len(middlewares) {
t.Errorf("test %d: expected %d middlewares but got %d", i, tc.expectMiddlewares, len(middlewares)) t.Errorf("expected %d middlewares but got %d", tc.expectMiddlewares, len(middlewares))
} }
} }
} }