From 874e78c6cd88641f57f2e23fc2e8b1f7fddaf372 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Wed, 4 Mar 2026 22:45:58 +0100 Subject: [PATCH] fix(pdfengines): correctly update the indexes if the bookmarks form field (map format) is given --- pkg/gotenberg/mocks.go | 5 + pkg/gotenberg/pdfengine.go | 3 + pkg/modules/exiftool/exiftool.go | 31 ++++++ .../libreoffice/pdfengine/pdfengine.go | 5 + pkg/modules/pdfcpu/pdfcpu.go | 5 + pkg/modules/pdfengines/multi.go | 36 +++++++ pkg/modules/pdfengines/routes.go | 43 ++++++-- pkg/modules/pdftk/pdftk.go | 5 + pkg/modules/qpdf/qpdf.go | 5 + test/integration/features/debug.feature | 4 + .../features/pdfengines_merge.feature | 99 ++++++++++++++++++- test/integration/scenario/scenario.go | 40 +++++--- 12 files changed, 257 insertions(+), 24 deletions(-) diff --git a/pkg/gotenberg/mocks.go b/pkg/gotenberg/mocks.go index 1df5f30f..16422569 100644 --- a/pkg/gotenberg/mocks.go +++ b/pkg/gotenberg/mocks.go @@ -51,6 +51,7 @@ type PdfEngineMock struct { FlattenMock func(ctx context.Context, logger *zap.Logger, inputPath string) error ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]any, error) + PageCountMock func(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]any, inputPath string) error ReadBookmarksMock func(ctx context.Context, logger *zap.Logger, inputPath string) ([]Bookmark, error) EncryptMock func(ctx context.Context, logger *zap.Logger, inputPath, userPassword, ownerPassword string) error @@ -78,6 +79,10 @@ func (engine *PdfEngineMock) ReadMetadata(ctx context.Context, logger *zap.Logge return engine.ReadMetadataMock(ctx, logger, inputPath) } +func (engine *PdfEngineMock) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + return engine.PageCountMock(ctx, logger, inputPath) +} + func (engine *PdfEngineMock) WriteMetadata(ctx context.Context, logger *zap.Logger, metadata map[string]any, inputPath string) error { return engine.WriteMetadataMock(ctx, logger, metadata, inputPath) } diff --git a/pkg/gotenberg/pdfengine.go b/pkg/gotenberg/pdfengine.go index 2cd50c0f..fd705998 100644 --- a/pkg/gotenberg/pdfengine.go +++ b/pkg/gotenberg/pdfengine.go @@ -143,6 +143,9 @@ type PdfEngine interface { // ReadMetadata extracts the metadata of a given PDF file. ReadMetadata(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]any, error) + // PageCount returns the number of pages in a PDF file. + PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) + // WriteMetadata writes the metadata into a given PDF file. WriteMetadata(ctx context.Context, logger *zap.Logger, metadata map[string]any, inputPath string) error diff --git a/pkg/modules/exiftool/exiftool.go b/pkg/modules/exiftool/exiftool.go index bbaede3e..8f12d6b0 100644 --- a/pkg/modules/exiftool/exiftool.go +++ b/pkg/modules/exiftool/exiftool.go @@ -203,6 +203,37 @@ func (engine *ExifTool) WriteMetadata(ctx context.Context, logger *zap.Logger, m return nil } +// PageCount returns the number of pages in a PDF file using ExifTool. +func (engine *ExifTool) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + metadata, err := engine.ReadMetadata(ctx, logger, inputPath) + if err != nil { + return 0, fmt.Errorf("read metadata with ExifTool: %w", err) + } + + pageCountValue, ok := metadata["PageCount"] + if !ok { + return 0, errors.New("PageCount not found in metadata") + } + + switch val := pageCountValue.(type) { + case int: + return val, nil + case int64: + return int(val), nil + case float64: + return int(val), nil + case string: + var res int + _, err := fmt.Sscanf(val, "%d", &res) + if err != nil { + return 0, fmt.Errorf("parse PageCount string '%s': %w", val, err) + } + return res, nil + default: + return 0, fmt.Errorf("unexpected PageCount type '%T'", pageCountValue) + } +} + // WriteBookmarks is not available in this implementation. func (engine *ExifTool) WriteBookmarks(ctx context.Context, logger *zap.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error { return fmt.Errorf("write PDF bookmarks with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/libreoffice/pdfengine/pdfengine.go b/pkg/modules/libreoffice/pdfengine/pdfengine.go index 52aecb5e..0dfc1ad4 100644 --- a/pkg/modules/libreoffice/pdfengine/pdfengine.go +++ b/pkg/modules/libreoffice/pdfengine/pdfengine.go @@ -91,6 +91,11 @@ func (engine *LibreOfficePdfEngine) WriteMetadata(ctx context.Context, logger *z return fmt.Errorf("write PDF metadata with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// PageCount is not available in this implementation. +func (engine *LibreOfficePdfEngine) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + return 0, fmt.Errorf("page count with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // WriteBookmarks is not available in this implementation. func (engine *LibreOfficePdfEngine) WriteBookmarks(ctx context.Context, logger *zap.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error { return fmt.Errorf("write PDF bookmarks with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/pdfcpu/pdfcpu.go b/pkg/modules/pdfcpu/pdfcpu.go index 8b4a02d0..3c75d341 100644 --- a/pkg/modules/pdfcpu/pdfcpu.go +++ b/pkg/modules/pdfcpu/pdfcpu.go @@ -183,6 +183,11 @@ func (engine *PdfCpu) WriteMetadata(ctx context.Context, logger *zap.Logger, met return fmt.Errorf("write PDF metadata with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// PageCount is not available in this implementation. +func (engine *PdfCpu) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + return 0, fmt.Errorf("page count with pdfcpu: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // ReadBookmarks reads the document outline (bookmarks) of a PDF file using pdfcpu. func (engine *PdfCpu) ReadBookmarks(ctx context.Context, logger *zap.Logger, inputPath string) ([]gotenberg.Bookmark, error) { tmpPath := fmt.Sprintf("%s.read.json", inputPath) diff --git a/pkg/modules/pdfengines/multi.go b/pkg/modules/pdfengines/multi.go index 6076ff5b..a1cef5bf 100644 --- a/pkg/modules/pdfengines/multi.go +++ b/pkg/modules/pdfengines/multi.go @@ -222,6 +222,42 @@ func (multi *multiPdfEngines) WriteMetadata(ctx context.Context, logger *zap.Log return fmt.Errorf("write PDF metadata with multi PDF engines: %w", err) } +type pageCountResult struct { + pageCount int + err error +} + +// PageCount returns the number of pages in a PDF file using the first available +// engine that supports metadata reading. +func (multi *multiPdfEngines) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + var err error + var mu sync.Mutex // to safely append errors. + + for _, engine := range multi.readMetadataEngines { + resultChan := make(chan pageCountResult, 1) + + go func(engine gotenberg.PdfEngine) { + pageCount, err := engine.PageCount(ctx, logger, inputPath) + resultChan <- pageCountResult{pageCount: pageCount, err: err} + }(engine) + + select { + case result := <-resultChan: + if result.err != nil { + mu.Lock() + err = multierr.Append(err, result.err) + mu.Unlock() + } else { + return result.pageCount, nil + } + case <-ctx.Done(): + return 0, ctx.Err() + } + } + + return 0, fmt.Errorf("page count with multi PDF engines: %w", err) +} + type readBookmarksResult struct { bookmarks []gotenberg.Bookmark err error diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index c902f5a2..e5f01f07 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -292,6 +292,21 @@ func WriteMetadataStub(ctx *api.Context, engine gotenberg.PdfEngine, metadata ma return nil } +func shiftBookmarks(bookmarks []gotenberg.Bookmark, offset int) []gotenberg.Bookmark { + if offset == 0 { + return bookmarks + } + shifted := make([]gotenberg.Bookmark, len(bookmarks)) + for i, b := range bookmarks { + shifted[i] = gotenberg.Bookmark{ + Title: b.Title, + Page: b.Page + offset, + Children: shiftBookmarks(b.Children, offset), + } + } + return shifted +} + // WriteBookmarksStub writes the bookmarks into PDF files. If no bookmarks, it // does nothing. func WriteBookmarksStub(ctx *api.Context, engine gotenberg.PdfEngine, bookmarks any, inputPaths []string) error { @@ -402,13 +417,6 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("validate form data: %w", err) } - if b, ok := bookmarks.(map[string][]gotenberg.Bookmark); ok { - err = WriteBookmarksStub(ctx, engine, b, inputPaths) - if err != nil { - return fmt.Errorf("write bookmarks: %w", err) - } - } - outputPath := ctx.GeneratePath(".pdf") err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath) if err != nil { @@ -425,8 +433,27 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("embed files into PDFs: %w", err) } + var finalBookmarks []gotenberg.Bookmark if b, ok := bookmarks.([]gotenberg.Bookmark); ok { - err = WriteBookmarksStub(ctx, engine, b, outputPaths) + finalBookmarks = b + } else if b, ok := bookmarks.(map[string][]gotenberg.Bookmark); ok { + offset := 0 + for _, inputPath := range inputPaths { + filename := filepath.Base(inputPath) + if fileBookmarks, ok := b[filename]; ok { + finalBookmarks = append(finalBookmarks, shiftBookmarks(fileBookmarks, offset)...) + } + + pageCount, err := engine.PageCount(ctx, ctx.Log(), inputPath) + if err != nil { + return fmt.Errorf("get page count of '%s': %w", filename, err) + } + offset += pageCount + } + } + + if len(finalBookmarks) > 0 { + err = WriteBookmarksStub(ctx, engine, finalBookmarks, outputPaths) if err != nil { return fmt.Errorf("write bookmarks: %w", err) } diff --git a/pkg/modules/pdftk/pdftk.go b/pkg/modules/pdftk/pdftk.go index 65298385..6abac934 100644 --- a/pkg/modules/pdftk/pdftk.go +++ b/pkg/modules/pdftk/pdftk.go @@ -145,6 +145,11 @@ func (engine *PdfTk) WriteMetadata(ctx context.Context, logger *zap.Logger, meta return fmt.Errorf("write PDF metadata with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// PageCount is not available in this implementation. +func (engine *PdfTk) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + return 0, fmt.Errorf("page count with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // WriteBookmarks is not available in this implementation. func (engine *PdfTk) WriteBookmarks(ctx context.Context, logger *zap.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error { return fmt.Errorf("write PDF bookmarks with PDFtk: %w", gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/pkg/modules/qpdf/qpdf.go b/pkg/modules/qpdf/qpdf.go index 70b79be3..d78613d9 100644 --- a/pkg/modules/qpdf/qpdf.go +++ b/pkg/modules/qpdf/qpdf.go @@ -172,6 +172,11 @@ func (engine *QPdf) WriteMetadata(ctx context.Context, logger *zap.Logger, metad return fmt.Errorf("write PDF metadata with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported) } +// PageCount is not available in this implementation. +func (engine *QPdf) PageCount(ctx context.Context, logger *zap.Logger, inputPath string) (int, error) { + return 0, fmt.Errorf("page count with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported) +} + // WriteBookmarks is not available in this implementation. func (engine *QPdf) WriteBookmarks(ctx context.Context, logger *zap.Logger, inputPath string, bookmarks []gotenberg.Bookmark) error { return fmt.Errorf("write PDF bookmarks with QPDF: %w", gotenberg.ErrPdfEngineMethodNotSupported) diff --git a/test/integration/features/debug.feature b/test/integration/features/debug.feature index 14eeb077..1ebb55b4 100644 --- a/test/integration/features/debug.feature +++ b/test/integration/features/debug.feature @@ -104,8 +104,10 @@ Feature: /debug "pdfengines-engines": "[]", "pdfengines-flatten-engines": "[qpdf]", "pdfengines-merge-engines": "[qpdf,pdfcpu,pdftk]", + "pdfengines-read-bookmarks-engines": "[pdfcpu]", "pdfengines-read-metadata-engines": "[exiftool]", "pdfengines-split-engines": "[pdfcpu,qpdf,pdftk]", + "pdfengines-write-bookmarks-engines": "[pdfcpu]", "pdfengines-write-metadata-engines": "[exiftool]", "prometheus-collect-interval": "1s", "prometheus-disable-collect": "false", @@ -224,8 +226,10 @@ Feature: /debug "pdfengines-engines": "[]", "pdfengines-flatten-engines": "[qpdf]", "pdfengines-merge-engines": "[qpdf,pdfcpu,pdftk]", + "pdfengines-read-bookmarks-engines": "[pdfcpu]", "pdfengines-read-metadata-engines": "[exiftool]", "pdfengines-split-engines": "[pdfcpu,qpdf,pdftk]", + "pdfengines-write-bookmarks-engines": "[pdfcpu]", "pdfengines-write-metadata-engines": "[exiftool]", "prometheus-collect-interval": "1s", "prometheus-disable-collect": "false", diff --git a/test/integration/features/pdfengines_merge.feature b/test/integration/features/pdfengines_merge.feature index bb520c78..1c1676c7 100644 --- a/test/integration/features/pdfengines_merge.feature +++ b/test/integration/features/pdfengines_merge.feature @@ -1,5 +1,5 @@ @pdfengines -@pdfengines-encrypt +@pdfengines-merge @merge Feature: /forms/pdfengines/merge @@ -130,6 +130,16 @@ Feature: /forms/pdfengines/merge """ Invalid form data: form field 'metadata' is invalid (got 'foo', resulting to unmarshal metadata: invalid character 'o' in literal false (expecting 'a')) """ + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.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')) + """ @convert Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1) @@ -203,6 +213,74 @@ Feature: /forms/pdfengines/merge } """ + @bookmarks + Scenario: POST /forms/pdfengines/merge (Bookmarks List) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): + | files | testdata/page_1.pdf | file | + | files | testdata/page_2.pdf | file | + | bookmarks | [{"title":"Merged 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 "/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": "Merged Index", + "page": 1 + } + ] + } + """ + + @bookmarks + Scenario: POST /forms/pdfengines/merge (Bookmarks Map) + Given I have a default Gotenberg container + When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" 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":"Page 1 Index","page":1,"children":[{"title":"Page 1 Sub-index","page":1}]}],"page_2.pdf":[{"title":"Page 2 Index","page":1,"children":[{"title":"Page 2 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" + 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": "Page 1 Index", + "page": 1, + "children": [ + { + "title": "Page 1 Sub-index", + "page": 1 + } + ] + }, + { + "title": "Page 2 Index", + "page": 2, + "children": [ + { + "title": "Page 2 Sub-index", + "page": 2 + } + ] + } + ] + } + """ + @flatten Scenario: POST /forms/pdfengines/merge (Flatten) Given I have a default Gotenberg container @@ -270,7 +348,8 @@ Feature: /forms/pdfengines/merge @metadata @flatten @embed - Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds) + @bookmarks + Scenario: POST /forms/pdfengines/merge (PDF/A-1b & PDF/UA-1 & Metadata & Flatten & Embeds & Bookmarks) Given I have a default Gotenberg container When I make a "POST" request to Gotenberg at the "/forms/pdfengines/merge" endpoint with the following form data and header(s): | files | testdata/page_1.pdf | file | @@ -278,6 +357,7 @@ Feature: /forms/pdfengines/merge | pdfa | PDF/A-1b | field | | pdfua | true | field | | metadata | {"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreateDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"} | field | + | bookmarks | [{"title":"Merged Index","page":1}] | field | | flatten | true | field | | embeds | testdata/embed_1.xml | file | | embeds | testdata/embed_2.xml | file | @@ -301,6 +381,21 @@ Feature: /forms/pdfengines/merge Then the response PDF(s) should be flatten Then the response PDF(s) should have the "embed_1.xml" file embedded Then the response PDF(s) should have the "embed_2.xml" file embedded + 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": "Merged Index", + "page": 1 + } + ] + } + """ When I make a "POST" request to Gotenberg at the "/forms/pdfengines/metadata/read" endpoint with the following form data and header(s): | files | teststore/foo.pdf | file | Then the response status code should be 200 diff --git a/test/integration/scenario/scenario.go b/test/integration/scenario/scenario.go index 29413268..bc8d6abc 100644 --- a/test/integration/scenario/scenario.go +++ b/test/integration/scenario/scenario.go @@ -27,6 +27,7 @@ type scenario struct { resp *httptest.ResponseRecorder concurrentResps []*httptest.ResponseRecorder workdir string + teststoreDir string gotenbergContainer testcontainers.Container gotenbergContainerNetwork *testcontainers.DockerNetwork server *server @@ -163,12 +164,14 @@ func (s *scenario) iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders(ct fields[name] = value case "file": if strings.Contains(value, "teststore") { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) - _, err := os.Stat(dirPath) - if os.IsNotExist(err) { - return fmt.Errorf("directory %q does not exist", dirPath) + if s.teststoreDir == "" { + return errors.New("no teststore directory available from previous requests") } - value = strings.ReplaceAll(value, "teststore", dirPath) + _, err := os.Stat(s.teststoreDir) + if os.IsNotExist(err) { + return fmt.Errorf("directory %q does not exist", s.teststoreDir) + } + value = strings.ReplaceAll(value, "teststore", s.teststoreDir) } else { wd, err := os.Getwd() if err != nil { @@ -216,6 +219,13 @@ func (s *scenario) iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders(ct return fmt.Errorf("write response body: %w", err) } + if resp.StatusCode == http.StatusNoContent { + // Gotenberg processes this asynchronously. The webhook test server + // will save the incoming files under this trace ID directory shortly. + s.teststoreDir = fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + return nil + } + if resp.StatusCode != http.StatusOK { return nil } @@ -241,6 +251,8 @@ func (s *scenario) iMakeARequestToGotenbergWithTheFollowingFormDataAndHeaders(ct return fmt.Errorf("create working directory: %w", err) } + s.teststoreDir = dirPath + fpath := fmt.Sprintf("%s/%s", dirPath, filename) file, err := os.Create(fpath) if err != nil { @@ -637,7 +649,7 @@ func (s *scenario) theBodyShouldMatchJSON(kind string, expectedDoc *godog.DocStr } func (s *scenario) thereShouldBePdfs(expected int, kind string) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) { @@ -666,7 +678,7 @@ func (s *scenario) thereShouldBePdfs(expected int, kind string) error { } func (s *scenario) thereShouldBeTheFollowingFiles(kind string, filesTable *godog.Table) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) { @@ -708,7 +720,7 @@ func (s *scenario) thereShouldBeTheFollowingFiles(kind string, filesTable *godog } func (s *scenario) thePdfsShouldBeValidWithAToleranceOf(ctx context.Context, kind, validate string, tolerance int) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) { @@ -788,7 +800,7 @@ func (s *scenario) thePdfShouldHavePages(ctx context.Context, name string, pages } } else { substr := strings.ReplaceAll(name, "*_", "") - err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + err := filepath.Walk(s.teststoreDir, func(currentPath string, info os.FileInfo, pathErr error) error { if pathErr != nil { return pathErr } @@ -844,7 +856,7 @@ func (s *scenario) thePdfShouldBeSetToLandscapeOrientation(ctx context.Context, } } else { substr := strings.ReplaceAll(name, "*_", "") - err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + err := filepath.Walk(s.teststoreDir, func(currentPath string, info os.FileInfo, pathErr error) error { if pathErr != nil { return pathErr } @@ -911,7 +923,7 @@ func (s *scenario) thePdfShouldHaveTheFollowingContentAtPage(ctx context.Context } } else { substr := strings.ReplaceAll(name, "*_", "") - err := filepath.Walk(fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")), func(currentPath string, info os.FileInfo, pathErr error) error { + err := filepath.Walk(s.teststoreDir, func(currentPath string, info os.FileInfo, pathErr error) error { if pathErr != nil { return pathErr } @@ -955,7 +967,7 @@ func (s *scenario) thePdfShouldHaveTheFollowingContentAtPage(ctx context.Context } func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should string) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) { @@ -1005,7 +1017,7 @@ func (s *scenario) thePdfsShouldBeFlatten(ctx context.Context, kind, should stri } func (s *scenario) thePdfsShouldBeEncrypted(ctx context.Context, kind string, should string) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) { @@ -1057,7 +1069,7 @@ func (s *scenario) thePdfsShouldBeEncrypted(ctx context.Context, kind string, sh } func (s *scenario) thePdfsShouldHaveEmbeddedFile(ctx context.Context, kind, should, embed string) error { - dirPath := fmt.Sprintf("%s/%s", s.workdir, s.resp.Header().Get("Gotenberg-Trace")) + dirPath := s.teststoreDir _, err := os.Stat(dirPath) if os.IsNotExist(err) {