mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 20:52:14 +01:00
feat(pdfengines): add split feature
This commit is contained in:
2
Makefile
2
Makefile
@@ -73,6 +73,7 @@ LOG_FORMAT=auto
|
|||||||
LOG_FIELDS_PREFIX=
|
LOG_FIELDS_PREFIX=
|
||||||
PDFENGINES_ENGINES=
|
PDFENGINES_ENGINES=
|
||||||
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
|
PDFENGINES_MERGE_ENGINES=qpdf,pdfcpu,pdftk
|
||||||
|
PDFENGINES_SPLIT_ENGINES=pdfcpu,qpdf,pdftk
|
||||||
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
|
PDFENGINES_CONVERT_ENGINES=libreoffice-pdfengine
|
||||||
PDFENGINES_READ_METADATA_ENGINES=exiftool
|
PDFENGINES_READ_METADATA_ENGINES=exiftool
|
||||||
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
|
PDFENGINES_WRITE_METADATA_ENGINES=exiftool
|
||||||
@@ -141,6 +142,7 @@ run: ## Start a Gotenberg container
|
|||||||
--log-fields-prefix=$(LOG_FIELDS_PREFIX) \
|
--log-fields-prefix=$(LOG_FIELDS_PREFIX) \
|
||||||
--pdfengines-engines=$(PDFENGINES_ENGINES) \
|
--pdfengines-engines=$(PDFENGINES_ENGINES) \
|
||||||
--pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \
|
--pdfengines-merge-engines=$(PDFENGINES_MERGE_ENGINES) \
|
||||||
|
--pdfengines-split-engines=$(PDFENGINES_SPLIT_ENGINES) \
|
||||||
--pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \
|
--pdfengines-convert-engines=$(PDFENGINES_CONVERT_ENGINES) \
|
||||||
--pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \
|
--pdfengines-read-metadata-engines=$(PDFENGINES_READ_METADATA_ENGINES) \
|
||||||
--pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \
|
--pdfengines-write-metadata-engines=$(PDFENGINES_WRITE_METADATA_ENGINES) \
|
||||||
|
|||||||
@@ -3,22 +3,56 @@ package gotenberg
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MkdirAll defines the method signature for create a directory. Implement this
|
||||||
|
// interface if you don't want to rely on [os.MkdirAll], notably for testing
|
||||||
|
// purpose.
|
||||||
|
type MkdirAll interface {
|
||||||
|
// MkdirAll uses the same signature as [os.MkdirAll].
|
||||||
|
MkdirAll(path string, perm os.FileMode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OsMkdirAll implements the [MkdirAll] interface with [os.MkdirAll].
|
||||||
|
type OsMkdirAll struct{}
|
||||||
|
|
||||||
|
// MkdirAll is a wrapper around [os.MkdirAll].
|
||||||
|
func (o *OsMkdirAll) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, perm) }
|
||||||
|
|
||||||
|
// PathRename defines the method signature for renaming files. Implement this
|
||||||
|
// interface if you don't want to rely on [os.Rename], notably for testing
|
||||||
|
// purpose.
|
||||||
|
type PathRename interface {
|
||||||
|
// Rename uses the same signature as [os.Rename].
|
||||||
|
Rename(oldpath, newpath string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// OsPathRename implements the [PathRename] interface with [os.Rename].
|
||||||
|
type OsPathRename struct{}
|
||||||
|
|
||||||
|
// Rename is a wrapper around [os.Rename].
|
||||||
|
func (o *OsPathRename) Rename(oldpath, newpath string) error {
|
||||||
|
return os.Rename(oldpath, newpath)
|
||||||
|
}
|
||||||
|
|
||||||
// FileSystem provides utilities for managing temporary directories. It creates
|
// FileSystem provides utilities for managing temporary directories. It creates
|
||||||
// unique directory names based on UUIDs to ensure isolation of temporary files
|
// unique directory names based on UUIDs to ensure isolation of temporary files
|
||||||
// for different modules.
|
// for different modules.
|
||||||
type FileSystem struct {
|
type FileSystem struct {
|
||||||
workingDir string
|
workingDir string
|
||||||
|
mkdirAll MkdirAll
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFileSystem initializes a new [FileSystem] instance with a unique working
|
// NewFileSystem initializes a new [FileSystem] instance with a unique working
|
||||||
// directory.
|
// directory.
|
||||||
func NewFileSystem() *FileSystem {
|
func NewFileSystem(mkdirAll MkdirAll) *FileSystem {
|
||||||
return &FileSystem{
|
return &FileSystem{
|
||||||
workingDir: uuid.NewString(),
|
workingDir: uuid.NewString(),
|
||||||
|
mkdirAll: mkdirAll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +78,7 @@ func (fs *FileSystem) NewDirPath() string {
|
|||||||
func (fs *FileSystem) MkdirAll() (string, error) {
|
func (fs *FileSystem) MkdirAll() (string, error) {
|
||||||
path := fs.NewDirPath()
|
path := fs.NewDirPath()
|
||||||
|
|
||||||
err := os.MkdirAll(path, 0o755)
|
err := fs.mkdirAll.MkdirAll(path, 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("create directory %s: %w", path, err)
|
return "", fmt.Errorf("create directory %s: %w", path, err)
|
||||||
}
|
}
|
||||||
@@ -52,10 +86,27 @@ func (fs *FileSystem) MkdirAll() (string, error) {
|
|||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PathRename defines the method signature for renaming files. Implement this
|
// WalkDir walks through the root level of a directory and returns a list of
|
||||||
// interface if you don't want to rely on [os.Rename], notably for testing
|
// files paths that match the specified file extension.
|
||||||
// purpose.
|
func WalkDir(dir, ext string) ([]string, error) {
|
||||||
type PathRename interface {
|
var files []string
|
||||||
// Rename uses the same signature as [os.Rename].
|
err := filepath.Walk(dir, func(path string, info os.FileInfo, pathErr error) error {
|
||||||
Rename(oldpath, newpath string) error
|
if pathErr != nil {
|
||||||
|
return pathErr
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.EqualFold(filepath.Ext(info.Name()), ext) {
|
||||||
|
files = append(files, path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return files, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Interface guards.
|
||||||
|
var (
|
||||||
|
_ MkdirAll = (*OsMkdirAll)(nil)
|
||||||
|
_ PathRename = (*OsPathRename)(nil)
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,14 +1,84 @@
|
|||||||
package gotenberg
|
package gotenberg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestOsMkdirAll_MkdirAll(t *testing.T) {
|
||||||
|
dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create working directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("remove working directory: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOsPathRename_Rename(t *testing.T) {
|
||||||
|
dirPath, err := NewFileSystem(new(OsMkdirAll)).MkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create working directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := "/tests/test/testdata/api/sample1.txt"
|
||||||
|
copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
|
||||||
|
|
||||||
|
in, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := in.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("close file: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
out, err := os.Create(copyPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create new file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err := out.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("close new file: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("copy file to new file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rename := new(OsPathRename)
|
||||||
|
newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
|
||||||
|
|
||||||
|
err = rename.Rename(copyPath, newPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("remove working directory: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFileSystem_WorkingDir(t *testing.T) {
|
func TestFileSystem_WorkingDir(t *testing.T) {
|
||||||
fs := NewFileSystem()
|
fs := NewFileSystem(new(MkdirAllMock))
|
||||||
dirName := fs.WorkingDir()
|
dirName := fs.WorkingDir()
|
||||||
|
|
||||||
if dirName == "" {
|
if dirName == "" {
|
||||||
@@ -17,7 +87,7 @@ func TestFileSystem_WorkingDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSystem_WorkingDirPath(t *testing.T) {
|
func TestFileSystem_WorkingDirPath(t *testing.T) {
|
||||||
fs := NewFileSystem()
|
fs := NewFileSystem(new(MkdirAllMock))
|
||||||
expectedPath := fmt.Sprintf("%s/%s", os.TempDir(), fs.WorkingDir())
|
expectedPath := fmt.Sprintf("%s/%s", os.TempDir(), fs.WorkingDir())
|
||||||
|
|
||||||
if fs.WorkingDirPath() != expectedPath {
|
if fs.WorkingDirPath() != expectedPath {
|
||||||
@@ -26,7 +96,7 @@ func TestFileSystem_WorkingDirPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSystem_NewDirPath(t *testing.T) {
|
func TestFileSystem_NewDirPath(t *testing.T) {
|
||||||
fs := NewFileSystem()
|
fs := NewFileSystem(new(MkdirAllMock))
|
||||||
newDir := fs.NewDirPath()
|
newDir := fs.NewDirPath()
|
||||||
expectedPrefix := fs.WorkingDirPath()
|
expectedPrefix := fs.WorkingDirPath()
|
||||||
|
|
||||||
@@ -36,20 +106,117 @@ func TestFileSystem_NewDirPath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFileSystem_MkdirAll(t *testing.T) {
|
func TestFileSystem_MkdirAll(t *testing.T) {
|
||||||
fs := NewFileSystem()
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
mkdirAll MkdirAll
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "error",
|
||||||
|
mkdirAll: &MkdirAllMock{
|
||||||
|
MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
mkdirAll: &MkdirAllMock{
|
||||||
|
MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
fs := NewFileSystem(tc.mkdirAll)
|
||||||
|
|
||||||
newPath, err := fs.MkdirAll()
|
_, err := fs.MkdirAll()
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = os.Stat(newPath)
|
if !tc.expectError && err != nil {
|
||||||
if os.IsNotExist(err) {
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
t.Errorf("expected directory '%s' to exist but it doesn't", newPath)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll(fs.WorkingDirPath())
|
if tc.expectError && err == nil {
|
||||||
if err != nil {
|
t.Fatal("expected error but got none")
|
||||||
t.Fatalf("expected no error while cleaning up but got: %v", err)
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalkDir(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
dir string
|
||||||
|
ext string
|
||||||
|
expectError bool
|
||||||
|
expectFiles []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "directory does not exist",
|
||||||
|
dir: uuid.NewString(),
|
||||||
|
ext: ".pdf",
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "find PDF files",
|
||||||
|
dir: func() string {
|
||||||
|
path := fmt.Sprintf("%s/a_directory", os.TempDir())
|
||||||
|
|
||||||
|
err := os.MkdirAll(path, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(fmt.Sprintf("expected no error but got: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprintf("%s/a_foo_file.pdf", path), []byte{1}, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprintf("%s/a_bar_file.PDF", path), []byte{1}, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprintf("%s/a_baz_file.txt", path), []byte{1}, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}(),
|
||||||
|
ext: ".pdf",
|
||||||
|
expectError: false,
|
||||||
|
expectFiles: []string{"/tmp/a_directory/a_bar_file.PDF", "/tmp/a_directory/a_foo_file.pdf"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
defer func() {
|
||||||
|
err := os.RemoveAll(tc.dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error while cleaning up but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
files, err := WalkDir(tc.dir, tc.ext)
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(files, tc.expectFiles) {
|
||||||
|
t.Errorf("expected files %+v, but got %+v", tc.expectFiles, files)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package gotenberg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@@ -36,6 +37,7 @@ func (mod *ValidatorMock) Validate() error {
|
|||||||
// PdfEngineMock is a mock for the [PdfEngine] interface.
|
// PdfEngineMock is a mock for the [PdfEngine] interface.
|
||||||
type PdfEngineMock struct {
|
type PdfEngineMock struct {
|
||||||
MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
|
MergeMock func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
|
||||||
|
SplitMock func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
|
||||||
ConvertMock func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath 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]interface{}, error)
|
ReadMetadataMock func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error)
|
||||||
WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error
|
WriteMetadataMock func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error
|
||||||
@@ -45,6 +47,10 @@ func (engine *PdfEngineMock) Merge(ctx context.Context, logger *zap.Logger, inpu
|
|||||||
return engine.MergeMock(ctx, logger, inputPaths, outputPath)
|
return engine.MergeMock(ctx, logger, inputPaths, outputPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *PdfEngineMock) Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return engine.SplitMock(ctx, logger, mode, inputPath, outputDirPath)
|
||||||
|
}
|
||||||
|
|
||||||
func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
func (engine *PdfEngineMock) Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
||||||
return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath)
|
return engine.ConvertMock(ctx, logger, formats, inputPath, outputPath)
|
||||||
}
|
}
|
||||||
@@ -137,6 +143,15 @@ func (provider *MetricsProviderMock) Metrics() ([]Metric, error) {
|
|||||||
return provider.MetricsMock()
|
return provider.MetricsMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MkdirAllMock is a mock for the [MkdirAll] interface.
|
||||||
|
type MkdirAllMock struct {
|
||||||
|
MkdirAllMock func(path string, perm os.FileMode) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mkdirAll *MkdirAllMock) MkdirAll(path string, perm os.FileMode) error {
|
||||||
|
return mkdirAll.MkdirAllMock(path, perm)
|
||||||
|
}
|
||||||
|
|
||||||
// PathRenameMock is a mock for the [PathRename] interface.
|
// PathRenameMock is a mock for the [PathRename] interface.
|
||||||
type PathRenameMock struct {
|
type PathRenameMock struct {
|
||||||
RenameMock func(oldpath, newpath string) error
|
RenameMock func(oldpath, newpath string) error
|
||||||
@@ -156,4 +171,6 @@ var (
|
|||||||
_ ProcessSupervisor = (*ProcessSupervisorMock)(nil)
|
_ ProcessSupervisor = (*ProcessSupervisorMock)(nil)
|
||||||
_ LoggerProvider = (*LoggerProviderMock)(nil)
|
_ LoggerProvider = (*LoggerProviderMock)(nil)
|
||||||
_ MetricsProvider = (*MetricsProviderMock)(nil)
|
_ MetricsProvider = (*MetricsProviderMock)(nil)
|
||||||
|
_ MkdirAll = (*MkdirAllMock)(nil)
|
||||||
|
_ PathRename = (*PathRenameMock)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package gotenberg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -52,6 +53,9 @@ func TestPDFEngineMock(t *testing.T) {
|
|||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@@ -68,6 +72,11 @@ func TestPDFEngineMock(t *testing.T) {
|
|||||||
t.Errorf("expected no error from PdfEngineMock.Merge, but got: %v", err)
|
t.Errorf("expected no error from PdfEngineMock.Merge, but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, err = mock.Split(context.Background(), zap.NewNop(), SplitMode{}, "", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error from PdfEngineMock.Split, but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "")
|
err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)
|
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)
|
||||||
@@ -205,6 +214,19 @@ func TestMetricsProviderMock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMkdirAllMock(t *testing.T) {
|
||||||
|
mock := &MkdirAllMock{
|
||||||
|
MkdirAllMock: func(dir string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mock.MkdirAll("/foo", 0o755)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error from MkdirAllMock.MkdirAll, but got: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPathRenameMock(t *testing.T) {
|
func TestPathRenameMock(t *testing.T) {
|
||||||
mock := &PathRenameMock{
|
mock := &PathRenameMock{
|
||||||
RenameMock: func(oldpath, newpath string) error {
|
RenameMock: func(oldpath, newpath string) error {
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ var (
|
|||||||
// PdfEngine interface is not supported by its current implementation.
|
// PdfEngine interface is not supported by its current implementation.
|
||||||
ErrPdfEngineMethodNotSupported = errors.New("method not supported")
|
ErrPdfEngineMethodNotSupported = errors.New("method not supported")
|
||||||
|
|
||||||
|
// ErrPdfSplitModeNotSupported is returned when the Split method of the
|
||||||
|
// PdfEngine interface does not sumport a requested PDF split mode.
|
||||||
|
ErrPdfSplitModeNotSupported = errors.New("split mode not supported")
|
||||||
|
|
||||||
// ErrPdfFormatNotSupported is returned when the Convert method of the
|
// ErrPdfFormatNotSupported is returned when the Convert method of the
|
||||||
// PdfEngine interface does not support a requested PDF format conversion.
|
// PdfEngine interface does not support a requested PDF format conversion.
|
||||||
ErrPdfFormatNotSupported = errors.New("PDF format not supported")
|
ErrPdfFormatNotSupported = errors.New("PDF format not supported")
|
||||||
@@ -21,6 +25,26 @@ var (
|
|||||||
ErrPdfEngineMetadataValueNotSupported = errors.New("metadata value not supported")
|
ErrPdfEngineMetadataValueNotSupported = errors.New("metadata value not supported")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SplitModeIntervals represents a mode where a PDF is split at specific
|
||||||
|
// intervals.
|
||||||
|
SplitModeIntervals string = "intervals"
|
||||||
|
|
||||||
|
// SplitModePages represents a mode where a PDF is split at specific page
|
||||||
|
// ranges.
|
||||||
|
SplitModePages string = "pages"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SplitMode gathers the data required to split a PDF into multiple parts.
|
||||||
|
type SplitMode struct {
|
||||||
|
// Mode is either "intervals" or "pages".
|
||||||
|
Mode string
|
||||||
|
|
||||||
|
// Span is either the intervals or the page ranges to extract, depending on
|
||||||
|
// the selected mode.
|
||||||
|
Span string
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// PdfA1a represents the PDF/A-1a format.
|
// PdfA1a represents the PDF/A-1a format.
|
||||||
PdfA1a string = "PDF/A-1a"
|
PdfA1a string = "PDF/A-1a"
|
||||||
@@ -65,6 +89,9 @@ type PdfEngine interface {
|
|||||||
// is determined by the order of files provided in inputPaths.
|
// is determined by the order of files provided in inputPaths.
|
||||||
Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
|
Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error
|
||||||
|
|
||||||
|
// Split splits a given PDF file.
|
||||||
|
Split(ctx context.Context, logger *zap.Logger, mode SplitMode, inputPath, outputDirPath string) ([]string, error)
|
||||||
|
|
||||||
// Convert transforms a given PDF to the specified formats defined in
|
// Convert transforms a given PDF to the specified formats defined in
|
||||||
// PdfFormats. If no format, it does nothing.
|
// PdfFormats. If no format, it does nothing.
|
||||||
Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error
|
Convert(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
a.logger = logger
|
a.logger = logger
|
||||||
|
|
||||||
// File system.
|
// File system.
|
||||||
a.fs = gotenberg.NewFileSystem()
|
a.fs = gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -850,7 +850,7 @@ func TestApi_Start(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
mod.readyFn = tc.readyFn
|
mod.readyFn = tc.readyFn
|
||||||
mod.fs = gotenberg.NewFileSystem()
|
mod.fs = gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
mod.logger = zap.NewNop()
|
mod.logger = zap.NewNop()
|
||||||
|
|
||||||
err := mod.Start()
|
err := mod.Start()
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ type Context struct {
|
|||||||
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
echoCtx echo.Context
|
echoCtx echo.Context
|
||||||
|
mkdirAll gotenberg.MkdirAll
|
||||||
pathRename gotenberg.PathRename
|
pathRename gotenberg.PathRename
|
||||||
context.Context
|
context.Context
|
||||||
}
|
}
|
||||||
@@ -81,12 +82,6 @@ type downloadFrom struct {
|
|||||||
ExtraHttpHeaders map[string]string `json:"extraHttpHeaders"`
|
ExtraHttpHeaders map[string]string `json:"extraHttpHeaders"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type osPathRename struct{}
|
|
||||||
|
|
||||||
func (o *osPathRename) Rename(oldpath, newpath string) error {
|
|
||||||
return os.Rename(oldpath, newpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// newContext returns a [Context] by parsing a "multipart/form-data" request.
|
// newContext returns a [Context] by parsing a "multipart/form-data" request.
|
||||||
func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration, bodyLimit int64, downloadFromCfg downloadFromConfig, traceHeader, trace string) (*Context, context.CancelFunc, error) {
|
func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSystem, timeout time.Duration, bodyLimit int64, downloadFromCfg downloadFromConfig, traceHeader, trace string) (*Context, context.CancelFunc, error) {
|
||||||
processCtx, processCancel := context.WithTimeout(context.Background(), timeout)
|
processCtx, processCancel := context.WithTimeout(context.Background(), timeout)
|
||||||
@@ -112,7 +107,8 @@ func newContext(echoCtx echo.Context, logger *zap.Logger, fs *gotenberg.FileSyst
|
|||||||
cancelled: false,
|
cancelled: false,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
echoCtx: echoCtx,
|
echoCtx: echoCtx,
|
||||||
pathRename: new(osPathRename),
|
mkdirAll: new(gotenberg.OsMkdirAll),
|
||||||
|
pathRename: new(gotenberg.OsPathRename),
|
||||||
Context: processCtx,
|
Context: processCtx,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -414,9 +410,21 @@ func (ctx *Context) GeneratePath(extension string) string {
|
|||||||
return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New().String(), extension)
|
return fmt.Sprintf("%s/%s%s", ctx.dirPath, uuid.New().String(), extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSubDirectory creates a subdirectory within the context's working
|
||||||
|
// directory.
|
||||||
|
func (ctx *Context) CreateSubDirectory(dirName string) (string, error) {
|
||||||
|
path := fmt.Sprintf("%s/%s", ctx.dirPath, dirName)
|
||||||
|
err := ctx.mkdirAll.MkdirAll(path, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("create sub-directory %s: %w", path, err)
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Rename is just a wrapper around [os.Rename], as we need to mock this
|
// Rename is just a wrapper around [os.Rename], as we need to mock this
|
||||||
// behavior in our tests.
|
// behavior in our tests.
|
||||||
func (ctx *Context) Rename(oldpath, newpath string) error {
|
func (ctx *Context) Rename(oldpath, newpath string) error {
|
||||||
|
ctx.Log().Debug(fmt.Sprintf("rename %s to %s", oldpath, newpath))
|
||||||
err := ctx.pathRename.Rename(oldpath, newpath)
|
err := ctx.pathRename.Rename(oldpath, newpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename path: %w", err)
|
return fmt.Errorf("rename path: %w", err)
|
||||||
@@ -496,8 +504,3 @@ func (ctx *Context) OutputFilename(outputPath string) string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath))
|
return fmt.Sprintf("%s%s", filename, filepath.Ext(outputPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface guard.
|
|
||||||
var (
|
|
||||||
_ gotenberg.PathRename = (*osPathRename)(nil)
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -4,78 +4,22 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestOsPathRename_Rename(t *testing.T) {
|
|
||||||
dirPath, err := gotenberg.NewFileSystem().MkdirAll()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("create working directory: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
path := "/tests/test/testdata/api/sample1.txt"
|
|
||||||
copyPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
|
|
||||||
|
|
||||||
in, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("open file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err := in.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("close file: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
out, err := os.Create(copyPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("create new file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
err := out.Close()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("close new file: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = io.Copy(out, in)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("copy file to new file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rename := new(osPathRename)
|
|
||||||
newPath := filepath.Join(dirPath, fmt.Sprintf("%s.txt", uuid.NewString()))
|
|
||||||
|
|
||||||
err = rename.Rename(copyPath, newPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("expected no error but got: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("remove working directory: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewContext(t *testing.T) {
|
func TestNewContext(t *testing.T) {
|
||||||
defaultAllowList, err := regexp2.Compile("", 0)
|
defaultAllowList, err := regexp2.Compile("", 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -548,7 +492,7 @@ func TestNewContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handler := func(c echo.Context) error {
|
handler := func(c echo.Context) error {
|
||||||
ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123")
|
ctx, cancel, err := newContext(c, zap.NewNop(), gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, tc.bodyLimit, tc.downloadFromCfg, "Gotenberg-Trace", "123")
|
||||||
defer cancel()
|
defer cancel()
|
||||||
// Context already cancelled.
|
// Context already cancelled.
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -647,6 +591,42 @@ func TestContext_FormData(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContext_CreateSubDirectory(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx *Context
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "failure",
|
||||||
|
ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return errors.New("cannot rename")
|
||||||
|
}}},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
ctx: &Context{mkdirAll: &gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}}},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
tc.ctx.logger = zap.NewNop()
|
||||||
|
_, err := tc.ctx.CreateSubDirectory("foo")
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContext_GeneratePath(t *testing.T) {
|
func TestContext_GeneratePath(t *testing.T) {
|
||||||
ctx := &Context{
|
ctx := &Context{
|
||||||
dirPath: "/foo",
|
dirPath: "/foo",
|
||||||
@@ -680,6 +660,7 @@ func TestContext_Rename(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
tc.ctx.logger = zap.NewNop()
|
||||||
err := tc.ctx.Rename("", "")
|
err := tc.ctx.Rename("", "")
|
||||||
|
|
||||||
if tc.expectError && err == nil {
|
if tc.expectError && err == nil {
|
||||||
@@ -788,7 +769,7 @@ func TestContext_BuildOutputFile(t *testing.T) {
|
|||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
dirPath, err := fs.MkdirAll()
|
dirPath, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected no erro but got: %v", err)
|
t.Fatalf("expected no erro but got: %v", err)
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ func ParseError(err error) (int, string) {
|
|||||||
return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)
|
return http.StatusTooManyRequests, http.StatusText(http.StatusTooManyRequests)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if errors.Is(err, gotenberg.ErrPdfSplitModeNotSupported) {
|
||||||
|
return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues"
|
||||||
|
}
|
||||||
|
|
||||||
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
if errors.Is(err, gotenberg.ErrPdfFormatNotSupported) {
|
||||||
return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues"
|
return http.StatusBadRequest, "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ func TestParseError(t *testing.T) {
|
|||||||
expectStatus: http.StatusTooManyRequests,
|
expectStatus: http.StatusTooManyRequests,
|
||||||
expectMessage: http.StatusText(http.StatusTooManyRequests),
|
expectMessage: http.StatusText(http.StatusTooManyRequests),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
err: gotenberg.ErrPdfSplitModeNotSupported,
|
||||||
|
expectStatus: http.StatusBadRequest,
|
||||||
|
expectMessage: "At least one PDF engine cannot process the requested PDF split mode, while others may have failed to split due to different issues",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
err: gotenberg.ErrPdfFormatNotSupported,
|
err: gotenberg.ErrPdfFormatNotSupported,
|
||||||
expectStatus: http.StatusBadRequest,
|
expectStatus: http.StatusBadRequest,
|
||||||
@@ -462,7 +467,7 @@ func TestContextMiddleware(t *testing.T) {
|
|||||||
c.Set("trace", "foo")
|
c.Set("trace", "foo")
|
||||||
c.Set("startTime", time.Now())
|
c.Set("startTime", time.Now())
|
||||||
|
|
||||||
err := contextMiddleware(gotenberg.NewFileSystem(), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c)
|
err := contextMiddleware(gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c)
|
||||||
|
|
||||||
if tc.expectErr && err == nil {
|
if tc.expectErr && err == nil {
|
||||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||||
|
|||||||
@@ -86,6 +86,14 @@ func (ctx *ContextMock) SetEchoContext(c echo.Context) {
|
|||||||
ctx.Context.echoCtx = c
|
ctx.Context.echoCtx = c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetMkdirAll sets the [gotenberg.MkdirAll].
|
||||||
|
//
|
||||||
|
// ctx := &api.ContextMock{Context: &api.Context{}}
|
||||||
|
// ctx.SetMkdirAll(mkdirAll)
|
||||||
|
func (ctx *ContextMock) SetMkdirAll(mkdirAll gotenberg.MkdirAll) {
|
||||||
|
ctx.Context.mkdirAll = mkdirAll
|
||||||
|
}
|
||||||
|
|
||||||
// SetPathRename sets the [gotenberg.PathRename].
|
// SetPathRename sets the [gotenberg.PathRename].
|
||||||
//
|
//
|
||||||
// ctx := &api.ContextMock{Context: &api.Context{}}
|
// ctx := &api.ContextMock{Context: &api.Context{}}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContextMock_SetDirPath(t *testing.T) {
|
func TestContextMock_SetDirPath(t *testing.T) {
|
||||||
@@ -117,10 +119,23 @@ func TestContextMock_SetEchoContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextMock_SetMkdirAll(t *testing.T) {
|
||||||
|
mock := ContextMock{&Context{}}
|
||||||
|
|
||||||
|
expect := new(gotenberg.OsMkdirAll)
|
||||||
|
mock.SetMkdirAll(expect)
|
||||||
|
|
||||||
|
actual := mock.mkdirAll
|
||||||
|
|
||||||
|
if actual != expect {
|
||||||
|
t.Errorf("expected %v but got %v", expect, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextMock_SetPathRename(t *testing.T) {
|
func TestContextMock_SetPathRename(t *testing.T) {
|
||||||
mock := ContextMock{&Context{}}
|
mock := ContextMock{&Context{}}
|
||||||
|
|
||||||
expect := new(osPathRename)
|
expect := new(gotenberg.OsPathRename)
|
||||||
mock.SetPathRename(expect)
|
mock.SetPathRename(expect)
|
||||||
|
|
||||||
actual := mock.pathRename
|
actual := mock.pathRename
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func newChromiumBrowser(arguments browserArguments) browser {
|
|||||||
b := &chromiumBrowser{
|
b := &chromiumBrowser{
|
||||||
initialCtx: context.Background(),
|
initialCtx: context.Background(),
|
||||||
arguments: arguments,
|
arguments: arguments,
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
}
|
}
|
||||||
b.isStarted.Store(false)
|
b.isStarted.Store(false)
|
||||||
|
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
b.isStarted.Store(false)
|
b.isStarted.Store(false)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -275,7 +275,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: true,
|
noDeadline: true,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -291,7 +291,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -308,7 +308,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -325,7 +325,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -357,7 +357,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -389,7 +389,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -424,7 +424,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -457,7 +457,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -495,7 +495,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -528,7 +528,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -554,7 +554,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -588,7 +588,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -621,7 +621,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -654,7 +654,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -688,7 +688,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -723,7 +723,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -758,7 +758,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -812,7 +812,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -845,7 +845,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -881,7 +881,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -914,7 +914,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -949,7 +949,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -984,7 +984,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1019,7 +1019,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1065,7 +1065,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1100,7 +1100,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1146,7 +1146,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1181,7 +1181,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1217,7 +1217,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1255,7 +1255,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1288,7 +1288,7 @@ func TestChromiumBrowser_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1421,7 +1421,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
b.isStarted.Store(false)
|
b.isStarted.Store(false)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -1437,7 +1437,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: true,
|
noDeadline: true,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -1453,7 +1453,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -1470,7 +1470,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
b.isStarted.Store(true)
|
b.isStarted.Store(true)
|
||||||
return b
|
return b
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
noDeadline: false,
|
noDeadline: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -1487,7 +1487,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1519,7 +1519,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1551,7 +1551,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1586,7 +1586,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1619,7 +1619,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1657,7 +1657,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1690,7 +1690,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1716,7 +1716,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1750,7 +1750,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1783,7 +1783,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1816,7 +1816,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1850,7 +1850,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1885,7 +1885,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1920,7 +1920,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1974,7 +1974,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2009,7 +2009,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2042,7 +2042,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2077,7 +2077,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2112,7 +2112,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2147,7 +2147,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2193,7 +2193,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2228,7 +2228,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2274,7 +2274,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2317,7 +2317,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -2365,7 +2365,7 @@ func TestChromiumBrowser_screenshot(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -326,8 +326,9 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
|
mode := pdfengines.FormDataPdfSplitMode(form, false)
|
||||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||||
metadata := pdfengines.FormDataPdfMetadata(form)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
err := form.
|
err := form.
|
||||||
@@ -337,7 +338,7 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert URL to PDF: %w", err)
|
return fmt.Errorf("convert URL to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -386,8 +387,9 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
|
mode := pdfengines.FormDataPdfSplitMode(form, false)
|
||||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||||
metadata := pdfengines.FormDataPdfMetadata(form)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
var inputPath string
|
var inputPath string
|
||||||
err := form.
|
err := form.
|
||||||
@@ -398,7 +400,7 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("file://%s", inputPath)
|
url := fmt.Sprintf("file://%s", inputPath)
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert HTML to PDF: %w", err)
|
return fmt.Errorf("convert HTML to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -448,8 +450,9 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
form, options := FormDataChromiumPdfOptions(ctx)
|
form, options := FormDataChromiumPdfOptions(ctx)
|
||||||
|
mode := pdfengines.FormDataPdfSplitMode(form, false)
|
||||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||||
metadata := pdfengines.FormDataPdfMetadata(form)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inputPath string
|
inputPath string
|
||||||
@@ -469,7 +472,7 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route {
|
|||||||
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
return fmt.Errorf("transform markdown file(s) to HTML: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = convertUrl(ctx, chromium, engine, url, options, pdfFormats, metadata)
|
err = convertUrl(ctx, chromium, engine, url, options, mode, pdfFormats, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert markdown to PDF: %w", err)
|
return fmt.Errorf("convert markdown to PDF: %w", err)
|
||||||
}
|
}
|
||||||
@@ -593,7 +596,7 @@ func markdownToHtml(ctx *api.Context, inputPath string, markdownPaths []string)
|
|||||||
return fmt.Sprintf("file://%s", inputPath), nil
|
return fmt.Sprintf("file://%s", inputPath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, pdfFormats gotenberg.PdfFormats, metadata map[string]interface{}) error {
|
func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url string, options PdfOptions, mode gotenberg.SplitMode, pdfFormats gotenberg.PdfFormats, metadata map[string]interface{}) error {
|
||||||
outputPath := ctx.GeneratePath(".pdf")
|
outputPath := ctx.GeneratePath(".pdf")
|
||||||
|
|
||||||
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
err := chromium.Pdf(ctx, ctx.Log(), url, outputPath, options)
|
||||||
@@ -632,16 +635,34 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url
|
|||||||
return fmt.Errorf("convert to PDF: %w", err)
|
return fmt.Errorf("convert to PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
outputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, []string{outputPath})
|
outputPaths, err := pdfengines.SplitPdfStub(ctx, engine, mode, []string{outputPath})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert PDF: %w", err)
|
return fmt.Errorf("split PDF: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths)
|
convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convert PDF(s): %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = pdfengines.WriteMetadataStub(ctx, engine, metadata, convertOutputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write metadata: %w", err)
|
return fmt.Errorf("write metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zeroValuedSplitMode := gotenberg.SplitMode{}
|
||||||
|
zeroValuedPdfFormats := gotenberg.PdfFormats{}
|
||||||
|
if mode != zeroValuedSplitMode && pdfFormats != zeroValuedPdfFormats {
|
||||||
|
// The PDF has been split and split parts have been converted to a
|
||||||
|
// specific format. We want to keep the split naming.
|
||||||
|
for i, convertOutputPath := range convertOutputPaths {
|
||||||
|
err = ctx.Rename(convertOutputPath, outputPaths[i])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rename output path: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = ctx.AddOutputPaths(outputPaths...)
|
err = ctx.AddOutputPaths(outputPaths...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("add output paths: %w", err)
|
return fmt.Errorf("add output paths: %w", err)
|
||||||
|
|||||||
@@ -1428,6 +1428,7 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
api Api
|
api Api
|
||||||
engine gotenberg.PdfEngine
|
engine gotenberg.PdfEngine
|
||||||
options PdfOptions
|
options PdfOptions
|
||||||
|
splitMode gotenberg.SplitMode
|
||||||
pdfFormats gotenberg.PdfFormats
|
pdfFormats gotenberg.PdfFormats
|
||||||
metadata map[string]interface{}
|
metadata map[string]interface{}
|
||||||
expectError bool
|
expectError bool
|
||||||
@@ -1570,6 +1571,36 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "PDF engine split error",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
}},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success with split mode",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
engine: &gotenberg.PdfEngineMock{SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
}},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "PDF engine convert error",
|
scenario: "PDF engine convert error",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1600,6 +1631,27 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "success with split mode and PDF formats",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
api: &ApiMock{PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||||
|
return nil
|
||||||
|
}},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: DefaultPdfOptions(),
|
||||||
|
splitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"},
|
||||||
|
pdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "PDF engine write metadata error",
|
scenario: "PDF engine write metadata error",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
@@ -1659,7 +1711,13 @@ func TestConvertUrl(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
err := convertUrl(tc.ctx.Context, tc.api, tc.engine, "", tc.options, tc.pdfFormats, tc.metadata)
|
tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
err := convertUrl(tc.ctx.Context, tc.api, tc.engine, "", tc.options, tc.splitMode, tc.pdfFormats, tc.metadata)
|
||||||
|
|
||||||
if tc.expectError && err == nil {
|
if tc.expectError && err == nil {
|
||||||
t.Fatal("expected error but got none", err)
|
t.Fatal("expected error but got none", err)
|
||||||
|
|||||||
@@ -58,6 +58,11 @@ func (engine *ExifTool) Merge(ctx context.Context, logger *zap.Logger, inputPath
|
|||||||
return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split is not available in this implementation.
|
||||||
|
func (engine *ExifTool) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, fmt.Errorf("split PDF with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *ExifTool) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *ExifTool) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("convert PDF to '%+v' with ExifTool: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ func TestExiftool_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExiftool_Split(t *testing.T) {
|
||||||
|
engine := new(ExifTool)
|
||||||
|
_, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "")
|
||||||
|
|
||||||
|
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||||
|
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExiftool_Convert(t *testing.T) {
|
func TestExiftool_Convert(t *testing.T) {
|
||||||
engine := new(ExifTool)
|
engine := new(ExifTool)
|
||||||
err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||||
@@ -257,7 +266,7 @@ func TestExiftool_WriteMetadata(t *testing.T) {
|
|||||||
|
|
||||||
var destinationPath string
|
var destinationPath string
|
||||||
if tc.createCopy {
|
if tc.createCopy {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
outputDir, err := fs.MkdirAll()
|
outputDir, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected error no but got: %v", err)
|
t.Fatalf("expected error no but got: %v", err)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type libreOfficeProcess struct {
|
|||||||
func newLibreOfficeProcess(arguments libreOfficeArguments) libreOffice {
|
func newLibreOfficeProcess(arguments libreOfficeArguments) libreOffice {
|
||||||
p := &libreOfficeProcess{
|
p := &libreOfficeProcess{
|
||||||
arguments: arguments,
|
arguments: arguments,
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
}
|
}
|
||||||
p.isStarted.Store(false)
|
p.isStarted.Store(false)
|
||||||
|
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
p.isStarted.Store(false)
|
p.isStarted.Store(false)
|
||||||
return p
|
return p
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
cancelledCtx: false,
|
cancelledCtx: false,
|
||||||
start: false,
|
start: false,
|
||||||
expectError: true,
|
expectError: true,
|
||||||
@@ -243,7 +243,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
p.isStarted.Store(true)
|
p.isStarted.Store(true)
|
||||||
return p
|
return p
|
||||||
}(),
|
}(),
|
||||||
fs: gotenberg.NewFileSystem(),
|
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: "foo"}},
|
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: "foo"}},
|
||||||
cancelledCtx: false,
|
cancelledCtx: false,
|
||||||
start: false,
|
start: false,
|
||||||
@@ -261,7 +261,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
),
|
),
|
||||||
options: Options{PageRanges: "foo"},
|
options: Options{PageRanges: "foo"},
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -291,7 +291,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
),
|
),
|
||||||
options: Options{Password: "foo"},
|
options: Options{Password: "foo"},
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -344,7 +344,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -372,7 +372,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -400,7 +400,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -452,7 +452,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -481,7 +481,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -510,7 +510,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -539,7 +539,7 @@ func TestLibreOfficeProcess_pdf(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -625,7 +625,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) {
|
|||||||
{
|
{
|
||||||
scenario: "basic latin characters",
|
scenario: "basic latin characters",
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -646,7 +646,7 @@ func TestNonBasicLatinCharactersGuard(t *testing.T) {
|
|||||||
{
|
{
|
||||||
scenario: "non-basic latin characters",
|
scenario: "non-basic latin characters",
|
||||||
fs: func() *gotenberg.FileSystem {
|
fs: func() *gotenberg.FileSystem {
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
|
||||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ func (engine *LibreOfficePdfEngine) Merge(ctx context.Context, logger *zap.Logge
|
|||||||
return fmt.Errorf("merge PDFs with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("merge PDFs with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split is not available in this implementation.
|
||||||
|
func (engine *LibreOfficePdfEngine) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, fmt.Errorf("split PDF with LibreOffice: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert converts the given PDF to a specific PDF format. Currently, only the
|
// Convert converts the given PDF to a specific PDF format. Currently, only the
|
||||||
// PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/UA formats are available. If another
|
// PDF/A-1b, PDF/A-2b, PDF/A-3b and PDF/UA formats are available. If another
|
||||||
// PDF format is requested, it returns a [gotenberg.ErrPdfFormatNotSupported]
|
// PDF format is requested, it returns a [gotenberg.ErrPdfFormatNotSupported]
|
||||||
|
|||||||
@@ -118,11 +118,21 @@ func TestLibreOfficePdfEngine_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLibreOfficePdfEngine_Split(t *testing.T) {
|
||||||
|
engine := new(LibreOfficePdfEngine)
|
||||||
|
_, err := engine.Split(context.Background(), zap.NewNop(), gotenberg.SplitMode{}, "", "")
|
||||||
|
|
||||||
|
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||||
|
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLibreOfficePdfEngine_Convert(t *testing.T) {
|
func TestLibreOfficePdfEngine_Convert(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
api api.Uno
|
api api.Uno
|
||||||
expectError bool
|
expectError bool
|
||||||
|
expectedError error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "convert success",
|
scenario: "convert success",
|
||||||
@@ -134,13 +144,14 @@ func TestLibreOfficePdfEngine_Convert(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid PDF format",
|
scenario: "ErrInvalidPdfFormats",
|
||||||
api: &api.ApiMock{
|
api: &api.ApiMock{
|
||||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error {
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error {
|
||||||
return api.ErrInvalidPdfFormats
|
return api.ErrInvalidPdfFormats
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectError: true,
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrPdfFormatNotSupported,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "convert fail",
|
scenario: "convert fail",
|
||||||
@@ -163,6 +174,10 @@ func TestLibreOfficePdfEngine_Convert(t *testing.T) {
|
|||||||
if tc.expectError && err == nil {
|
if tc.expectError && err == nil {
|
||||||
t.Fatal("expected error but got none")
|
t.Fatal("expected error but got none")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
|
||||||
|
t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,8 +28,11 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
defaultOptions := libreofficeapi.DefaultOptions()
|
defaultOptions := libreofficeapi.DefaultOptions()
|
||||||
|
|
||||||
form := ctx.FormData()
|
form := ctx.FormData()
|
||||||
|
splitMode := pdfengines.FormDataPdfSplitMode(form, false)
|
||||||
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
pdfFormats := pdfengines.FormDataPdfFormats(form)
|
||||||
metadata := pdfengines.FormDataPdfMetadata(form)
|
metadata := pdfengines.FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
|
zeroValuedSplitMode := gotenberg.SplitMode{}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
inputPaths []string
|
inputPaths []string
|
||||||
@@ -165,7 +168,9 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
MaxImageResolution: maxImageResolution,
|
MaxImageResolution: maxImageResolution,
|
||||||
}
|
}
|
||||||
|
|
||||||
if nativePdfFormats {
|
if nativePdfFormats && splitMode == zeroValuedSplitMode {
|
||||||
|
// Only apply natively given PDF formats if we're not
|
||||||
|
// splitting the PDF later.
|
||||||
options.PdfFormats = pdfFormats
|
options.PdfFormats = pdfFormats
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,11 +214,44 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
outputPaths = []string{outputPath}
|
outputPaths = []string{outputPath}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !nativePdfFormats {
|
if splitMode != zeroValuedSplitMode {
|
||||||
outputPaths, err = pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths)
|
if !merge {
|
||||||
|
// document.docx -> document.docx.pdf, so that split naming
|
||||||
|
// document.docx_0.pdf, etc.
|
||||||
|
for i, inputPath := range inputPaths {
|
||||||
|
outputPath := fmt.Sprintf("%s.pdf", inputPath)
|
||||||
|
|
||||||
|
err = ctx.Rename(outputPaths[i], outputPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rename output path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPaths[i] = outputPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPaths, err = pdfengines.SplitPdfStub(ctx, engine, splitMode, outputPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("split PDFs: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !nativePdfFormats || (nativePdfFormats && splitMode != zeroValuedSplitMode) {
|
||||||
|
convertOutputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("convert PDFs: %w", err)
|
return fmt.Errorf("convert PDFs: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if splitMode != zeroValuedSplitMode {
|
||||||
|
// The PDF has been split and split parts have been converted to
|
||||||
|
// specific formats. We want to keep the split naming.
|
||||||
|
for i, convertOutputPath := range convertOutputPaths {
|
||||||
|
err = ctx.Rename(convertOutputPath, outputPaths[i])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rename output path: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths)
|
err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths)
|
||||||
@@ -221,7 +259,7 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
|||||||
return fmt.Errorf("write metadata: %w", err)
|
return fmt.Errorf("write metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(outputPaths) > 1 {
|
if len(outputPaths) > 1 && splitMode == zeroValuedSplitMode {
|
||||||
// If .zip archive, document.docx -> document.docx.pdf.
|
// If .zip archive, document.docx -> document.docx.pdf.
|
||||||
for i, inputPath := range inputPaths {
|
for i, inputPath := range inputPaths {
|
||||||
outputPath := fmt.Sprintf("%s.pdf", inputPath)
|
outputPath := fmt.Sprintf("%s.pdf", inputPath)
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ package libreoffice
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -300,6 +303,40 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "PDF engine split error",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "PDF engine convert error",
|
scenario: "PDF engine convert error",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -365,32 +402,6 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 0,
|
expectOutputPathsCount: 0,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
scenario: "cannot rename many files",
|
|
||||||
ctx: func() *api.ContextMock {
|
|
||||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
|
||||||
ctx.SetFiles(map[string]string{
|
|
||||||
"document.docx": "/document.docx",
|
|
||||||
"document2.docx": "/document2.docx",
|
|
||||||
"document2.doc": "/document2.doc",
|
|
||||||
})
|
|
||||||
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
|
||||||
return errors.New("cannot rename")
|
|
||||||
}})
|
|
||||||
return ctx
|
|
||||||
}(),
|
|
||||||
libreOffice: &libreofficeapi.ApiMock{
|
|
||||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
ExtensionsMock: func() []string {
|
|
||||||
return []string{".docx", ".doc"}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expectError: true,
|
|
||||||
expectHttpError: false,
|
|
||||||
expectOutputPathsCount: 0,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
scenario: "cannot add output paths",
|
scenario: "cannot add output paths",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -550,9 +561,173 @@ func TestConvertRoute(t *testing.T) {
|
|||||||
expectHttpError: false,
|
expectHttpError: false,
|
||||||
expectOutputPathsCount: 1,
|
expectOutputPathsCount: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "success with split (many files)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))]
|
||||||
|
filenameNoExt := filepath.Base(inputPathNoExt)
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 0,
|
||||||
|
),
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 1,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 4,
|
||||||
|
expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success with merge and split",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"merge": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))]
|
||||||
|
filenameNoExt := filepath.Base(inputPathNoExt)
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 0,
|
||||||
|
),
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 1,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success with split and native PDF/A & PDF/UA (many files)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"document.docx": "/document.docx",
|
||||||
|
"document2.docx": "/document2.docx",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
"pdfa": {
|
||||||
|
gotenberg.PdfA1b,
|
||||||
|
},
|
||||||
|
"pdfua": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
libreOffice: &libreofficeapi.ApiMock{
|
||||||
|
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
ExtensionsMock: func() []string {
|
||||||
|
return []string{".docx"}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))]
|
||||||
|
filenameNoExt := filepath.Base(inputPathNoExt)
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 0,
|
||||||
|
),
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, 1,
|
||||||
|
),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 4,
|
||||||
|
expectOutputPaths: []string{"/document_docx/document.docx_0.pdf", "/document_docx/document.docx_1.pdf", "/document2_docx/document2.docx_0.pdf", "/document2_docx/document2.docx_1.pdf"},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
|
tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
c := echo.New().NewContext(nil, nil)
|
c := echo.New().NewContext(nil, nil)
|
||||||
c.Set("context", tc.ctx.Context)
|
c.Set("context", tc.ctx.Context)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// interface using the pdfcpu command-line tool. This package allows for:
|
// interface using the pdfcpu command-line tool. This package allows for:
|
||||||
//
|
//
|
||||||
// 1. The merging of PDF files.
|
// 1. The merging of PDF files.
|
||||||
|
// 2. The splitting of PDF files.
|
||||||
//
|
//
|
||||||
// See: https://github.com/pdfcpu/pdfcpu.
|
// See: https://github.com/pdfcpu/pdfcpu.
|
||||||
package pdfcpu
|
package pdfcpu
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -70,6 +71,38 @@ func (engine *PdfCpu) Merge(ctx context.Context, logger *zap.Logger, inputPaths
|
|||||||
return fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
return fmt.Errorf("merge PDFs with pdfcpu: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split splits a given PDF file.
|
||||||
|
func (engine *PdfCpu) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
switch mode.Mode {
|
||||||
|
case gotenberg.SplitModeIntervals:
|
||||||
|
args = append(args, "split", "-mode", "span", inputPath, outputDirPath, mode.Span)
|
||||||
|
case gotenberg.SplitModePages:
|
||||||
|
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
||||||
|
args = append(args, "trim", "-pages", mode.Span, inputPath, outputPath)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("split PDFs using mode '%s' with pdfcpu: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cmd.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split PDFs with pdfcpu: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPaths, err := gotenberg.WalkDir(outputDirPath, ".pdf")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("walk directory to find resulting PDFs from split with pdfcpu: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputPaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Convert is not available in this implementation.
|
// Convert is not available in this implementation.
|
||||||
func (engine *PdfCpu) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (engine *PdfCpu) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("convert PDF to '%+v' with pdfcpu: %w", formats, gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func TestPdfCpu_Merge(t *testing.T) {
|
|||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
outputDir, err := fs.MkdirAll()
|
outputDir, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
@@ -142,6 +142,95 @@ func TestPdfCpu_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfCpu_Split(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx context.Context
|
||||||
|
mode gotenberg.SplitMode
|
||||||
|
inputPath string
|
||||||
|
expectError bool
|
||||||
|
expectedError error
|
||||||
|
expectOutputPathsCount int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "ErrPdfSplitModeNotSupported",
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrPdfSplitModeNotSupported,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid context",
|
||||||
|
ctx: nil,
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid input path",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
inputPath: "",
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (intervals)",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||||
|
expectError: false,
|
||||||
|
expectOutputPathsCount: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (pages)",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"},
|
||||||
|
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||||
|
expectError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
engine := new(PdfCpu)
|
||||||
|
err := engine.Provision(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
outputDir, err := fs.MkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = os.RemoveAll(fs.WorkingDirPath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error while cleaning up but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir)
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
|
||||||
|
t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectOutputPathsCount != len(outputPaths) {
|
||||||
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPdfCpu_Convert(t *testing.T) {
|
func TestPdfCpu_Convert(t *testing.T) {
|
||||||
mod := new(PdfCpu)
|
mod := new(PdfCpu)
|
||||||
err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
type multiPdfEngines struct {
|
type multiPdfEngines struct {
|
||||||
mergeEngines []gotenberg.PdfEngine
|
mergeEngines []gotenberg.PdfEngine
|
||||||
|
splitEngines []gotenberg.PdfEngine
|
||||||
convertEngines []gotenberg.PdfEngine
|
convertEngines []gotenberg.PdfEngine
|
||||||
readMedataEngines []gotenberg.PdfEngine
|
readMedataEngines []gotenberg.PdfEngine
|
||||||
writeMedataEngines []gotenberg.PdfEngine
|
writeMedataEngines []gotenberg.PdfEngine
|
||||||
@@ -20,12 +21,14 @@ type multiPdfEngines struct {
|
|||||||
|
|
||||||
func newMultiPdfEngines(
|
func newMultiPdfEngines(
|
||||||
mergeEngines,
|
mergeEngines,
|
||||||
|
splitEngines,
|
||||||
convertEngines,
|
convertEngines,
|
||||||
readMetadataEngines,
|
readMetadataEngines,
|
||||||
writeMedataEngines []gotenberg.PdfEngine,
|
writeMedataEngines []gotenberg.PdfEngine,
|
||||||
) *multiPdfEngines {
|
) *multiPdfEngines {
|
||||||
return &multiPdfEngines{
|
return &multiPdfEngines{
|
||||||
mergeEngines: mergeEngines,
|
mergeEngines: mergeEngines,
|
||||||
|
splitEngines: splitEngines,
|
||||||
convertEngines: convertEngines,
|
convertEngines: convertEngines,
|
||||||
readMedataEngines: readMetadataEngines,
|
readMedataEngines: readMetadataEngines,
|
||||||
writeMedataEngines: writeMedataEngines,
|
writeMedataEngines: writeMedataEngines,
|
||||||
@@ -57,6 +60,44 @@ func (multi *multiPdfEngines) Merge(ctx context.Context, logger *zap.Logger, inp
|
|||||||
return fmt.Errorf("merge PDFs with multi PDF engines: %w", err)
|
return fmt.Errorf("merge PDFs with multi PDF engines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type splitResult struct {
|
||||||
|
outputPaths []string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split tries to split at intervals a given PDF thanks to its children. If the
|
||||||
|
// context is done, it stops and returns an error.
|
||||||
|
func (multi *multiPdfEngines) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
var err error
|
||||||
|
var mu sync.Mutex // to safely append errors.
|
||||||
|
|
||||||
|
resultChan := make(chan splitResult, len(multi.splitEngines))
|
||||||
|
|
||||||
|
for _, engine := range multi.splitEngines {
|
||||||
|
go func(engine gotenberg.PdfEngine) {
|
||||||
|
outputPaths, err := engine.Split(ctx, logger, mode, inputPath, outputDirPath)
|
||||||
|
resultChan <- splitResult{outputPaths: outputPaths, err: err}
|
||||||
|
}(engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
for range multi.splitEngines {
|
||||||
|
select {
|
||||||
|
case result := <-resultChan:
|
||||||
|
if result.err != nil {
|
||||||
|
mu.Lock()
|
||||||
|
err = multierr.Append(err, result.err)
|
||||||
|
mu.Unlock()
|
||||||
|
} else {
|
||||||
|
return result.outputPaths, nil
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("split PDF with multi PDF engines: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Convert converts the given PDF to a specific PDF format. thanks to its
|
// Convert converts the given PDF to a specific PDF format. thanks to its
|
||||||
// children. If the context is done, it stops and returns an error.
|
// children. If the context is done, it stops and returns an error.
|
||||||
func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
func (multi *multiPdfEngines) Convert(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
|||||||
@@ -19,25 +19,22 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "nominal behavior",
|
scenario: "nominal behavior",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
[]gotenberg.PdfEngine{
|
mergeEngines: []gotenberg.PdfEngine{
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "at least one engine does not return an error",
|
scenario: "at least one engine does not return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
[]gotenberg.PdfEngine{
|
mergeEngines: []gotenberg.PdfEngine{
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -49,17 +46,14 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: false,
|
expectError: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "all engines return an error",
|
scenario: "all engines return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
[]gotenberg.PdfEngine{
|
mergeEngines: []gotenberg.PdfEngine{
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -71,27 +65,21 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "context expired",
|
scenario: "context expired",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
[]gotenberg.PdfEngine{
|
mergeEngines: []gotenberg.PdfEngine{
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: func() context.Context {
|
ctx: func() context.Context {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
@@ -115,6 +103,97 @@ func TestMultiPdfEngines_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMultiPdfEngines_Split(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
engine *multiPdfEngines
|
||||||
|
ctx context.Context
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "nominal behavior",
|
||||||
|
engine: &multiPdfEngines{
|
||||||
|
splitEngines: []gotenberg.PdfEngine{
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: context.Background(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "at least one engine does not return an error",
|
||||||
|
engine: &multiPdfEngines{
|
||||||
|
splitEngines: []gotenberg.PdfEngine{
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: context.Background(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "all engines return an error",
|
||||||
|
engine: &multiPdfEngines{
|
||||||
|
splitEngines: []gotenberg.PdfEngine{
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: context.Background(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "context expired",
|
||||||
|
engine: &multiPdfEngines{
|
||||||
|
splitEngines: []gotenberg.PdfEngine{
|
||||||
|
&gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ctx: func() context.Context {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
_, err := tc.engine.Split(tc.ctx, zap.NewNop(), gotenberg.SplitMode{}, "", "")
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultiPdfEngines_Convert(t *testing.T) {
|
func TestMultiPdfEngines_Convert(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
@@ -124,25 +203,21 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "nominal behavior",
|
scenario: "nominal behavior",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
convertEngines: []gotenberg.PdfEngine{
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "at least one engine does not return an error",
|
scenario: "at least one engine does not return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
convertEngines: []gotenberg.PdfEngine{
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -154,16 +229,13 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "all engines return an error",
|
scenario: "all engines return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
convertEngines: []gotenberg.PdfEngine{
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -175,26 +247,21 @@ func TestMultiPdfEngines_Convert(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "context expired",
|
scenario: "context expired",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
convertEngines: []gotenberg.PdfEngine{
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
nil,
|
|
||||||
),
|
|
||||||
ctx: func() context.Context {
|
ctx: func() context.Context {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
@@ -227,26 +294,21 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "nominal behavior",
|
scenario: "nominal behavior",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
readMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
||||||
return make(map[string]interface{}), nil
|
return make(map[string]interface{}), nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "at least one engine does not return an error",
|
scenario: "at least one engine does not return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
readMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
||||||
return nil, errors.New("foo")
|
return nil, errors.New("foo")
|
||||||
@@ -258,16 +320,13 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "all engines return an error",
|
scenario: "all engines return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
readMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
||||||
return nil, errors.New("foo")
|
return nil, errors.New("foo")
|
||||||
@@ -279,25 +338,21 @@ func TestMultiPdfEngines_ReadMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
),
|
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "context expired",
|
scenario: "context expired",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
readMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
||||||
return make(map[string]interface{}), nil
|
return make(map[string]interface{}), nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nil,
|
},
|
||||||
),
|
|
||||||
ctx: func() context.Context {
|
ctx: func() context.Context {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
@@ -330,27 +385,21 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "nominal behavior",
|
scenario: "nominal behavior",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
writeMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "at least one engine does not return an error",
|
scenario: "at least one engine does not return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
writeMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -362,16 +411,13 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "all engines return an error",
|
scenario: "all engines return an error",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
writeMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
return errors.New("foo")
|
return errors.New("foo")
|
||||||
@@ -383,24 +429,21 @@ func TestMultiPdfEngines_WriteMetadata(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
expectError: true,
|
expectError: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
scenario: "context expired",
|
scenario: "context expired",
|
||||||
engine: newMultiPdfEngines(
|
engine: &multiPdfEngines{
|
||||||
nil,
|
writeMedataEngines: []gotenberg.PdfEngine{
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
[]gotenberg.PdfEngine{
|
|
||||||
&gotenberg.PdfEngineMock{
|
&gotenberg.PdfEngineMock{
|
||||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
},
|
||||||
ctx: func() context.Context {
|
ctx: func() context.Context {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
cancel()
|
cancel()
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func init() {
|
|||||||
// enabled.
|
// enabled.
|
||||||
type PdfEngines struct {
|
type PdfEngines struct {
|
||||||
mergeNames []string
|
mergeNames []string
|
||||||
|
splitNames []string
|
||||||
convertNames []string
|
convertNames []string
|
||||||
readMetadataNames []string
|
readMetadataNames []string
|
||||||
writeMedataNames []string
|
writeMedataNames []string
|
||||||
@@ -42,6 +43,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
FlagSet: func() *flag.FlagSet {
|
FlagSet: func() *flag.FlagSet {
|
||||||
fs := flag.NewFlagSet("pdfengines", flag.ExitOnError)
|
fs := flag.NewFlagSet("pdfengines", flag.ExitOnError)
|
||||||
fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature - empty means all")
|
fs.StringSlice("pdfengines-merge-engines", []string{"qpdf", "pdfcpu", "pdftk"}, "Set the PDF engines and their order for the merge feature - empty means all")
|
||||||
|
fs.StringSlice("pdfengines-split-engines", []string{"pdfcpu", "qpdf", "pdftk"}, "Set the PDF engines and their order for the split feature - empty means all")
|
||||||
fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature - empty means all")
|
fs.StringSlice("pdfengines-convert-engines", []string{"libreoffice-pdfengine"}, "Set the PDF engines and their order for the convert feature - empty means all")
|
||||||
fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read metadata feature - empty means all")
|
fs.StringSlice("pdfengines-read-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the read 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-write-metadata-engines", []string{"exiftool"}, "Set the PDF engines and their order for the write metadata feature - empty means all")
|
||||||
@@ -64,6 +66,7 @@ func (mod *PdfEngines) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
||||||
flags := ctx.ParsedFlags()
|
flags := ctx.ParsedFlags()
|
||||||
mergeNames := flags.MustStringSlice("pdfengines-merge-engines")
|
mergeNames := flags.MustStringSlice("pdfengines-merge-engines")
|
||||||
|
splitNames := flags.MustStringSlice("pdfengines-split-engines")
|
||||||
convertNames := flags.MustStringSlice("pdfengines-convert-engines")
|
convertNames := flags.MustStringSlice("pdfengines-convert-engines")
|
||||||
readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines")
|
readMetadataNames := flags.MustStringSlice("pdfengines-read-metadata-engines")
|
||||||
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
|
writeMetadataNames := flags.MustStringSlice("pdfengines-write-metadata-engines")
|
||||||
@@ -98,6 +101,11 @@ func (mod *PdfEngines) Provision(ctx *gotenberg.Context) error {
|
|||||||
mod.mergeNames = mergeNames
|
mod.mergeNames = mergeNames
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod.splitNames = defaultNames
|
||||||
|
if len(splitNames) > 0 {
|
||||||
|
mod.splitNames = splitNames
|
||||||
|
}
|
||||||
|
|
||||||
mod.convertNames = defaultNames
|
mod.convertNames = defaultNames
|
||||||
if len(convertNames) > 0 {
|
if len(convertNames) > 0 {
|
||||||
mod.convertNames = convertNames
|
mod.convertNames = convertNames
|
||||||
@@ -161,6 +169,7 @@ func (mod *PdfEngines) Validate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findNonExistingEngines(mod.mergeNames)
|
findNonExistingEngines(mod.mergeNames)
|
||||||
|
findNonExistingEngines(mod.splitNames)
|
||||||
findNonExistingEngines(mod.convertNames)
|
findNonExistingEngines(mod.convertNames)
|
||||||
findNonExistingEngines(mod.readMetadataNames)
|
findNonExistingEngines(mod.readMetadataNames)
|
||||||
findNonExistingEngines(mod.writeMedataNames)
|
findNonExistingEngines(mod.writeMedataNames)
|
||||||
@@ -177,6 +186,7 @@ func (mod *PdfEngines) Validate() error {
|
|||||||
func (mod *PdfEngines) SystemMessages() []string {
|
func (mod *PdfEngines) SystemMessages() []string {
|
||||||
return []string{
|
return []string{
|
||||||
fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
|
fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
|
||||||
|
fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")),
|
||||||
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
|
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
|
||||||
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
|
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
|
||||||
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
|
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
|
||||||
@@ -201,6 +211,7 @@ func (mod *PdfEngines) PdfEngine() (gotenberg.PdfEngine, error) {
|
|||||||
|
|
||||||
return newMultiPdfEngines(
|
return newMultiPdfEngines(
|
||||||
engines(mod.mergeNames),
|
engines(mod.mergeNames),
|
||||||
|
engines(mod.splitNames),
|
||||||
engines(mod.convertNames),
|
engines(mod.convertNames),
|
||||||
engines(mod.readMetadataNames),
|
engines(mod.readMetadataNames),
|
||||||
engines(mod.writeMedataNames),
|
engines(mod.writeMedataNames),
|
||||||
@@ -222,6 +233,7 @@ func (mod *PdfEngines) Routes() ([]api.Route, error) {
|
|||||||
|
|
||||||
return []api.Route{
|
return []api.Route{
|
||||||
mergeRoute(engine),
|
mergeRoute(engine),
|
||||||
|
splitRoute(engine),
|
||||||
convertRoute(engine),
|
convertRoute(engine),
|
||||||
readMetadataRoute(engine),
|
readMetadataRoute(engine),
|
||||||
writeMetadataRoute(engine),
|
writeMetadataRoute(engine),
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
scenario string
|
scenario string
|
||||||
ctx *gotenberg.Context
|
ctx *gotenberg.Context
|
||||||
expectedMergePdfEngines []string
|
expectedMergePdfEngines []string
|
||||||
|
expectedSplitPdfEngines []string
|
||||||
expectedConvertPdfEngines []string
|
expectedConvertPdfEngines []string
|
||||||
expectedReadMetadataPdfEngines []string
|
expectedReadMetadataPdfEngines []string
|
||||||
expectedWriteMetadataPdfEngines []string
|
expectedWriteMetadataPdfEngines []string
|
||||||
@@ -66,6 +67,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
)
|
)
|
||||||
}(),
|
}(),
|
||||||
expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"},
|
expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"},
|
||||||
|
expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"},
|
||||||
expectedConvertPdfEngines: []string{"libreoffice-pdfengine"},
|
expectedConvertPdfEngines: []string{"libreoffice-pdfengine"},
|
||||||
expectedReadMetadataPdfEngines: []string{"exiftool"},
|
expectedReadMetadataPdfEngines: []string{"exiftool"},
|
||||||
expectedWriteMetadataPdfEngines: []string{"exiftool"},
|
expectedWriteMetadataPdfEngines: []string{"exiftool"},
|
||||||
@@ -107,7 +109,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs := new(PdfEngines).Descriptor().FlagSet
|
fs := new(PdfEngines).Descriptor().FlagSet
|
||||||
err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
|
err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected no error but got: %v", err)
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
}
|
}
|
||||||
@@ -125,6 +127,7 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
}(),
|
}(),
|
||||||
|
|
||||||
expectedMergePdfEngines: []string{"b"},
|
expectedMergePdfEngines: []string{"b"},
|
||||||
|
expectedSplitPdfEngines: []string{"a"},
|
||||||
expectedConvertPdfEngines: []string{"b"},
|
expectedConvertPdfEngines: []string{"b"},
|
||||||
expectedReadMetadataPdfEngines: []string{"a"},
|
expectedReadMetadataPdfEngines: []string{"a"},
|
||||||
expectedWriteMetadataPdfEngines: []string{"a"},
|
expectedWriteMetadataPdfEngines: []string{"a"},
|
||||||
@@ -200,6 +203,12 @@ func TestPdfEngines_Provision(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for index, name := range mod.splitNames {
|
||||||
|
if name != tc.expectedSplitPdfEngines[index] {
|
||||||
|
t.Fatalf("expected split name at index %d to be %s, but got: %s", index, name, tc.expectedSplitPdfEngines[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for index, name := range mod.convertNames {
|
for index, name := range mod.convertNames {
|
||||||
if name != tc.expectedConvertPdfEngines[index] {
|
if name != tc.expectedConvertPdfEngines[index] {
|
||||||
t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index])
|
t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index])
|
||||||
@@ -303,17 +312,19 @@ func TestPdfEngines_Validate(t *testing.T) {
|
|||||||
func TestPdfEngines_SystemMessages(t *testing.T) {
|
func TestPdfEngines_SystemMessages(t *testing.T) {
|
||||||
mod := new(PdfEngines)
|
mod := new(PdfEngines)
|
||||||
mod.mergeNames = []string{"foo", "bar"}
|
mod.mergeNames = []string{"foo", "bar"}
|
||||||
|
mod.splitNames = []string{"foo", "bar"}
|
||||||
mod.convertNames = []string{"foo", "bar"}
|
mod.convertNames = []string{"foo", "bar"}
|
||||||
mod.readMetadataNames = []string{"foo", "bar"}
|
mod.readMetadataNames = []string{"foo", "bar"}
|
||||||
mod.writeMedataNames = []string{"foo", "bar"}
|
mod.writeMedataNames = []string{"foo", "bar"}
|
||||||
|
|
||||||
messages := mod.SystemMessages()
|
messages := mod.SystemMessages()
|
||||||
if len(messages) != 4 {
|
if len(messages) != 5 {
|
||||||
t.Errorf("expected one and only one message, but got %d", len(messages))
|
t.Errorf("expected one and only one message, but got %d", len(messages))
|
||||||
}
|
}
|
||||||
|
|
||||||
expect := []string{
|
expect := []string{
|
||||||
fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
|
fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
|
||||||
|
fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")),
|
||||||
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
|
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
|
||||||
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
|
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
|
||||||
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
|
fmt.Sprintf("write medata engines - %s", strings.Join(mod.writeMedataNames[:], " ")),
|
||||||
@@ -329,6 +340,7 @@ func TestPdfEngines_SystemMessages(t *testing.T) {
|
|||||||
func TestPdfEngines_PdfEngine(t *testing.T) {
|
func TestPdfEngines_PdfEngine(t *testing.T) {
|
||||||
mod := PdfEngines{
|
mod := PdfEngines{
|
||||||
mergeNames: []string{"foo", "bar"},
|
mergeNames: []string{"foo", "bar"},
|
||||||
|
splitNames: []string{"foo", "bar"},
|
||||||
convertNames: []string{"foo", "bar"},
|
convertNames: []string{"foo", "bar"},
|
||||||
readMetadataNames: []string{"foo", "bar"},
|
readMetadataNames: []string{"foo", "bar"},
|
||||||
writeMedataNames: []string{"foo", "bar"},
|
writeMedataNames: []string{"foo", "bar"},
|
||||||
@@ -370,7 +382,7 @@ func TestPdfEngines_Routes(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "routes not disabled",
|
scenario: "routes not disabled",
|
||||||
expectRoutes: 4,
|
expectRoutes: 5,
|
||||||
disableRoutes: false,
|
disableRoutes: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
|
||||||
@@ -13,6 +15,63 @@ import (
|
|||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FormDataPdfSplitMode creates a [gotenberg.SplitMode] from the form data.
|
||||||
|
func FormDataPdfSplitMode(form *api.FormData, mandatory bool) gotenberg.SplitMode {
|
||||||
|
var (
|
||||||
|
mode string
|
||||||
|
span string
|
||||||
|
)
|
||||||
|
|
||||||
|
splitModeFunc := func(value string) error {
|
||||||
|
if value != "" && value != gotenberg.SplitModeIntervals && value != gotenberg.SplitModePages {
|
||||||
|
return fmt.Errorf("wrong value, expected either '%s' or '%s'", gotenberg.SplitModeIntervals, gotenberg.SplitModePages)
|
||||||
|
}
|
||||||
|
mode = value
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
splitSpanFunc := func(value string) error {
|
||||||
|
value = strings.Join(strings.Fields(value), "")
|
||||||
|
|
||||||
|
if mode == gotenberg.SplitModeIntervals {
|
||||||
|
intValue, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if intValue < 1 {
|
||||||
|
return errors.New("value is inferior to 1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span = value
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if mandatory {
|
||||||
|
form.
|
||||||
|
MandatoryCustom("splitMode", func(value string) error {
|
||||||
|
return splitModeFunc(value)
|
||||||
|
}).
|
||||||
|
MandatoryCustom("splitSpan", func(value string) error {
|
||||||
|
return splitSpanFunc(value)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.
|
||||||
|
Custom("splitMode", func(value string) error {
|
||||||
|
return splitModeFunc(value)
|
||||||
|
}).
|
||||||
|
Custom("splitSpan", func(value string) error {
|
||||||
|
return splitSpanFunc(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return gotenberg.SplitMode{
|
||||||
|
Mode: mode,
|
||||||
|
Span: span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FormDataPdfFormats creates [gotenberg.PdfFormats] from the form data.
|
// FormDataPdfFormats creates [gotenberg.PdfFormats] from the form data.
|
||||||
// Fallback to default value if the considered key is not present.
|
// Fallback to default value if the considered key is not present.
|
||||||
func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats {
|
func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats {
|
||||||
@@ -32,9 +91,10 @@ func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FormDataPdfMetadata creates metadata object from the form data.
|
// FormDataPdfMetadata creates metadata object from the form data.
|
||||||
func FormDataPdfMetadata(form *api.FormData) map[string]interface{} {
|
func FormDataPdfMetadata(form *api.FormData, mandatory bool) map[string]interface{} {
|
||||||
var metadata map[string]interface{}
|
var metadata map[string]interface{}
|
||||||
form.Custom("metadata", func(value string) error {
|
|
||||||
|
metadataFunc := func(value string) error {
|
||||||
if len(value) > 0 {
|
if len(value) > 0 {
|
||||||
err := json.Unmarshal([]byte(value), &metadata)
|
err := json.Unmarshal([]byte(value), &metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -42,7 +102,18 @@ func FormDataPdfMetadata(form *api.FormData) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
|
|
||||||
|
if mandatory {
|
||||||
|
form.MandatoryCustom("metadata", func(value string) error {
|
||||||
|
return metadataFunc(value)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
form.Custom("metadata", func(value string) error {
|
||||||
|
return metadataFunc(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +137,52 @@ func MergeStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string
|
|||||||
return outputPath, nil
|
return outputPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitPdfStub splits a list of PDF files based on [gotenberg.SplitMode].
|
||||||
|
// It returns a list of output paths or the list of provided input paths if no
|
||||||
|
// split requested.
|
||||||
|
func SplitPdfStub(ctx *api.Context, engine gotenberg.PdfEngine, mode gotenberg.SplitMode, inputPaths []string) ([]string, error) {
|
||||||
|
zeroValued := gotenberg.SplitMode{}
|
||||||
|
if mode == zeroValued {
|
||||||
|
return inputPaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputPaths []string
|
||||||
|
for _, inputPath := range inputPaths {
|
||||||
|
inputPathNoExt := inputPath[:len(inputPath)-len(filepath.Ext(inputPath))]
|
||||||
|
filenameNoExt := filepath.Base(inputPathNoExt)
|
||||||
|
outputDirPath, err := ctx.CreateSubDirectory(strings.ReplaceAll(filepath.Base(filenameNoExt), ".", "_"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create subdirectory from input path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
paths, err := engine.Split(ctx, ctx.Log(), mode, inputPath, outputDirPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split PDF '%s': %w", inputPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode.Mode == gotenberg.SplitModePages {
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the original filename.
|
||||||
|
for i, path := range paths {
|
||||||
|
newPath := fmt.Sprintf(
|
||||||
|
"%s/%s_%d.pdf",
|
||||||
|
outputDirPath, filenameNoExt, i,
|
||||||
|
)
|
||||||
|
|
||||||
|
err = ctx.Rename(path, newPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("rename path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPaths = append(outputPaths, newPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputPaths, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ConvertStub transforms a given PDF to the specified formats defined in
|
// ConvertStub transforms a given PDF to the specified formats defined in
|
||||||
// [gotenberg.PdfFormats]. If no format, it does nothing and returns the input
|
// [gotenberg.PdfFormats]. If no format, it does nothing and returns the input
|
||||||
// paths.
|
// paths.
|
||||||
@@ -116,7 +233,7 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
|
|
||||||
form := ctx.FormData()
|
form := ctx.FormData()
|
||||||
pdfFormats := FormDataPdfFormats(form)
|
pdfFormats := FormDataPdfFormats(form)
|
||||||
metadata := FormDataPdfMetadata(form)
|
metadata := FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
var inputPaths []string
|
var inputPaths []string
|
||||||
err := form.
|
err := form.
|
||||||
@@ -152,6 +269,65 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// splitRoute returns an [api.Route] which can extract pages from a PDF.
|
||||||
|
func splitRoute(engine gotenberg.PdfEngine) api.Route {
|
||||||
|
return api.Route{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Path: "/forms/pdfengines/split",
|
||||||
|
IsMultipart: true,
|
||||||
|
Handler: func(c echo.Context) error {
|
||||||
|
ctx := c.Get("context").(*api.Context)
|
||||||
|
|
||||||
|
form := ctx.FormData()
|
||||||
|
mode := FormDataPdfSplitMode(form, true)
|
||||||
|
pdfFormats := FormDataPdfFormats(form)
|
||||||
|
metadata := FormDataPdfMetadata(form, false)
|
||||||
|
|
||||||
|
var inputPaths []string
|
||||||
|
err := form.
|
||||||
|
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||||
|
Validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outputPaths, err := SplitPdfStub(ctx, engine, mode, inputPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("split PDFs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
convertOutputPaths, err := ConvertStub(ctx, engine, pdfFormats, outputPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convert PDFs: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = WriteMetadataStub(ctx, engine, metadata, convertOutputPaths)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("write metadata: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zeroValuedSplitMode := gotenberg.SplitMode{}
|
||||||
|
zeroValuedPdfFormats := gotenberg.PdfFormats{}
|
||||||
|
if mode != zeroValuedSplitMode && pdfFormats != zeroValuedPdfFormats {
|
||||||
|
// Rename the files to keep the split naming.
|
||||||
|
for i, convertOutputPath := range convertOutputPaths {
|
||||||
|
err = ctx.Rename(convertOutputPath, outputPaths[i])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("rename output path: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.AddOutputPaths(outputPaths...)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("add output paths: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// convertRoute returns an [api.Route] which can convert PDFs to a specific ODF
|
// convertRoute returns an [api.Route] which can convert PDFs to a specific ODF
|
||||||
// format.
|
// format.
|
||||||
func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
func convertRoute(engine gotenberg.PdfEngine) api.Route {
|
||||||
@@ -258,25 +434,12 @@ func writeMetadataRoute(engine gotenberg.PdfEngine) api.Route {
|
|||||||
Handler: func(c echo.Context) error {
|
Handler: func(c echo.Context) error {
|
||||||
ctx := c.Get("context").(*api.Context)
|
ctx := c.Get("context").(*api.Context)
|
||||||
|
|
||||||
var (
|
form := ctx.FormData()
|
||||||
inputPaths []string
|
metadata := FormDataPdfMetadata(form, true)
|
||||||
metadata map[string]interface{}
|
|
||||||
)
|
|
||||||
|
|
||||||
err := ctx.FormData().
|
var inputPaths []string
|
||||||
|
err := form.
|
||||||
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
MandatoryPaths([]string{".pdf"}, &inputPaths).
|
||||||
MandatoryCustom("metadata", func(value string) error {
|
|
||||||
if len(value) > 0 {
|
|
||||||
err := json.Unmarshal([]byte(value), &metadata)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unmarshal metadata: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(metadata) == 0 {
|
|
||||||
return errors.New("no metadata")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}).
|
|
||||||
Validate()
|
Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("validate form data: %w", err)
|
return fmt.Errorf("validate form data: %w", err)
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ package pdfengines
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -17,6 +20,156 @@ import (
|
|||||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestFormDataPdfSplitMode(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx *api.ContextMock
|
||||||
|
mandatory bool
|
||||||
|
expectedSplitMode gotenberg.SplitMode
|
||||||
|
expectValidationError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "no custom form fields",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{},
|
||||||
|
expectValidationError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no custom form fields (mandatory)",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
mandatory: true,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{},
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid splitMode",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{},
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid splitSpan (intervals)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"intervals",
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1-2",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals},
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "splitSpan inferior to 1 (intervals)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"intervals",
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"-1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals},
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "valid form fields (intervals)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"intervals",
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
expectValidationError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "valid form fields (pages)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"pages",
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1-2",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: false,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
expectValidationError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "valid form fields (mandatory)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
"intervals",
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
mandatory: true,
|
||||||
|
expectedSplitMode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
expectValidationError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
|
form := tc.ctx.Context.FormData()
|
||||||
|
actual := FormDataPdfSplitMode(form, tc.mandatory)
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(actual, tc.expectedSplitMode) {
|
||||||
|
t.Fatalf("expected %+v but got: %+v", tc.expectedSplitMode, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := form.Validate()
|
||||||
|
|
||||||
|
if tc.expectValidationError && err == nil {
|
||||||
|
t.Fatal("expected validation error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectValidationError && err != nil {
|
||||||
|
t.Fatalf("expected no validation error but got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFormDataPdfFormats(t *testing.T) {
|
func TestFormDataPdfFormats(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
@@ -74,15 +227,24 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
ctx *api.ContextMock
|
ctx *api.ContextMock
|
||||||
|
mandatory bool
|
||||||
expectedMetadata map[string]interface{}
|
expectedMetadata map[string]interface{}
|
||||||
expectValidationError bool
|
expectValidationError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
scenario: "no metadata form field",
|
scenario: "no metadata form field",
|
||||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
mandatory: false,
|
||||||
expectedMetadata: nil,
|
expectedMetadata: nil,
|
||||||
expectValidationError: false,
|
expectValidationError: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
scenario: "no metadata form field (mandatory)",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
mandatory: true,
|
||||||
|
expectedMetadata: nil,
|
||||||
|
expectValidationError: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
scenario: "invalid metadata form field",
|
scenario: "invalid metadata form field",
|
||||||
ctx: func() *api.ContextMock {
|
ctx: func() *api.ContextMock {
|
||||||
@@ -94,6 +256,7 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
|
mandatory: false,
|
||||||
expectedMetadata: nil,
|
expectedMetadata: nil,
|
||||||
expectValidationError: true,
|
expectValidationError: true,
|
||||||
},
|
},
|
||||||
@@ -108,6 +271,7 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
})
|
})
|
||||||
return ctx
|
return ctx
|
||||||
}(),
|
}(),
|
||||||
|
mandatory: false,
|
||||||
expectedMetadata: map[string]interface{}{
|
expectedMetadata: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
@@ -117,7 +281,7 @@ func TestFormDataPdfMetadata(t *testing.T) {
|
|||||||
t.Run(tc.scenario, func(t *testing.T) {
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
tc.ctx.SetLogger(zap.NewNop())
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
form := tc.ctx.Context.FormData()
|
form := tc.ctx.Context.FormData()
|
||||||
actual := FormDataPdfMetadata(form)
|
actual := FormDataPdfMetadata(form, tc.mandatory)
|
||||||
|
|
||||||
if !reflect.DeepEqual(actual, tc.expectedMetadata) {
|
if !reflect.DeepEqual(actual, tc.expectedMetadata) {
|
||||||
t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual)
|
t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual)
|
||||||
@@ -193,6 +357,128 @@ func TestMergeStub(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitPdfStub(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx *api.ContextMock
|
||||||
|
engine gotenberg.PdfEngine
|
||||||
|
mode gotenberg.SplitMode
|
||||||
|
expectError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "no split mode",
|
||||||
|
mode: gotenberg.SplitMode{},
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "cannot create subdirectory",
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return errors.New("cannot create subdirectory")
|
||||||
|
}})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "split error",
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "rename error",
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return errors.New("cannot rename")
|
||||||
|
}})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (intervals)",
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModeIntervals, Span: "1"},
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (pages)",
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
dirPath := fmt.Sprintf("%s/%s", os.TempDir(), uuid.NewString())
|
||||||
|
tc.ctx.SetDirPath(dirPath)
|
||||||
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
|
|
||||||
|
_, err := SplitPdfStub(tc.ctx.Context, tc.engine, tc.mode, []string{"my.pdf", "my2.pdf"})
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertStub(t *testing.T) {
|
func TestConvertStub(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
@@ -503,6 +789,287 @@ func TestMergeHandler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSplitHandler(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx *api.ContextMock
|
||||||
|
engine gotenberg.PdfEngine
|
||||||
|
expectError bool
|
||||||
|
expectHttpError bool
|
||||||
|
expectHttpStatus int
|
||||||
|
expectOutputPathsCount int
|
||||||
|
expectOutputPaths []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "missing at least one mandatory file",
|
||||||
|
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "no split mode",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: true,
|
||||||
|
expectHttpStatus: http.StatusBadRequest,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "error from PDF engine (split)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return nil, errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "error from PDF engine (convert)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
"pdfua": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "error from PDF engine (write metadata)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
|
return errors.New("foo")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "cannot add output paths",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.SetCancelled(true)
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{inputPath}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: true,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (intervals)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModeIntervals,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1",
|
||||||
|
},
|
||||||
|
"pdfua": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{"file_split_1.pdf", "file_split_2.pdf"}, nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 2,
|
||||||
|
expectOutputPaths: []string{"/file/file_0.pdf", "/file/file_1.pdf"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (pages)",
|
||||||
|
ctx: func() *api.ContextMock {
|
||||||
|
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||||
|
ctx.SetFiles(map[string]string{
|
||||||
|
"file.pdf": "/file.pdf",
|
||||||
|
})
|
||||||
|
ctx.SetValues(map[string][]string{
|
||||||
|
"splitMode": {
|
||||||
|
gotenberg.SplitModePages,
|
||||||
|
},
|
||||||
|
"splitSpan": {
|
||||||
|
"1-2",
|
||||||
|
},
|
||||||
|
"pdfua": {
|
||||||
|
"true",
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return ctx
|
||||||
|
}(),
|
||||||
|
engine: &gotenberg.PdfEngineMock{
|
||||||
|
SplitMock: func(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
return []string{"/file/file.pdf"}, nil
|
||||||
|
},
|
||||||
|
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectError: false,
|
||||||
|
expectHttpError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
expectOutputPaths: []string{"/file/file.pdf"},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
tc.ctx.SetLogger(zap.NewNop())
|
||||||
|
tc.ctx.SetMkdirAll(&gotenberg.MkdirAllMock{MkdirAllMock: func(path string, perm os.FileMode) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
tc.ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||||
|
return nil
|
||||||
|
}})
|
||||||
|
c := echo.New().NewContext(nil, nil)
|
||||||
|
c.Set("context", tc.ctx.Context)
|
||||||
|
|
||||||
|
err := splitRoute(tc.engine).Handler(c)
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var httpErr api.HttpError
|
||||||
|
isHttpError := errors.As(err, &httpErr)
|
||||||
|
|
||||||
|
if tc.expectHttpError && !isHttpError {
|
||||||
|
t.Errorf("expected an HTTP error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tc.expectHttpError && isHttpError {
|
||||||
|
t.Errorf("expected no HTTP error but got one: %v", httpErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && tc.expectHttpError && isHttpError {
|
||||||
|
status, _ := httpErr.HttpError()
|
||||||
|
if status != tc.expectHttpStatus {
|
||||||
|
t.Errorf("expected %d as HTTP status code but got %d", tc.expectHttpStatus, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectOutputPathsCount != len(tc.ctx.OutputPaths()) {
|
||||||
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(tc.ctx.OutputPaths()))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range tc.expectOutputPaths {
|
||||||
|
if !slices.Contains(tc.ctx.OutputPaths(), path) {
|
||||||
|
t.Errorf("expected '%s' in output paths %v", path, tc.ctx.OutputPaths())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConvertHandler(t *testing.T) {
|
func TestConvertHandler(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// interface using the PDFtk command-line tool. This package allows for:
|
// interface using the PDFtk command-line tool. This package allows for:
|
||||||
//
|
//
|
||||||
// 1. The merging of PDF files.
|
// 1. The merging of PDF files.
|
||||||
|
// 2. The splitting of PDF files.
|
||||||
//
|
//
|
||||||
// The path to the PDFtk binary must be specified using the PDFTK_BIN_PATH
|
// The path to the PDFtk binary must be specified using the PDFTK_BIN_PATH
|
||||||
// environment variable.
|
// environment variable.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -51,6 +52,31 @@ func (engine *PdfTk) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split splits a given PDF file.
|
||||||
|
func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
var args []string
|
||||||
|
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
||||||
|
|
||||||
|
switch mode.Mode {
|
||||||
|
case gotenberg.SplitModePages:
|
||||||
|
args = append(args, inputPath, "cat", mode.Span, "output", outputPath)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("split PDFs using mode '%s' with PDFtk: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cmd.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split PDFs with PDFtk: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{outputPath}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *PdfTk) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
var args []string
|
var args []string
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func TestPdfTk_Merge(t *testing.T) {
|
|||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
outputDir, err := fs.MkdirAll()
|
outputDir, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
@@ -142,6 +142,88 @@ func TestPdfTk_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfCpu_Split(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx context.Context
|
||||||
|
mode gotenberg.SplitMode
|
||||||
|
inputPath string
|
||||||
|
expectError bool
|
||||||
|
expectedError error
|
||||||
|
expectOutputPathsCount int
|
||||||
|
expectOutputPaths []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "ErrPdfSplitModeNotSupported",
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrPdfSplitModeNotSupported,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid context",
|
||||||
|
ctx: nil,
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid input path",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
inputPath: "",
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (pages)",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||||
|
expectError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
engine := new(PdfTk)
|
||||||
|
err := engine.Provision(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
outputDir, err := fs.MkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = os.RemoveAll(fs.WorkingDirPath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error while cleaning up but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir)
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
|
||||||
|
t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectOutputPathsCount != len(outputPaths) {
|
||||||
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPdfTk_Convert(t *testing.T) {
|
func TestPdfTk_Convert(t *testing.T) {
|
||||||
engine := new(PdfTk)
|
engine := new(PdfTk)
|
||||||
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// interface using the QPDF command-line tool. This package allows for:
|
// interface using the QPDF command-line tool. This package allows for:
|
||||||
//
|
//
|
||||||
// 1. The merging of PDF files.
|
// 1. The merging of PDF files.
|
||||||
|
// 2. The splitting of PDF files.
|
||||||
//
|
//
|
||||||
// The path to the QPDF binary must be specified using the QPDK_BIN_PATH
|
// The path to the QPDF binary must be specified using the QPDK_BIN_PATH
|
||||||
// environment variable.
|
// environment variable.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -45,12 +46,37 @@ func (engine *QPdf) Provision(ctx *gotenberg.Context) error {
|
|||||||
func (engine *QPdf) Validate() error {
|
func (engine *QPdf) Validate() error {
|
||||||
_, err := os.Stat(engine.binPath)
|
_, err := os.Stat(engine.binPath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return fmt.Errorf("QPdf binary path does not exist: %w", err)
|
return fmt.Errorf("QPDF binary path does not exist: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Split splits a given PDF file.
|
||||||
|
func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
|
var args []string
|
||||||
|
outputPath := fmt.Sprintf("%s/%s", outputDirPath, filepath.Base(inputPath))
|
||||||
|
|
||||||
|
switch mode.Mode {
|
||||||
|
case gotenberg.SplitModePages:
|
||||||
|
args = append(args, inputPath, "--pages", ".", mode.Span, "--", outputPath)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("split PDFs using mode '%s' with QPDF: %w", mode.Mode, gotenberg.ErrPdfSplitModeNotSupported)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd, err := gotenberg.CommandContext(ctx, logger, engine.binPath, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("create command: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = cmd.Exec()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("split PDFs with QPDF: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{outputPath}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *QPdf) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
var args []string
|
var args []string
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ func TestQPdf_Merge(t *testing.T) {
|
|||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := gotenberg.NewFileSystem()
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
outputDir, err := fs.MkdirAll()
|
outputDir, err := fs.MkdirAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expected error but got: %v", err)
|
t.Fatalf("expected error but got: %v", err)
|
||||||
@@ -142,6 +142,88 @@ func TestQPdf_Merge(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQPdf_Split(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
ctx context.Context
|
||||||
|
mode gotenberg.SplitMode
|
||||||
|
inputPath string
|
||||||
|
expectError bool
|
||||||
|
expectedError error
|
||||||
|
expectOutputPathsCount int
|
||||||
|
expectOutputPaths []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "ErrPdfSplitModeNotSupported",
|
||||||
|
expectError: true,
|
||||||
|
expectedError: gotenberg.ErrPdfSplitModeNotSupported,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid context",
|
||||||
|
ctx: nil,
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "invalid input path",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
inputPath: "",
|
||||||
|
expectError: true,
|
||||||
|
expectOutputPathsCount: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success (pages)",
|
||||||
|
ctx: context.TODO(),
|
||||||
|
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2"},
|
||||||
|
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||||
|
expectError: false,
|
||||||
|
expectOutputPathsCount: 1,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
engine := new(QPdf)
|
||||||
|
err := engine.Provision(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||||
|
outputDir, err := fs.MkdirAll()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
err = os.RemoveAll(fs.WorkingDirPath())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error while cleaning up but got: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
outputPaths, err := engine.Split(tc.ctx, zap.NewNop(), tc.mode, tc.inputPath, outputDir)
|
||||||
|
|
||||||
|
if !tc.expectError && err != nil {
|
||||||
|
t.Fatalf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectError && err == nil {
|
||||||
|
t.Fatal("expected error but got none")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedError != nil && !errors.Is(err, tc.expectedError) {
|
||||||
|
t.Fatalf("expected error %v but got: %v", tc.expectedError, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectOutputPathsCount != len(outputPaths) {
|
||||||
|
t.Errorf("expected %d output paths but got %d", tc.expectOutputPathsCount, len(outputPaths))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQPdf_Convert(t *testing.T) {
|
func TestQPdf_Convert(t *testing.T) {
|
||||||
engine := new(QPdf)
|
engine := new(QPdf)
|
||||||
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||||
|
|||||||
Reference in New Issue
Block a user