mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 04:32:15 +01:00
chore: minor refactor of webhook module
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user