From caea81501d56fee87857d41519bf0ea95ebd5cc5 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 4 Mar 2026 21:34:16 +0100 Subject: [PATCH] test(integration): add bookmarks routes --- Makefile | 2 + pkg/modules/pdfcpu/pdfcpu.go | 53 ++- pkg/modules/pdfengines/multi.go | 46 +-- pkg/modules/pdfengines/pdfengines.go | 22 +- pkg/modules/pdfengines/routes.go | 1 + .../features/pdfengines_bookmarks.feature | 339 ++++++++++++++++++ .../features/pdfengines_metadata.feature | 2 +- 7 files changed, 415 insertions(+), 50 deletions(-) create mode 100644 test/integration/features/pdfengines_bookmarks.feature diff --git a/Makefile b/Makefile index c0b69b8..2c1bcca 100644 --- a/Makefile +++ b/Makefile @@ -195,6 +195,8 @@ NO_CONCURRENCY=false # metadata # pdfengines-split # split +# pdfengines-bookmarks +# bookmarks # prometheus-metrics # root # version diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 8b91dcc..8b4a02d 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -1,6 +1,7 @@ package pdfcpu import ( + "bytes" "context" "encoding/json" "errors" @@ -191,38 +192,60 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *zap.Logger, inp return nil, fmt.Errorf("create command: %w", err) } - _, err = cmd.Exec() - if err != nil { - // pdfcpu returns an error if there are no bookmarks. - // We should check if the error is "no bookmarks available". - // For now, we assume an error means no bookmarks or a real error. - // However, let's try to be a bit more robust if possible. - // If the file does not exist, it's likely there were no bookmarks. - _, statErr := os.Stat(tmpPath) - if os.IsNotExist(statErr) { - return nil, nil - } - return nil, fmt.Errorf("read bookmarks with pdfcpu: %w", err) - } - defer func() { err := os.Remove(tmpPath) - if err != nil { + if err != nil && !os.IsNotExist(err) { logger.Error(fmt.Sprintf("remove temporary bookmarks JSON file: %v", err)) } }() + _, cmdErr := cmd.Exec() + + // Check file existence and size. + info, statErr := os.Stat(tmpPath) + + if cmdErr != nil { + // If the file wasn't created, or it was created but is 0 bytes, + // it means pdfcpu had no bookmarks to write. + if os.IsNotExist(statErr) || (statErr == nil && info.Size() == 0) { + return make([]gotenberg.Bookmark, 0), nil + } + + // Fallback: Check the error string just in case pdfcpu failed without + // touching the file. + if strings.Contains(strings.ToLower(cmdErr.Error()), "no bookmarks") { + return make([]gotenberg.Bookmark, 0), nil + } + return nil, fmt.Errorf("read bookmarks with pdfcpu: %w", cmdErr) + } + + // If cmd succeeded, but output a 0-byte file anyway. + if info != nil && info.Size() == 0 { + return make([]gotenberg.Bookmark, 0), nil + } + + // Read the file content. jsonBytes, err := os.ReadFile(tmpPath) if err != nil { return nil, fmt.Errorf("read temporary bookmarks JSON file: %w", err) } + // Check if the content is just empty whitespace. + if len(bytes.TrimSpace(jsonBytes)) == 0 { + return make([]gotenberg.Bookmark, 0), nil + } + var data pdfcpuBookmarks err = json.Unmarshal(jsonBytes, &data) if err != nil { return nil, fmt.Errorf("unmarshal bookmarks: %w", err) } + // Safety check: Does the parsed JSON actually contain bookmarks? + if len(data.Bookmarks) == 0 { + return make([]gotenberg.Bookmark, 0), nil + } + var mapBookmarks func(bookmarks []pdfcpuBookmark) []gotenberg.Bookmark mapBookmarks = func(bookmarks []pdfcpuBookmark) []gotenberg.Bookmark { res := make([]gotenberg.Bookmark, len(bookmarks)) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index e2ee07f..6076ff5 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -12,16 +12,16 @@ import ( ) type multiPdfEngines struct { - mergeEngines []gotenberg.PdfEngine - splitEngines []gotenberg.PdfEngine - flattenEngines []gotenberg.PdfEngine - convertEngines []gotenberg.PdfEngine - readMetadataEngines []gotenberg.PdfEngine - writeMetadataEngines []gotenberg.PdfEngine - passwordEngines []gotenberg.PdfEngine - embedEngines []gotenberg.PdfEngine - bookmarksEngines []gotenberg.PdfEngine - readBookmarksEngines []gotenberg.PdfEngine + mergeEngines []gotenberg.PdfEngine + splitEngines []gotenberg.PdfEngine + flattenEngines []gotenberg.PdfEngine + convertEngines []gotenberg.PdfEngine + readMetadataEngines []gotenberg.PdfEngine + writeMetadataEngines []gotenberg.PdfEngine + passwordEngines []gotenberg.PdfEngine + embedEngines []gotenberg.PdfEngine + readBookmarksEngines []gotenberg.PdfEngine + writeBookmarksEngines []gotenberg.PdfEngine } func newMultiPdfEngines( @@ -33,20 +33,20 @@ func newMultiPdfEngines( writeMetadataEngines, passwordEngines, embedEngines, - bookmarksEngines, - readBookmarksEngines []gotenberg.PdfEngine, + readBookmarksEngines, + writeBookmarksEngines []gotenberg.PdfEngine, ) *multiPdfEngines { return &multiPdfEngines{ - mergeEngines: mergeEngines, - splitEngines: splitEngines, - flattenEngines: flattenEngines, - convertEngines: convertEngines, - readMetadataEngines: readMetadataEngines, - writeMetadataEngines: writeMetadataEngines, - passwordEngines: passwordEngines, - embedEngines: embedEngines, - bookmarksEngines: bookmarksEngines, - readBookmarksEngines: readBookmarksEngines, + mergeEngines: mergeEngines, + splitEngines: splitEngines, + flattenEngines: flattenEngines, + convertEngines: convertEngines, + readMetadataEngines: readMetadataEngines, + writeMetadataEngines: writeMetadataEngines, + passwordEngines: passwordEngines, + embedEngines: embedEngines, + readBookmarksEngines: readBookmarksEngines, + writeBookmarksEngines: writeBookmarksEngines, } } @@ -264,7 +264,7 @@ func (multi *multiPdfEngines) WriteBookmarks(ctx context.Context, logger *zap.Lo var err error errChan := make(chan error, 1) - for _, engine := range multi.bookmarksEngines { + for _, engine := range multi.writeBookmarksEngines { go func(engine gotenberg.PdfEngine) { errChan <- engine.WriteBookmarks(ctx, logger, inputPath, bookmarks) }(engine) diff --git a/pkg/modules/pdfengines/pdfengines.go b/pkg/modules/pdfengines/pdfengines.go index f42cb5e..08f29d3 100644 --- a/pkg/modules/pdfengines/pdfengines.go +++ b/pkg/modules/pdfengines/pdfengines.go @@ -36,8 +36,8 @@ type PdfEngines struct { writeMetadataNames []string encryptNames []string embedNames []string - writeBookmarksNames []string readBookmarksNames []string + writeBookmarksNames []string engines []gotenberg.PdfEngine disableRoutes bool } @@ -56,8 +56,8 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor { fs.StringSlice("pdfengines-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all") fs.StringSlice("pdfengines-encrypt-engines", []string{"qpdf", "pdftk", "pdfcpu"}, "Set the PDF engines and their order for the password protection feature - empty means all") fs.StringSlice("pdfengines-embed-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the file embedding feature - empty means all") - fs.StringSlice("pdfengines-bookmarks-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the bookmarks feature - empty means all") fs.StringSlice("pdfengines-read-bookmarks-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the read bookmarks feature - empty means all") + fs.StringSlice("pdfengines-write-bookmarks-engines", []string{"pdfcpu"}, "Set the PDF engines and their order for the write bookmarks feature - empty means all") fs.Bool("pdfengines-disable-routes", false, "Disable the routes") // Deprecated flags. @@ -85,8 +85,8 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines") encryptNames := flags.MustStringSlice("pdfengines-encrypt-engines") embedNames := flags.MustStringSlice("pdfengines-embed-engines") - bookmarksNames := flags.MustStringSlice("pdfengines-bookmarks-engines") readBookmarksNames := flags.MustStringSlice("pdfengines-read-bookmarks-engines") + writeBookmarksNames := flags.MustStringSlice("pdfengines-write-bookmarks-engines") mod.disableRoutes = flags.MustBool("pdfengines-disable-routes") engines, err := ctx.Modules(new(gotenberg.PdfEngine)) @@ -153,16 +153,16 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error { mod.embedNames = embedNames } - mod.writeBookmarksNames = defaultNames - if len(bookmarksNames) > 0 { - mod.writeBookmarksNames = bookmarksNames - } - mod.readBookmarksNames = defaultNames if len(readBookmarksNames) > 0 { mod.readBookmarksNames = readBookmarksNames } + mod.writeBookmarksNames = defaultNames + if len(writeBookmarksNames) > 0 { + mod.writeBookmarksNames = writeBookmarksNames + } + return nil } @@ -212,8 +212,8 @@ func (mod *PdfEngines) Validate() error { findNonExistingEngines(mod.writeMetadataNames) findNonExistingEngines(mod.encryptNames) findNonExistingEngines(mod.embedNames) - findNonExistingEngines(mod.writeBookmarksNames) findNonExistingEngines(mod.readBookmarksNames) + findNonExistingEngines(mod.writeBookmarksNames) if len(nonExistingEngines) == 0 { return nil @@ -234,8 +234,8 @@ func (mod *PdfEngines) SystemMessages() []string { fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")), fmt.Sprintf("encrypt engines - %s", strings.Join(mod.encryptNames[:], " ")), fmt.Sprintf("embed engines - %s", strings.Join(mod.embedNames[:], " ")), - fmt.Sprintf("write bookmarks engines - %s", strings.Join(mod.writeBookmarksNames[:], " ")), fmt.Sprintf("read bookmarks engines - %s", strings.Join(mod.readBookmarksNames[:], " ")), + fmt.Sprintf("write bookmarks engines - %s", strings.Join(mod.writeBookmarksNames[:], " ")), } } @@ -264,8 +264,8 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) { engines(mod.writeMetadataNames), engines(mod.encryptNames), engines(mod.embedNames), - engines(mod.writeBookmarksNames), engines(mod.readBookmarksNames), + engines(mod.writeBookmarksNames), ), nil } diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index f245032..c902f5a 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -322,6 +322,7 @@ func WriteBookmarksStub(ctx *api.Context, engine gotenberg.PdfEngine, bookmarks } } default: + // Should not happen. return fmt.Errorf("bookmarks type '%T' not supported", bookmarks) } diff --git a/test/integration/features/pdfengines_bookmarks.feature b/test/integration/features/pdfengines_bookmarks.feature new file mode 100644 index 0000000..2bc34af --- /dev/null +++ b/test/integration/features/pdfengines_bookmarks.feature @@ -0,0 +1,339 @@ +@pdfengines +@pdfengines-bookmarks +@bookmarks +Feature: /forms/pdfengines/bookmarks/{write|read} + + Scenario: POST /forms/pdfengines/bookmarks/{write|read} (Single PDF & Bookmarks list) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1,"children":[{"title":"Sub-index","page":1}]}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the response + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": [ + { + "title": "Index", + "page": 1, + "children": [ + { + "title": "Sub-index", + "page": 1 + } + ] + } + ] + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/{write|read} (Single PDF & Bookmarks Map) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | {"page_1.pdf":[{"title":"Index","page":1}]} | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then there should be 1 PDF(s) in the response + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": [ + { + "title": "Index", + "page": 1 + } + ] + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/{write|read} (Many PDFs & Bookmarks List) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files. | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/page_1.pdf | file | + | files | teststore/page_2.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "page_1.pdf": [ + { + "title": "Index", + "page": 1 + } + ], + "page_2.pdf": [ + { + "title": "Index", + "page": 1 + } + ] + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/{write|read} (Many PDFs & Bookmarks Map) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files. | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | bookmarks | {"page_1.pdf":[{"title":"Index","page":1}],"page_2.pdf":[{"title":"Index","page":1}]} | field | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be 2 PDF(s) in the response + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/page_1.pdf | file | + | files | teststore/page_2.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "page_1.pdf": [ + { + "title": "Index", + "page": 1 + } + ], + "page_2.pdf": [ + { + "title": "Index", + "page": 1 + } + ] + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/read (Empty List) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "page_1.pdf": [] + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/write (Bad Request) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'bookmarks' is required; no form file found for extensions: [.pdf] + """ + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | foo | field | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: form field 'bookmarks' is invalid (got 'foo', resulting to unmarshal bookmarks: invalid character 'o' in literal false (expecting 'a')) + """ + + Scenario: POST /forms/pdfengines/bookmarks/read (Bad Request) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 400 + Then the response header "Content-Type" should be "text/plain; charset=UTF-8" + Then the response body should match string: + """ + Invalid form data: no form file found for extensions: [.pdf] + """ + + Scenario: POST /forms/pdfengines/bookmarks/write (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/bookmarks/read (Routes Disabled) + Given I have a Gotenberg container with the following environment variable(s): + | PDFENGINES_DISABLE_ROUTES | true | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 404 + + Scenario: POST /forms/pdfengines/bookmarks/write (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + | Gotenberg-Trace | forms_pdfengines_bookmarks_write | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_bookmarks_write" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_bookmarks_write" | + + Scenario: POST /forms/pdfengines/bookmarks/read (Gotenberg Trace) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | Gotenberg-Trace | forms_pdfengines_bookmarks_read | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response header "Gotenberg-Trace" should be "forms_pdfengines_bookmarks_read" + Then the Gotenberg container should log the following entries: + | "trace":"forms_pdfengines_bookmarks_read" | + + @output-filename + Scenario: POST /forms/pdfengines/bookmarks/write (Output Filename - Single PDF) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + Then there should be the following file(s) in the response: + | foo.pdf | + + @output-filename + Scenario: POST /forms/pdfengines/bookmarks/write (Output Filename - Many PDFs) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/zip" + Then there should be the following file(s) in the response: + | foo.zip | + | page_1.pdf | + | page_2.pdf | + + @download-from + Scenario: POST /forms/pdfengines/bookmarks/write (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | bookmarks | [{"title":"Index","page":1}] | field | + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + + @download-from + Scenario: POST /forms/pdfengines/bookmarks/read (Download From) + Given I have a default Gotenberg container + Given I have a static server + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | downloadFrom | [{"url":"http://host.docker.internal:%d/static/testdata/page_1.pdf","extraHttpHeaders":{"X-Foo":"bar"}}] | field | + Then the file request header "X-Foo" should be "bar" + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + + @webhook + Scenario: POST /forms/pdfengines/bookmarks/write (Webhook) + 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/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + | Gotenberg-Webhook-Url | http://host.docker.internal:%d/webhook | header | + | Gotenberg-Webhook-Error-Url | http://host.docker.internal:%d/webhook/error | header | + | Gotenberg-Output-Filename | foo | 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 + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" + Then the response body should match JSON: + """ + { + "foo.pdf": [ + { + "title": "Index", + "page": 1 + } + ] + } + """ + + @webhook + Scenario: POST /forms/pdfengines/bookmarks/read (Webhook) + 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/bookmarks/read" 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 | + 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/json" + Then the webhook request body should match JSON: + """ + { + "status": 400, + "message": "The webhook middleware can only work with multipart/form-data routes that results in output files" + } + """ + + Scenario: POST /forms/pdfengines/bookmarks/write (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + Then the response status code should be 401 + + Scenario: POST /forms/pdfengines/bookmarks/read (Basic Auth) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_BASIC_AUTH | true | + | GOTENBERG_API_BASIC_AUTH_USERNAME | foo | + | GOTENBERG_API_BASIC_AUTH_PASSWORD | bar | + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + Then the response status code should be 401 + + Scenario: POST /foo/forms/pdfengines/bookmarks/{write|read} (Root Path) + Given I have a Gotenberg container with the following environment variable(s): + | API_ENABLE_DEBUG_ROUTE | true | + | API_ROOT_PATH | /foo/ | + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/bookmarks/write" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | bookmarks | [{"title":"Index","page":1}] | field | + | Gotenberg-Output-Filename | foo | header | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/pdf" + When I make a "POST" request to Gotenberg at the "/foo/forms/pdfengines/bookmarks/read" endpoint with the following form data and header(s): + | files | teststore/foo.pdf | file | + Then the response status code should be 200 + Then the response header "Content-Type" should be "application/json" diff --git a/test/integration/features/pdfengines_metadata.feature b/test/integration/features/pdfengines_metadata.feature index 336c350..8a5b914 100644 --- a/test/integration/features/pdfengines_metadata.feature +++ b/test/integration/features/pdfengines_metadata.feature @@ -1,7 +1,7 @@ @pdfengines @pdfengines-metadata @metadata -Feature: /forms/pdfengines/{write|read} +Feature: /forms/pdfengines/metadata/{write|read} Scenario: POST /forms/pdfengines/metadata/{write|read} (Single PDF) Given I have a default Gotenberg container