mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-07-02 08:27:41 +08:00
feat(webhook): add events
This commit is contained in:
@@ -66,6 +66,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-file
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -67,6 +67,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-file
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -66,6 +66,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-file
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -40,6 +40,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-screenshot
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -41,6 +41,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-screenshot
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -40,6 +40,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-screenshot
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -83,6 +83,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: my-file
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -17,6 +17,7 @@ body:multipart-form {
|
||||
headers {
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -19,6 +19,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: with-bookmarks
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -20,6 +20,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: converted
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -20,6 +20,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: with-embeds
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -20,6 +20,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: encrypted
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -18,6 +18,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: flattened
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -37,6 +37,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: merged
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -17,6 +17,7 @@ body:multipart-form {
|
||||
headers {
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -19,6 +19,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: with-metadata
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -20,6 +20,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: rotated
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -37,6 +37,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: split
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -24,6 +24,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: stamped
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -24,6 +24,7 @@ headers {
|
||||
~Gotenberg-Output-Filename: watermarked
|
||||
~Gotenberg-Webhook-Url: http://localhost:8080/webhook
|
||||
~Gotenberg-Webhook-Error-Url: http://localhost:8080/webhook/error
|
||||
~Gotenberg-Webhook-Events-Url: http://localhost:8080/webhook/events
|
||||
~Gotenberg-Webhook-Method: POST
|
||||
~Gotenberg-Webhook-Error-Method: POST
|
||||
~Gotenberg-Webhook-Extra-Http-Headers: {"X-Custom":"value"}
|
||||
|
||||
@@ -2,6 +2,7 @@ package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
@@ -26,6 +27,7 @@ type client struct {
|
||||
method string
|
||||
errorUrl string
|
||||
errorMethod string
|
||||
eventsUrl string
|
||||
extraHttpHeaders map[string]string
|
||||
startTime time.Time
|
||||
|
||||
@@ -144,3 +146,66 @@ func (c client) send(ctx context.Context, body io.Reader, headers map[string]str
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendEvent sends a structured JSON event to the events URL. It is
|
||||
// fire-and-forget: failures are logged but do not propagate.
|
||||
func (c client) sendEvent(ctx context.Context, correlationIdHeader, correlationId string, event map[string]any) {
|
||||
if c.eventsUrl == "" {
|
||||
return
|
||||
}
|
||||
|
||||
b, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("marshal webhook event: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
tracer := gotenberg.Tracer()
|
||||
ctx, span := tracer.Start(ctx, "POST Webhook Event",
|
||||
trace.WithSpanKind(trace.SpanKindClient),
|
||||
trace.WithAttributes(semconv.ServerAddress(c.eventsUrl)),
|
||||
)
|
||||
defer span.End()
|
||||
|
||||
req, err := retryablehttp.NewRequest(http.MethodPost, c.eventsUrl, b)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("create webhook event request: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
|
||||
|
||||
req.Header.Set("User-Agent", "Gotenberg")
|
||||
for key, value := range c.extraHttpHeaders {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.Header.Set(correlationIdHeader, correlationId)
|
||||
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
span.RecordError(err)
|
||||
span.SetStatus(codes.Error, err.Error())
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("send webhook event to '%s': %s", c.eventsUrl, err))
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := resp.Body.Close()
|
||||
if err != nil {
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("close response body from '%s': %s", c.eventsUrl, err))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
span.RecordError(fmt.Errorf("webhook event: got status '%s'", resp.Status))
|
||||
span.SetStatus(codes.Error, resp.Status)
|
||||
c.logger.ErrorContext(ctx, fmt.Sprintf("send webhook event to '%s': got status '%s'", c.eventsUrl, resp.Status))
|
||||
return
|
||||
}
|
||||
|
||||
span.SetStatus(codes.Ok, "")
|
||||
c.logger.InfoContext(ctx, fmt.Sprintf("webhook event sent to '%s'", c.eventsUrl))
|
||||
}
|
||||
|
||||
@@ -85,7 +85,14 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
if err != nil {
|
||||
params.ctx.Log().Error(fmt.Sprintf("send output file to webhook: %s", err))
|
||||
params.handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
params.client.sendEvent(params.ctx, params.correlationIdHeader, params.correlationId, map[string]any{
|
||||
"event": "webhook.success",
|
||||
"correlationId": params.correlationId,
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
})
|
||||
}
|
||||
|
||||
return func(c echo.Context) error {
|
||||
@@ -176,6 +183,15 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
// What about the events URL?
|
||||
webhookEventsUrl := c.Request().Header.Get("Gotenberg-Webhook-Events-Url")
|
||||
if webhookEventsUrl != "" {
|
||||
err = gotenberg.FilterDeadline(w.allowList, w.denyList, webhookEventsUrl, deadline)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filter webhook events URL: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve values from echo.Context before it gets recycled.
|
||||
// See https://github.com/gotenberg/gotenberg/issues/1000.
|
||||
startTime := c.Get("startTime").(time.Time)
|
||||
@@ -187,6 +203,7 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
method: webhookMethod,
|
||||
errorUrl: webhookErrorUrl,
|
||||
errorMethod: webhookErrorMethod,
|
||||
eventsUrl: webhookEventsUrl,
|
||||
extraHttpHeaders: extraHttpHeaders,
|
||||
startTime: startTime,
|
||||
|
||||
@@ -233,6 +250,16 @@ func webhookMiddleware(w *Webhook) api.Middleware {
|
||||
if err != nil {
|
||||
ctx.Log().Error(fmt.Sprintf("send error response to webhook: %s", err.Error()))
|
||||
}
|
||||
|
||||
client.sendEvent(ctx, correlationIdHeader, correlationId, map[string]any{
|
||||
"event": "webhook.error",
|
||||
"correlationId": correlationId,
|
||||
"timestamp": time.Now().UTC().Format(time.RFC3339Nano),
|
||||
"error": map[string]any{
|
||||
"status": status,
|
||||
"message": message,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if w.enableSyncMode {
|
||||
|
||||
@@ -63,6 +63,7 @@ make test-integration PLATFORM=linux/arm64 # Force a specific platform
|
||||
- `the (response|webhook request) body should match string:` (docstring)
|
||||
- `the (response|webhook request) body should contain string:` (docstring)
|
||||
- `the (response|webhook request) body should match JSON:` (docstring — use `"ignore"` for dynamic values like timestamps)
|
||||
- `the webhook event should match JSON:` (docstring — use `"ignore"` for dynamic values; polls for up to 5s)
|
||||
- `there should be <N> PDF(s) in the (response|webhook request)`
|
||||
- `there should be the following file(s) in the (response|webhook request):` (table of filenames)
|
||||
- `the "<name>" PDF should have <N> page(s)`
|
||||
|
||||
@@ -44,3 +44,45 @@ Feature: Webhook
|
||||
Then the response status code should be 204
|
||||
Then the webhook request header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the webhook request
|
||||
|
||||
Scenario: Webhook Events URL (Success)
|
||||
Given I have a default Gotenberg container
|
||||
Given I have a webhook server
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
|
||||
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
|
||||
| Gotenberg-Webhook-Events-Url | http://host.docker.internal:%d/webhook/events | header |
|
||||
Then the response status code should be 204
|
||||
When I wait for the asynchronous request to the webhook
|
||||
Then the webhook request header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the webhook request
|
||||
Then the webhook event should match JSON:
|
||||
"""
|
||||
{
|
||||
"event": "webhook.success",
|
||||
"correlationId": "ignore",
|
||||
"timestamp": "ignore"
|
||||
}
|
||||
"""
|
||||
|
||||
Scenario: Webhook Events URL (Synchronous)
|
||||
Given I have a Gotenberg container with the following environment variable(s):
|
||||
| WEBHOOK_ENABLE_SYNC_MODE | true |
|
||||
Given I have a webhook server
|
||||
When I make a "POST" request to Gotenberg at the "/forms/pdfengines/flatten" endpoint with the following form data and header(s):
|
||||
| files | testdata/page_1.pdf | file |
|
||||
| Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header |
|
||||
| Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header |
|
||||
| Gotenberg-Webhook-Events-Url | http://host.docker.internal:%d/webhook/events | header |
|
||||
Then the response status code should be 204
|
||||
Then the webhook request header "Content-Type" should be "application/pdf"
|
||||
Then there should be 1 PDF(s) in the webhook request
|
||||
Then the webhook event should match JSON:
|
||||
"""
|
||||
{
|
||||
"event": "webhook.success",
|
||||
"correlationId": "ignore",
|
||||
"timestamp": "ignore"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -181,7 +181,7 @@ func (s *scenario) iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders(ct
|
||||
}
|
||||
files[name] = append(files[name], value)
|
||||
case "header":
|
||||
if name == "Gotenberg-Webhook-Url" || name == "Gotenberg-Webhook-Error-Url" {
|
||||
if name == "Gotenberg-Webhook-Url" || name == "Gotenberg-Webhook-Error-Url" || name == "Gotenberg-Webhook-Events-Url" {
|
||||
headers[name] = fmt.Sprintf(value, s.hostPort)
|
||||
continue
|
||||
}
|
||||
@@ -653,6 +653,48 @@ func (s *scenario) theBodyShouldMatchJSON(kind string, expectedDoc *godog.DocStr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) theWebhookEventShouldMatchJSON(ctx context.Context, expectedDoc *godog.DocString) error {
|
||||
if s.server == nil {
|
||||
return errors.New("server not initialized")
|
||||
}
|
||||
|
||||
// Poll briefly — the event fires right after the main webhook.
|
||||
var body []byte
|
||||
deadline := time.After(5 * time.Second)
|
||||
for {
|
||||
body = s.server.getEventBody()
|
||||
if body != nil {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-deadline:
|
||||
return errors.New("timed out waiting for webhook event")
|
||||
default:
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
var expected, actual any
|
||||
|
||||
content := strings.ReplaceAll(expectedDoc.Content, "{version}", GotenbergVersion)
|
||||
err := json.Unmarshal([]byte(content), &expected)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal expected JSON: %w", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &actual)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshal actual JSON: %w", err)
|
||||
}
|
||||
|
||||
err = compareJson(expected, actual)
|
||||
if err != nil {
|
||||
return fmt.Errorf("expected matching webhook event JSON: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scenario) thereShouldBePdfs(expected int, kind string) error {
|
||||
dirPath := s.teststoreDir
|
||||
|
||||
@@ -1158,6 +1200,7 @@ func InitializeScenario(ctx *godog.ScenarioContext) {
|
||||
ctx.Then(`^the (response|webhook request) body should match string:$`, s.theBodyShouldMatchString)
|
||||
ctx.Then(`^the (response|webhook request) body should contain string:$`, s.theBodyShouldContainString)
|
||||
ctx.Then(`^the (response|webhook request) body should match JSON:$`, s.theBodyShouldMatchJSON)
|
||||
ctx.Then(`^the webhook event should match JSON:$`, s.theWebhookEventShouldMatchJSON)
|
||||
ctx.Then(`^there should be (\d+) PDF\(s\) in the (response|webhook request)$`, s.thereShouldBePdfs)
|
||||
ctx.Then(`^there should be the following file\(s\) in the (response|webhook request):$`, s.thereShouldBeTheFollowingFiles)
|
||||
ctx.Then(`^the (response|webhook request) PDF\(s\) should be valid "([^"]*)" with a tolerance of (\d+) failed rule\(s\)$`, s.thePdfsShouldBeValidWithAToleranceOf)
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"github.com/google/uuid"
|
||||
@@ -19,10 +20,12 @@ import (
|
||||
)
|
||||
|
||||
type server struct {
|
||||
srv *echo.Echo
|
||||
req *http.Request
|
||||
bodyCopy []byte
|
||||
errChan chan error
|
||||
srv *echo.Echo
|
||||
req *http.Request
|
||||
bodyCopy []byte
|
||||
errChan chan error
|
||||
eventBody []byte
|
||||
eventMu sync.Mutex
|
||||
}
|
||||
|
||||
func newServer(ctx context.Context, workdir string) (*server, error) {
|
||||
@@ -144,6 +147,18 @@ func newServer(ctx context.Context, workdir string) (*server, error) {
|
||||
srv.POST("/webhook/error", webhookErrorHandler)
|
||||
srv.PATCH("/webhook/error", webhookErrorHandler)
|
||||
srv.PUT("/webhook/error", webhookErrorHandler)
|
||||
|
||||
webhookEventsHandler := func(c echo.Context) error {
|
||||
body, err := io.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
return c.String(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
s.eventMu.Lock()
|
||||
s.eventBody = body
|
||||
s.eventMu.Unlock()
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
}
|
||||
srv.POST("/webhook/events", webhookEventsHandler)
|
||||
srv.GET("/static/:path", func(c echo.Context) error {
|
||||
s.req = c.Request()
|
||||
path := c.Param("path")
|
||||
@@ -170,6 +185,12 @@ func newServer(ctx context.Context, workdir string) (*server, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *server) getEventBody() []byte {
|
||||
s.eventMu.Lock()
|
||||
defer s.eventMu.Unlock()
|
||||
return s.eventBody
|
||||
}
|
||||
|
||||
func (s *server) start(ctx context.Context) (int, error) {
|
||||
// #nosec
|
||||
ln, err := net.Listen("tcp", "0.0.0.0:0")
|
||||
|
||||
Reference in New Issue
Block a user