mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-16 12:22:17 +01:00
test(integration): add bookmarks routes
This commit is contained in:
2
Makefile
2
Makefile
@@ -195,6 +195,8 @@ NO_CONCURRENCY=false
|
|||||||
# metadata
|
# metadata
|
||||||
# pdfengines-split
|
# pdfengines-split
|
||||||
# split
|
# split
|
||||||
|
# pdfengines-bookmarks
|
||||||
|
# bookmarks
|
||||||
# prometheus-metrics
|
# prometheus-metrics
|
||||||
# root
|
# root
|
||||||
# version
|
# version
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package pdfcpu
|
package pdfcpu
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -191,38 +192,60 @@ func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *zap.Logger, inp
|
|||||||
return nil, fmt.Errorf("create command: %w", err)
|
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() {
|
defer func() {
|
||||||
err := os.Remove(tmpPath)
|
err := os.Remove(tmpPath)
|
||||||
if err != nil {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
logger.Error(fmt.Sprintf("remove temporary bookmarks JSON file: %v", 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)
|
jsonBytes, err := os.ReadFile(tmpPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read temporary bookmarks JSON file: %w", err)
|
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
|
var data pdfcpuBookmarks
|
||||||
err = json.Unmarshal(jsonBytes, &data)
|
err = json.Unmarshal(jsonBytes, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unmarshal bookmarks: %w", err)
|
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
|
var mapBookmarks func(bookmarks []pdfcpuBookmark) []gotenberg.Bookmark
|
||||||
mapBookmarks = func(bookmarks []pdfcpuBookmark) []gotenberg.Bookmark {
|
mapBookmarks = func(bookmarks []pdfcpuBookmark) []gotenberg.Bookmark {
|
||||||
res := make([]gotenberg.Bookmark, len(bookmarks))
|
res := make([]gotenberg.Bookmark, len(bookmarks))
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type multiPdfEngines struct {
|
type multiPdfEngines struct {
|
||||||
mergeEngines []gotenberg.PdfEngine
|
mergeEngines []gotenberg.PdfEngine
|
||||||
splitEngines []gotenberg.PdfEngine
|
splitEngines []gotenberg.PdfEngine
|
||||||
flattenEngines []gotenberg.PdfEngine
|
flattenEngines []gotenberg.PdfEngine
|
||||||
convertEngines []gotenberg.PdfEngine
|
convertEngines []gotenberg.PdfEngine
|
||||||
readMetadataEngines []gotenberg.PdfEngine
|
readMetadataEngines []gotenberg.PdfEngine
|
||||||
writeMetadataEngines []gotenberg.PdfEngine
|
writeMetadataEngines []gotenberg.PdfEngine
|
||||||
passwordEngines []gotenberg.PdfEngine
|
passwordEngines []gotenberg.PdfEngine
|
||||||
embedEngines []gotenberg.PdfEngine
|
embedEngines []gotenberg.PdfEngine
|
||||||
bookmarksEngines []gotenberg.PdfEngine
|
readBookmarksEngines []gotenberg.PdfEngine
|
||||||
readBookmarksEngines []gotenberg.PdfEngine
|
writeBookmarksEngines []gotenberg.PdfEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMultiPdfEngines(
|
func newMultiPdfEngines(
|
||||||
@@ -33,20 +33,20 @@ func newMultiPdfEngines(
|
|||||||
writeMetadataEngines,
|
writeMetadataEngines,
|
||||||
passwordEngines,
|
passwordEngines,
|
||||||
embedEngines,
|
embedEngines,
|
||||||
bookmarksEngines,
|
readBookmarksEngines,
|
||||||
readBookmarksEngines []gotenberg.PdfEngine,
|
writeBookmarksEngines []gotenberg.PdfEngine,
|
||||||
) *multiPdfEngines {
|
) *multiPdfEngines {
|
||||||
return &multiPdfEngines{
|
return &multiPdfEngines{
|
||||||
mergeEngines: mergeEngines,
|
mergeEngines: mergeEngines,
|
||||||
splitEngines: splitEngines,
|
splitEngines: splitEngines,
|
||||||
flattenEngines: flattenEngines,
|
flattenEngines: flattenEngines,
|
||||||
convertEngines: convertEngines,
|
convertEngines: convertEngines,
|
||||||
readMetadataEngines: readMetadataEngines,
|
readMetadataEngines: readMetadataEngines,
|
||||||
writeMetadataEngines: writeMetadataEngines,
|
writeMetadataEngines: writeMetadataEngines,
|
||||||
passwordEngines: passwordEngines,
|
passwordEngines: passwordEngines,
|
||||||
embedEngines: embedEngines,
|
embedEngines: embedEngines,
|
||||||
bookmarksEngines: bookmarksEngines,
|
readBookmarksEngines: readBookmarksEngines,
|
||||||
readBookmarksEngines: readBookmarksEngines,
|
writeBookmarksEngines: writeBookmarksEngines,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ func (multi *multiPdfEngines) WriteBookmarks(ctx context.Context, logger *zap.Lo
|
|||||||
var err error
|
var err error
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
|
|
||||||
for _, engine := range multi.bookmarksEngines {
|
for _, engine := range multi.writeBookmarksEngines {
|
||||||
go func(engine gotenberg.PdfEngine) {
|
go func(engine gotenberg.PdfEngine) {
|
||||||
errChan <- engine.WriteBookmarks(ctx, logger, inputPath, bookmarks)
|
errChan <- engine.WriteBookmarks(ctx, logger, inputPath, bookmarks)
|
||||||
}(engine)
|
}(engine)
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ type PdfEngines struct {
|
|||||||
writeMetadataNames []string
|
writeMetadataNames []string
|
||||||
encryptNames []string
|
encryptNames []string
|
||||||
embedNames []string
|
embedNames []string
|
||||||
writeBookmarksNames []string
|
|
||||||
readBookmarksNames []string
|
readBookmarksNames []string
|
||||||
|
writeBookmarksNames []string
|
||||||
engines []gotenberg.PdfEngine
|
engines []gotenberg.PdfEngine
|
||||||
disableRoutes bool
|
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-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-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-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-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")
|
fs.Bool("pdfengines-disable-routes", false, "Disable the routes")
|
||||||
|
|
||||||
// Deprecated flags.
|
// Deprecated flags.
|
||||||
@@ -85,8 +85,8 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
|||||||
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
|
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
|
||||||
encryptNames := flags.MustStringSlice("pdfengines-encrypt-engines")
|
encryptNames := flags.MustStringSlice("pdfengines-encrypt-engines")
|
||||||
embedNames := flags.MustStringSlice("pdfengines-embed-engines")
|
embedNames := flags.MustStringSlice("pdfengines-embed-engines")
|
||||||
bookmarksNames := flags.MustStringSlice("pdfengines-bookmarks-engines")
|
|
||||||
readBookmarksNames := flags.MustStringSlice("pdfengines-read-bookmarks-engines")
|
readBookmarksNames := flags.MustStringSlice("pdfengines-read-bookmarks-engines")
|
||||||
|
writeBookmarksNames := flags.MustStringSlice("pdfengines-write-bookmarks-engines")
|
||||||
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
mod.disableRoutes = flags.MustBool("pdfengines-disable-routes")
|
||||||
|
|
||||||
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
engines, err := ctx.Modules(new(gotenberg.PdfEngine))
|
||||||
@@ -153,16 +153,16 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
|||||||
mod.embedNames = embedNames
|
mod.embedNames = embedNames
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.writeBookmarksNames = defaultNames
|
|
||||||
if len(bookmarksNames) > 0 {
|
|
||||||
mod.writeBookmarksNames = bookmarksNames
|
|
||||||
}
|
|
||||||
|
|
||||||
mod.readBookmarksNames = defaultNames
|
mod.readBookmarksNames = defaultNames
|
||||||
if len(readBookmarksNames) > 0 {
|
if len(readBookmarksNames) > 0 {
|
||||||
mod.readBookmarksNames = readBookmarksNames
|
mod.readBookmarksNames = readBookmarksNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod.writeBookmarksNames = defaultNames
|
||||||
|
if len(writeBookmarksNames) > 0 {
|
||||||
|
mod.writeBookmarksNames = writeBookmarksNames
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,8 +212,8 @@ func (mod *PdfEngines) Validate() error {
|
|||||||
findNonExistingEngines(mod.writeMetadataNames)
|
findNonExistingEngines(mod.writeMetadataNames)
|
||||||
findNonExistingEngines(mod.encryptNames)
|
findNonExistingEngines(mod.encryptNames)
|
||||||
findNonExistingEngines(mod.embedNames)
|
findNonExistingEngines(mod.embedNames)
|
||||||
findNonExistingEngines(mod.writeBookmarksNames)
|
|
||||||
findNonExistingEngines(mod.readBookmarksNames)
|
findNonExistingEngines(mod.readBookmarksNames)
|
||||||
|
findNonExistingEngines(mod.writeBookmarksNames)
|
||||||
|
|
||||||
if len(nonExistingEngines) == 0 {
|
if len(nonExistingEngines) == 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -234,8 +234,8 @@ func (mod *PdfEngines) SystemMessages() []string {
|
|||||||
fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")),
|
fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")),
|
||||||
fmt.Sprintf("encrypt engines - %s", strings.Join(mod.encryptNames[:], " ")),
|
fmt.Sprintf("encrypt engines - %s", strings.Join(mod.encryptNames[:], " ")),
|
||||||
fmt.Sprintf("embed engines - %s", strings.Join(mod.embedNames[:], " ")),
|
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("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.writeMetadataNames),
|
||||||
engines(mod.encryptNames),
|
engines(mod.encryptNames),
|
||||||
engines(mod.embedNames),
|
engines(mod.embedNames),
|
||||||
engines(mod.writeBookmarksNames),
|
|
||||||
engines(mod.readBookmarksNames),
|
engines(mod.readBookmarksNames),
|
||||||
|
engines(mod.writeBookmarksNames),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ func WriteBookmarksStub(ctx *api.Context, engine gotenberg.PdfEngine, bookmarks
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
// Should not happen.
|
||||||
return fmt.Errorf("bookmarks type '%T' not supported", bookmarks)
|
return fmt.Errorf("bookmarks type '%T' not supported", bookmarks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
339
test/integration/features/pdfengines_bookmarks.feature
Normal file
339
test/integration/features/pdfengines_bookmarks.feature
Normal file
@@ -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"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
@pdfengines
|
@pdfengines
|
||||||
@pdfengines-metadata
|
@pdfengines-metadata
|
||||||
@metadata
|
@metadata
|
||||||
Feature: /forms/pdfengines/{write|read}
|
Feature: /forms/pdfengines/metadata/{write|read}
|
||||||
|
|
||||||
Scenario: POST /forms/pdfengines/metadata/{write|read} (Single PDF)
|
Scenario: POST /forms/pdfengines/metadata/{write|read} (Single PDF)
|
||||||
Given I have a default Gotenberg container
|
Given I have a default Gotenberg container
|
||||||
|
|||||||
Reference in New Issue
Block a user