mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 08:32:16 +01:00
test: add integration tests
This commit is contained in:
@@ -1,306 +0,0 @@
|
||||
package gotenberg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestCommand(t *testing.T) {
|
||||
cmd := Command(zap.NewNop(), "foo")
|
||||
if !cmd.process.SysProcAttr.Setpgid {
|
||||
t.Error("expected cmd.process.SysProcAttr.Setpgid to be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandContext(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
ctx context.Context
|
||||
expectCommandContextError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
ctx: context.Background(),
|
||||
expectCommandContextError: false,
|
||||
},
|
||||
{
|
||||
scenario: "nil context",
|
||||
ctx: nil,
|
||||
expectCommandContextError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
cmd, err := CommandContext(tc.ctx, zap.NewNop(), "foo")
|
||||
|
||||
if err == nil && !cmd.process.SysProcAttr.Setpgid {
|
||||
t.Fatal("expected cmd.process.SysProcAttr.Setpgid to be true")
|
||||
}
|
||||
|
||||
if !tc.expectCommandContextError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectCommandContextError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_Start(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
cmd *Cmd
|
||||
expectStartError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
|
||||
expectStartError: false,
|
||||
},
|
||||
{
|
||||
scenario: "start error",
|
||||
cmd: Command(zap.NewNop(), "foo"),
|
||||
expectStartError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.cmd.Start()
|
||||
|
||||
if !tc.expectStartError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectStartError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_Wait(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
cmd *Cmd
|
||||
expectWaitError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
cmd: func() *Cmd {
|
||||
cmd := Command(zap.NewNop(), "echo", "Hello", "World")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
expectWaitError: false,
|
||||
},
|
||||
{
|
||||
scenario: "wait error",
|
||||
cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
|
||||
expectWaitError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.cmd.Wait()
|
||||
|
||||
if !tc.expectWaitError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectWaitError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_Exec(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
cmd *Cmd
|
||||
timeout time.Duration
|
||||
expectExecError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
cmd: func() *Cmd {
|
||||
cmd, err := CommandContext(context.Background(), zap.NewNop(), "echo", "Hello", "World")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error from CommandContext(), but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
expectExecError: false,
|
||||
},
|
||||
{
|
||||
scenario: "nil context",
|
||||
cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
|
||||
expectExecError: true,
|
||||
},
|
||||
{
|
||||
scenario: "start error",
|
||||
cmd: func() *Cmd {
|
||||
cmd, err := CommandContext(context.Background(), zap.NewNop(), "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
expectExecError: true,
|
||||
},
|
||||
{
|
||||
scenario: "context done",
|
||||
cmd: Command(zap.NewNop(), "sleep", "2"),
|
||||
timeout: time.Duration(1) * time.Second,
|
||||
expectExecError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
if tc.timeout > 0 {
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), tc.timeout)
|
||||
defer cancel()
|
||||
|
||||
tc.cmd.ctx = ctx
|
||||
}
|
||||
|
||||
_, err := tc.cmd.Exec()
|
||||
|
||||
if !tc.expectExecError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectExecError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_pipeOutput(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
cmd *Cmd
|
||||
run bool
|
||||
expectPipeOutputError bool
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
cmd: Command(zap.NewExample(), "echo", "Hello", "World"),
|
||||
run: true,
|
||||
expectPipeOutputError: false,
|
||||
},
|
||||
{
|
||||
scenario: "no debug, no pipe",
|
||||
cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
|
||||
run: false,
|
||||
expectPipeOutputError: false,
|
||||
},
|
||||
{
|
||||
scenario: "stdout already piped",
|
||||
cmd: func() *Cmd {
|
||||
cmd := Command(zap.NewExample(), "echo", "Hello", "World")
|
||||
_, err := cmd.process.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
run: false,
|
||||
expectPipeOutputError: true,
|
||||
},
|
||||
{
|
||||
scenario: "stderr already piped",
|
||||
cmd: func() *Cmd {
|
||||
cmd := Command(zap.NewExample(), "echo", "Hello", "World")
|
||||
_, err := cmd.process.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
run: false,
|
||||
expectPipeOutputError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.cmd.pipeOutput()
|
||||
|
||||
if tc.run {
|
||||
errStart := tc.cmd.process.Start()
|
||||
if errStart != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !tc.expectPipeOutputError && err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectPipeOutputError && err == nil {
|
||||
t.Fatal("expected error but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmd_Kill(t *testing.T) {
|
||||
tests := []struct {
|
||||
scenario string
|
||||
cmd *Cmd
|
||||
}{
|
||||
{
|
||||
scenario: "nominal behavior",
|
||||
cmd: func() *Cmd {
|
||||
cmd := Command(zap.NewNop(), "sleep", "60")
|
||||
err := cmd.process.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
},
|
||||
{
|
||||
scenario: "no process",
|
||||
cmd: &Cmd{logger: zap.NewNop()},
|
||||
},
|
||||
{
|
||||
scenario: "process already killed",
|
||||
cmd: func() *Cmd {
|
||||
cmd := Command(zap.NewNop(), "sleep", "60")
|
||||
err := cmd.process.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
err = cmd.Kill()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return cmd
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.cmd.Kill()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,6 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewContext(t *testing.T) {
|
||||
if NewContext(ParsedFlags{}, nil) == nil {
|
||||
t.Error("expected a non-nil value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_ParsedFlags(t *testing.T) {
|
||||
ctx := NewContext(ParsedFlags{}, nil)
|
||||
|
||||
actual := ctx.ParsedFlags()
|
||||
expect := ParsedFlags{}
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected %v but got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_Module(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
package gotenberg
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"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) {
|
||||
fs := NewFileSystem(new(MkdirAllMock))
|
||||
dirName := fs.WorkingDir()
|
||||
|
||||
if dirName == "" {
|
||||
t.Error("expected directory name but got empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_WorkingDirPath(t *testing.T) {
|
||||
fs := NewFileSystem(new(MkdirAllMock))
|
||||
expectedPath := fmt.Sprintf("%s/%s", os.TempDir(), fs.WorkingDir())
|
||||
|
||||
if fs.WorkingDirPath() != expectedPath {
|
||||
t.Errorf("expected path '%s' but got '%s'", expectedPath, fs.WorkingDirPath())
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_NewDirPath(t *testing.T) {
|
||||
fs := NewFileSystem(new(MkdirAllMock))
|
||||
newDir := fs.NewDirPath()
|
||||
expectedPrefix := fs.WorkingDirPath()
|
||||
|
||||
if !strings.HasPrefix(newDir, expectedPrefix) {
|
||||
t.Errorf("expected new directory to start with '%s' but got '%s'", expectedPrefix, newDir)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystem_MkdirAll(t *testing.T) {
|
||||
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)
|
||||
|
||||
_, err := fs.MkdirAll()
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package gotenberg
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -51,7 +52,7 @@ func TestGarbageCollect(t *testing.T) {
|
||||
|
||||
return path
|
||||
}(),
|
||||
includeSubstr: []string{"foo", fmt.Sprintf("%s/a_directory/a_bar_file", os.TempDir())},
|
||||
includeSubstr: []string{"foo", path.Join(os.TempDir(), "/a_directory/a_bar_file")},
|
||||
expectError: false,
|
||||
expectExists: []string{"a_baz_file"},
|
||||
expectNotExists: []string{"a_foo_file", "a_bar_file"},
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package gotenberg
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestLeveledLogger_Error(t *testing.T) {
|
||||
NewLeveledLogger(zap.NewNop()).Error("foo")
|
||||
}
|
||||
|
||||
func TestLeveledLogger_Warn(t *testing.T) {
|
||||
NewLeveledLogger(zap.NewNop()).Warn("foo")
|
||||
}
|
||||
|
||||
func TestLeveledLogger_Info(t *testing.T) {
|
||||
NewLeveledLogger(zap.NewNop()).Info("foo")
|
||||
}
|
||||
|
||||
func TestLeveledLogger_Debug(t *testing.T) {
|
||||
NewLeveledLogger(zap.NewNop()).Debug("foo")
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
package gotenberg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestModuleMock(t *testing.T) {
|
||||
mock := &ModuleMock{
|
||||
DescriptorMock: func() ModuleDescriptor {
|
||||
return ModuleDescriptor{ID: "foo", New: func() Module {
|
||||
return nil
|
||||
}}
|
||||
},
|
||||
}
|
||||
|
||||
if mock.Descriptor().ID != "foo" {
|
||||
t.Errorf("expected ID '%s' from ModuleMock.Descriptor, but got '%s'", "foo", mock.Descriptor().ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerMock(t *testing.T) {
|
||||
mock := &ProvisionerMock{
|
||||
ProvisionMock: func(*Context) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Provision(&Context{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProvisionerMock.Provision, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatorMock(t *testing.T) {
|
||||
mock := &ValidatorMock{
|
||||
ValidateMock: func() error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Validate()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ValidatorMock.Validate, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebuggableMock(t *testing.T) {
|
||||
mock := &DebuggableMock{
|
||||
DebugMock: func() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"foo": "bar",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
d := mock.Debug()
|
||||
if d == nil {
|
||||
t.Errorf("expected debug data, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPDFEngineMock(t *testing.T) {
|
||||
mock := &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 SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||
return nil, nil
|
||||
},
|
||||
FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats PdfFormats, inputPath, outputPath string) error {
|
||||
return nil
|
||||
},
|
||||
ReadMetadataMock: func(ctx context.Context, logger *zap.Logger, inputPath string) (map[string]interface{}, error) {
|
||||
return nil, nil
|
||||
},
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Merge(context.Background(), zap.NewNop(), nil, "")
|
||||
if err != nil {
|
||||
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.Flatten(context.Background(), zap.NewNop(), "")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.Convert(context.Background(), zap.NewNop(), PdfFormats{}, "", "")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PdfEngineMock.Convert, but got: %v", err)
|
||||
}
|
||||
|
||||
_, err = mock.ReadMetadata(context.Background(), zap.NewNop(), "")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PdfEngineMock.ReadMetadata, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.WriteMetadata(context.Background(), zap.NewNop(), map[string]interface{}{}, "")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PdfEngineMock.WriteMetadata but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPDFEngineProviderMock(t *testing.T) {
|
||||
mock := &PdfEngineProviderMock{
|
||||
PdfEngineMock: func() (PdfEngine, error) {
|
||||
return new(PdfEngineMock), nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.PdfEngine()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PdfEngineProviderMock.PdfEngine, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessMock(t *testing.T) {
|
||||
mock := &ProcessMock{
|
||||
StartMock: func(logger *zap.Logger) error {
|
||||
return nil
|
||||
},
|
||||
StopMock: func(logger *zap.Logger) error {
|
||||
return nil
|
||||
},
|
||||
HealthyMock: func(logger *zap.Logger) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Start(zap.NewNop())
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProcessMock.Start, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.Stop(zap.NewNop())
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProcessMock.Stop, but got: %v", err)
|
||||
}
|
||||
|
||||
healthy := mock.Healthy(zap.NewNop())
|
||||
if !healthy {
|
||||
t.Error("expected true from ProcessMock.Healthy, but got false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessSupervisorMock(t *testing.T) {
|
||||
mock := &ProcessSupervisorMock{
|
||||
LaunchMock: func() error {
|
||||
return nil
|
||||
},
|
||||
ShutdownMock: func() error {
|
||||
return nil
|
||||
},
|
||||
HealthyMock: func() bool {
|
||||
return true
|
||||
},
|
||||
RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error {
|
||||
return nil
|
||||
},
|
||||
ReqQueueSizeMock: func() int64 {
|
||||
return 0
|
||||
},
|
||||
RestartsCountMock: func() int64 {
|
||||
return 0
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Launch()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProcessSupervisorMock.Launch, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.Shutdown()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProcessSupervisorMock.Shutdown, but got: %v", err)
|
||||
}
|
||||
|
||||
healthy := mock.Healthy()
|
||||
if !healthy {
|
||||
t.Error("expected true from ProcessSupervisorMock.Healthy, but got false")
|
||||
}
|
||||
|
||||
err = mock.Run(context.TODO(), zap.NewNop(), nil)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProcessSupervisorMock.Run, but got: %v", err)
|
||||
}
|
||||
|
||||
size := mock.ReqQueueSize()
|
||||
if size != 0 {
|
||||
t.Errorf("expected 0 from ProcessSupervisorMock.ReqQueueSize, but got: %d", size)
|
||||
}
|
||||
|
||||
restarts := mock.RestartsCount()
|
||||
if restarts != 0 {
|
||||
t.Errorf("expected 0 from ProcessSupervisorMock.RestartsCount, but got: %d", restarts)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoggerProviderMock(t *testing.T) {
|
||||
mock := &LoggerProviderMock{
|
||||
LoggerMock: func(mod Module) (*zap.Logger, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.Logger(new(ModuleMock))
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from LoggerProviderMock.Logger, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetricsProviderMock(t *testing.T) {
|
||||
mock := &MetricsProviderMock{
|
||||
MetricsMock: func() ([]Metric, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.Metrics()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from MetricsProviderMock.Metrics, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
mock := &PathRenameMock{
|
||||
RenameMock: func(oldpath, newpath string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Rename("", "")
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from PathRenameMock.Rename, but got: %v", err)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,837 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestNewContext(t *testing.T) {
|
||||
defaultAllowList, err := regexp2.Compile("", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
defaultDenyList, err := regexp2.Compile("", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
defaultDownloadFromCfg := downloadFromConfig{
|
||||
allowList: defaultAllowList,
|
||||
denyList: defaultDenyList,
|
||||
maxRetry: 1,
|
||||
disable: false,
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
request *http.Request
|
||||
bodyLimit int64
|
||||
downloadFromCfg downloadFromConfig
|
||||
downloadFromSrv *echo.Echo
|
||||
expectContext *Context
|
||||
expectError bool
|
||||
expectHttpError bool
|
||||
expectHttpStatus int
|
||||
}{
|
||||
{
|
||||
scenario: "http.ErrNotMultipart",
|
||||
request: httptest.NewRequest(http.MethodPost, "/", nil),
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
scenario: "http.ErrMissingBoundary",
|
||||
request: func() *http.Request {
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEMultipartForm)
|
||||
return req
|
||||
}(),
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusUnsupportedMediaType,
|
||||
},
|
||||
{
|
||||
scenario: "malformed body",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "request entity too large: form values",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("key", "value")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
bodyLimit: 1,
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||
},
|
||||
{
|
||||
scenario: "request entity too large: downloadFrom",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
bodyLimit: 45, // form values = 44 bytes.
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderContentDisposition, `attachment; filename="bar.txt"`)
|
||||
c.Response().Header().Set(echo.HeaderContentType, "text/plain")
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||
},
|
||||
{
|
||||
scenario: "request entity too large: form files",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
part, err := writer.CreateFormFile("foo.txt", "foo.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
_, err = part.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
bodyLimit: 1,
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusRequestEntityTooLarge,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: cannot unmarshal",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: no URL",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: filtered URL",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"https://foo.bar"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromCfg: func() downloadFromConfig {
|
||||
denyList, err := regexp2.Compile("https://foo.bar", 0)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return downloadFromConfig{allowList: defaultAllowList, denyList: denyList, maxRetry: 1, disable: false}
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: unreachable URL",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: invalid status code",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusNotFound, http.StatusText(http.StatusNotFound))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: no 'Content-Disposition' header",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: malformed 'Content-Disposition' header",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderContentDisposition, ";;")
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid downloadFrom form field: no filename parameter in 'Content-Disposition' header",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/"}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
c.Response().Header().Set(echo.HeaderContentDisposition, "inline;")
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
request: func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
err := writer.WriteField("foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
part, err := writer.CreateFormFile("foo.txt", "foo.txt")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
_, err = part.Write([]byte("foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
err = writer.WriteField("downloadFrom", `[{"url":"http://localhost:80/","extraHttpHeaders":{"X-Foo":"Bar"}}]`)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
return req
|
||||
}(),
|
||||
downloadFromSrv: func() *echo.Echo {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.GET("/", func(c echo.Context) error {
|
||||
if c.Request().Header.Get("User-Agent") != "Gotenberg" {
|
||||
t.Fatalf("expected 'Gotenberg' from header 'User-Agent', but got '%s'", c.Request().Header.Get("User-Agent"))
|
||||
}
|
||||
if c.Request().Header.Get("X-Foo") != "Bar" {
|
||||
t.Fatalf("expected 'Bar' from header 'X-Foo', but got '%s'", c.Request().Header.Get("X-Foo"))
|
||||
}
|
||||
if c.Request().Header.Get("Gotenberg-Trace") != "123" {
|
||||
t.Fatalf("expected '123' from header 'Gotenberg-Trace', but got '%s'", c.Request().Header.Get("Gotenberg-Trace"))
|
||||
}
|
||||
c.Response().Header().Set(echo.HeaderContentDisposition, `attachment; filename="bar.txt"`)
|
||||
c.Response().Header().Set(echo.HeaderContentType, "text/plain")
|
||||
return c.String(http.StatusOK, http.StatusText(http.StatusOK))
|
||||
})
|
||||
return srv
|
||||
}(),
|
||||
downloadFromCfg: defaultDownloadFromCfg,
|
||||
expectContext: &Context{
|
||||
values: map[string][]string{
|
||||
"foo": {"foo"},
|
||||
"downloadFrom": {
|
||||
`[{"url":"http://localhost:80/","extraHttpHeaders":{"X-Foo":"Bar"}}]`,
|
||||
},
|
||||
},
|
||||
files: map[string]string{
|
||||
"foo.txt": "foo.txt",
|
||||
"bar.txt": "bar.txt", // downloadFrom.
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
expectHttpError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
if tc.downloadFromSrv != nil {
|
||||
go func() {
|
||||
err := tc.downloadFromSrv.Start(":80")
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
defer func() {
|
||||
err := tc.downloadFromSrv.Shutdown(context.TODO())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
handler := func(c echo.Context) error {
|
||||
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()
|
||||
// Context already cancelled.
|
||||
defer cancel()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tc.expectContext != nil {
|
||||
if !reflect.DeepEqual(tc.expectContext.values, ctx.values) {
|
||||
t.Fatalf("expected context.values to be %v but got %v", tc.expectContext.values, ctx.values)
|
||||
}
|
||||
if len(tc.expectContext.files) != len(ctx.files) {
|
||||
t.Fatalf("expected context.files to contain %d items but got %d", len(tc.expectContext.files), len(ctx.files))
|
||||
}
|
||||
for key, value := range tc.expectContext.files {
|
||||
if !strings.HasSuffix(ctx.files[key], value) {
|
||||
t.Fatalf("expected context.files to contain '%s' but got '%s'", value, ctx.files[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(tc.request, recorder)
|
||||
err := 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 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_Request(t *testing.T) {
|
||||
request := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
c := echo.New().NewContext(request, recorder)
|
||||
|
||||
ctx := &Context{
|
||||
echoCtx: c,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(ctx.Request(), c.Request()) {
|
||||
t.Errorf("expected %v but got %v", ctx.Request(), c.Request())
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_FormData(t *testing.T) {
|
||||
ctx := &Context{
|
||||
values: map[string][]string{
|
||||
"foo": {"foo"},
|
||||
},
|
||||
files: map[string]string{
|
||||
"foo.txt": "/foo.txt",
|
||||
},
|
||||
}
|
||||
|
||||
actual := ctx.FormData()
|
||||
expect := &FormData{
|
||||
values: ctx.values,
|
||||
files: ctx.files,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf("expected %+v but got %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
ctx := &Context{
|
||||
dirPath: "/foo",
|
||||
}
|
||||
|
||||
path := ctx.GeneratePath(".pdf")
|
||||
if !strings.HasPrefix(path, ctx.dirPath) {
|
||||
t.Errorf("expected '%s' to start with '%s'", path, ctx.dirPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_Rename(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "failure",
|
||||
ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||
return errors.New("cannot rename")
|
||||
}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
ctx: &Context{pathRename: &gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||
return nil
|
||||
}}},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
tc.ctx.logger = zap.NewNop()
|
||||
err := tc.ctx.Rename("", "")
|
||||
|
||||
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_AddOutputPaths(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *Context
|
||||
path string
|
||||
expectCount int
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "ErrContextAlreadyClosed",
|
||||
ctx: &Context{cancelled: true},
|
||||
expectCount: 0,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrOutOfBoundsOutputPath",
|
||||
ctx: &Context{dirPath: "/foo"},
|
||||
path: "/bar/foo.txt",
|
||||
expectCount: 0,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
ctx: &Context{dirPath: "/foo"},
|
||||
path: "/foo/foo.txt",
|
||||
expectCount: 1,
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.ctx.AddOutputPaths(tc.path)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if len(tc.ctx.outputPaths) != tc.expectCount {
|
||||
t.Errorf("expected %d output paths but got %d", tc.expectCount, len(tc.ctx.outputPaths))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_Log(t *testing.T) {
|
||||
expect := zap.NewNop()
|
||||
ctx := Context{logger: expect}
|
||||
actual := ctx.Log()
|
||||
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf("expected %v but got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext_BuildOutputFile(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "ErrContextAlreadyClosed",
|
||||
ctx: &Context{cancelled: true},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no output path",
|
||||
ctx: &Context{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success: one output path",
|
||||
ctx: &Context{outputPaths: []string{"foo.txt"}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "cannot archive: invalid output paths",
|
||||
ctx: &Context{outputPaths: []string{"foo.txt", "foo.pdf"}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success: many output paths",
|
||||
ctx: &Context{
|
||||
outputPaths: []string{
|
||||
"/tests/test/testdata/api/sample1.txt",
|
||||
"/tests/test/testdata/api/sample1.txt",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
dirPath, err := fs.MkdirAll()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no erro 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)
|
||||
}
|
||||
}()
|
||||
|
||||
tc.ctx.dirPath = dirPath
|
||||
tc.ctx.Context = context.Background()
|
||||
tc.ctx.logger = zap.NewNop()
|
||||
|
||||
_, err = tc.ctx.BuildOutputFile()
|
||||
|
||||
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_OutputFilename(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *Context
|
||||
outputPath string
|
||||
expectOutputFilename string
|
||||
}{
|
||||
{
|
||||
scenario: "with Gotenberg-Output-Filename header",
|
||||
ctx: func() *Context {
|
||||
c := echo.New().NewContext(httptest.NewRequest(http.MethodGet, "/foo", nil), nil)
|
||||
c.Request().Header.Set("Gotenberg-Output-Filename", "foo")
|
||||
return &Context{echoCtx: c}
|
||||
}(),
|
||||
outputPath: "/foo/bar.txt",
|
||||
expectOutputFilename: "foo.txt",
|
||||
},
|
||||
{
|
||||
scenario: "without custom filename",
|
||||
ctx: func() *Context {
|
||||
c := echo.New().NewContext(httptest.NewRequest(http.MethodGet, "/foo", nil), nil)
|
||||
return &Context{echoCtx: c}
|
||||
}(),
|
||||
outputPath: "/foo/foo.txt",
|
||||
expectOutputFilename: "foo.txt",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
actual := tc.ctx.OutputFilename(tc.outputPath)
|
||||
|
||||
if actual != tc.expectOutputFilename {
|
||||
t.Errorf("expected '%s' but got '%s'", tc.expectOutputFilename, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1425,36 +1425,36 @@ func TestFormData_Content(t *testing.T) {
|
||||
scenario: "file does exist without file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo",
|
||||
defaultValue: "",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "file does exist with an uppercase file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo.TXT": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo.TXT": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo.txt",
|
||||
defaultValue: "",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "file does exist without a lowercase file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo.txt": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo.txt": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo.txt",
|
||||
defaultValue: "",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
@@ -1519,33 +1519,33 @@ func TestFormData_MandatoryContent(t *testing.T) {
|
||||
scenario: "mandatory file does exist without file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "mandatory file does exist with an uppercase file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo.TXT": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo.TXT": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo.txt",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "mandatory file does exist without a lowercase file extension",
|
||||
form: &FormData{
|
||||
files: map[string]string{
|
||||
"foo.txt": "/tests/test/testdata/api/sample1.txt",
|
||||
"foo.txt": "testdata/sample.txt",
|
||||
},
|
||||
},
|
||||
filename: "foo.txt",
|
||||
expect: "foo",
|
||||
expect: "This is a text from a text file.",
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
|
||||
@@ -1,584 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestParseError(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
err error
|
||||
expectStatus int
|
||||
expectMessage string
|
||||
}{
|
||||
{
|
||||
err: echo.ErrInternalServerError,
|
||||
expectStatus: http.StatusInternalServerError,
|
||||
expectMessage: http.StatusText(http.StatusInternalServerError),
|
||||
},
|
||||
{
|
||||
err: gotenberg.ErrFiltered,
|
||||
expectStatus: http.StatusForbidden,
|
||||
expectMessage: http.StatusText(http.StatusForbidden),
|
||||
},
|
||||
{
|
||||
err: gotenberg.ErrMaximumQueueSizeExceeded,
|
||||
expectStatus: 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,
|
||||
expectStatus: http.StatusBadRequest,
|
||||
expectMessage: "At least one PDF engine cannot process the requested PDF format, while others may have failed to convert due to different issues",
|
||||
},
|
||||
{
|
||||
err: gotenberg.ErrPdfEngineMetadataValueNotSupported,
|
||||
expectStatus: http.StatusBadRequest,
|
||||
expectMessage: "At least one PDF engine cannot process the requested metadata, while others may have failed to convert due to different issues",
|
||||
},
|
||||
{
|
||||
err: WrapError(
|
||||
errors.New("foo"),
|
||||
NewSentinelHttpError(http.StatusBadRequest, "foo"),
|
||||
),
|
||||
expectStatus: http.StatusBadRequest,
|
||||
expectMessage: "foo",
|
||||
},
|
||||
} {
|
||||
actualStatus, actualMessage := ParseError(tc.err)
|
||||
|
||||
if actualStatus != tc.expectStatus {
|
||||
t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, actualStatus)
|
||||
}
|
||||
|
||||
if actualMessage != tc.expectMessage {
|
||||
t.Errorf("test %d: expected message '%s' but got '%s'", i, tc.expectMessage, actualMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpErrorHandler(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
err error
|
||||
expectStatus int
|
||||
expectMessage string
|
||||
}{
|
||||
{
|
||||
err: echo.ErrInternalServerError,
|
||||
expectStatus: http.StatusInternalServerError,
|
||||
expectMessage: http.StatusText(http.StatusInternalServerError),
|
||||
},
|
||||
{
|
||||
err: context.DeadlineExceeded,
|
||||
expectStatus: http.StatusServiceUnavailable,
|
||||
expectMessage: http.StatusText(http.StatusServiceUnavailable),
|
||||
},
|
||||
{
|
||||
err: WrapError(
|
||||
errors.New("foo"),
|
||||
NewSentinelHttpError(http.StatusBadRequest, "foo"),
|
||||
),
|
||||
expectStatus: http.StatusBadRequest,
|
||||
expectMessage: "foo",
|
||||
},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
c.Set("logger", zap.NewNop())
|
||||
|
||||
handler := httpErrorHandler()
|
||||
handler(tc.err, c)
|
||||
|
||||
contentType := recorder.Header().Get(echo.HeaderContentType)
|
||||
if contentType != echo.MIMETextPlainCharsetUTF8 {
|
||||
t.Errorf("test %d: expected %s '%s' but got '%s'", i, echo.HeaderContentType, echo.MIMETextPlainCharsetUTF8, contentType)
|
||||
}
|
||||
|
||||
// Note: we cannot test the trace header in the response here, as it is set in the trace middleware.
|
||||
|
||||
if recorder.Code != tc.expectStatus {
|
||||
t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, recorder.Code)
|
||||
}
|
||||
|
||||
if recorder.Body.String() != tc.expectMessage {
|
||||
t.Errorf("test %d: expected message '%s' but got '%s'", i, tc.expectMessage, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLatencyMiddleware(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
|
||||
err := latencyMiddleware()(
|
||||
func(c echo.Context) error {
|
||||
return nil
|
||||
},
|
||||
)(c)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
startTime := c.Get("startTime").(time.Time)
|
||||
now := time.Now()
|
||||
|
||||
if now.Before(startTime) {
|
||||
t.Errorf("expected start time %s to be < %s", startTime, now)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootPathMiddleware(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
|
||||
err := rootPathMiddleware("foo")(
|
||||
func(c echo.Context) error {
|
||||
return nil
|
||||
},
|
||||
)(c)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
rootPath := c.Get("rootPath").(string)
|
||||
|
||||
if rootPath != "foo" {
|
||||
t.Errorf("expected '%s' but got '%s", "foo", rootPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTraceMiddleware(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
trace string
|
||||
}{
|
||||
{
|
||||
trace: "foo",
|
||||
},
|
||||
{
|
||||
trace: "",
|
||||
},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
|
||||
if tc.trace != "" {
|
||||
c.Request().Header.Set("Gotenberg-Trace", tc.trace)
|
||||
}
|
||||
|
||||
err := traceMiddleware("Gotenberg-Trace")(
|
||||
func(c echo.Context) error {
|
||||
return nil
|
||||
},
|
||||
)(c)
|
||||
if err != nil {
|
||||
t.Fatalf("test %d: expected no error but got: %v", i, err)
|
||||
}
|
||||
|
||||
trace := c.Get("trace").(string)
|
||||
|
||||
if trace == "" {
|
||||
t.Errorf("test %d: expected non empty trace in context", i)
|
||||
}
|
||||
|
||||
if tc.trace != "" && trace != tc.trace {
|
||||
t.Errorf("test %d: expected context trace '%s' but got '%s'", i, tc.trace, trace)
|
||||
}
|
||||
|
||||
if tc.trace == "" && trace == tc.trace {
|
||||
t.Errorf("test %d: expected context trace different from '%s' but got '%s'", i, tc.trace, trace)
|
||||
}
|
||||
|
||||
responseTrace := recorder.Header().Get("Gotenberg-Trace")
|
||||
|
||||
if tc.trace != "" && responseTrace != tc.trace {
|
||||
t.Errorf("test %d: expected header trace '%s' but got '%s'", i, tc.trace, responseTrace)
|
||||
}
|
||||
|
||||
if tc.trace == "" && responseTrace == tc.trace {
|
||||
t.Errorf("test %d: expected header trace different from '%s' but got '%s'", i, tc.trace, responseTrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicAuthMiddleware(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
request *http.Request
|
||||
username string
|
||||
password string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid basic auth",
|
||||
request: func() *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.SetBasicAuth("invalid", "invalid")
|
||||
return req
|
||||
}(),
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "valid basic auth",
|
||||
request: func() *http.Request {
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.SetBasicAuth("foo", "bar")
|
||||
return req
|
||||
}(),
|
||||
username: "foo",
|
||||
password: "bar",
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
c := srv.NewContext(tc.request, recorder)
|
||||
err := basicAuthMiddleware(tc.username, tc.password)(func(c echo.Context) error {
|
||||
return nil
|
||||
})(c)
|
||||
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 TestLoggerMiddleware(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
request *http.Request
|
||||
next echo.HandlerFunc
|
||||
skipLogging bool
|
||||
}{
|
||||
{
|
||||
request: httptest.NewRequest(http.MethodGet, "/", nil),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
}(),
|
||||
},
|
||||
{
|
||||
request: httptest.NewRequest(http.MethodGet, "/health", nil),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
skipLogging: true,
|
||||
},
|
||||
{
|
||||
request: httptest.NewRequest(http.MethodGet, "/health", nil),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(tc.request, recorder)
|
||||
c.Set("startTime", time.Now())
|
||||
c.Set("trace", "foo")
|
||||
c.Set("rootPath", "/")
|
||||
|
||||
var disableLoggingForPaths []string
|
||||
if tc.skipLogging {
|
||||
disableLoggingForPaths = append(disableLoggingForPaths, tc.request.RequestURI)
|
||||
}
|
||||
|
||||
err := loggerMiddleware(zap.NewNop(), disableLoggingForPaths)(tc.next)(c)
|
||||
if err != nil {
|
||||
t.Errorf("test %d: expected no error but got: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMiddleware(t *testing.T) {
|
||||
buildMultipartFormDataRequest := func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err := writer.WriteField("foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
for i, tc := range []struct {
|
||||
request *http.Request
|
||||
next echo.HandlerFunc
|
||||
expectErr bool
|
||||
expectStatus int
|
||||
expectContentType string
|
||||
expectFilename string
|
||||
}{
|
||||
{
|
||||
request: httptest.NewRequest(http.MethodGet, "/", nil),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
request: buildMultipartFormDataRequest(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return ErrAsyncProcess
|
||||
}
|
||||
}(),
|
||||
expectStatus: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
request: buildMultipartFormDataRequest(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return ErrNoOutputFile
|
||||
}
|
||||
}(),
|
||||
expectStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
request: buildMultipartFormDataRequest(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
}(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
request: buildMultipartFormDataRequest(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Output-Filename", "foo")
|
||||
|
||||
return req
|
||||
}(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*Context)
|
||||
ctx.outputPaths = []string{
|
||||
"/tests/test/testdata/api/sample2.pdf",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
expectStatus: http.StatusOK,
|
||||
expectContentType: "application/pdf",
|
||||
expectFilename: "foo.pdf",
|
||||
},
|
||||
{
|
||||
request: buildMultipartFormDataRequest(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*Context)
|
||||
ctx.outputPaths = []string{
|
||||
"/tests/test/testdata/api/sample1.txt",
|
||||
"/tests/test/testdata/api/sample2.pdf",
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
expectStatus: http.StatusOK,
|
||||
expectContentType: "application/zip",
|
||||
},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(tc.request, recorder)
|
||||
c.Set("logger", zap.NewNop())
|
||||
c.Set("traceHeader", "Gotenberg-Trace")
|
||||
c.Set("trace", "foo")
|
||||
c.Set("startTime", time.Now())
|
||||
|
||||
err := contextMiddleware(gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)), time.Duration(10)*time.Second, 0, downloadFromConfig{})(tc.next)(c)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||
}
|
||||
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("test %d: expected no error but got: %v", i, err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if recorder.Code != tc.expectStatus {
|
||||
t.Errorf("test %d: expected HTTP status code %d but got %d", i, tc.expectStatus, recorder.Code)
|
||||
}
|
||||
|
||||
if tc.expectStatus == http.StatusNoContent {
|
||||
continue
|
||||
}
|
||||
|
||||
contentType := recorder.Header().Get(echo.HeaderContentType)
|
||||
if contentType != tc.expectContentType {
|
||||
t.Errorf("test %d: expected %s '%s' but got '%s'", i, echo.HeaderContentType, tc.expectContentType, contentType)
|
||||
}
|
||||
|
||||
contentDisposition := recorder.Header().Get(echo.HeaderContentDisposition)
|
||||
if !strings.Contains(contentDisposition, tc.expectFilename) {
|
||||
t.Errorf("test %d: expected %s '%s' to contain '%s'", i, echo.HeaderContentDisposition, contentDisposition, tc.expectFilename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHardTimeoutMiddleware(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
next echo.HandlerFunc
|
||||
timeout time.Duration
|
||||
expectErr bool
|
||||
expectHardTimeout bool
|
||||
}{
|
||||
{
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
timeout: time.Duration(100) * time.Millisecond,
|
||||
},
|
||||
{
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
panic("foo")
|
||||
}
|
||||
}(),
|
||||
timeout: time.Duration(100) * time.Millisecond,
|
||||
expectErr: true,
|
||||
expectHardTimeout: true,
|
||||
},
|
||||
{
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
}(),
|
||||
timeout: time.Duration(100) * time.Millisecond,
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
time.Sleep(time.Duration(200) * time.Millisecond)
|
||||
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
timeout: time.Duration(100) * time.Millisecond,
|
||||
expectErr: true,
|
||||
expectHardTimeout: true,
|
||||
},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/foo", nil)
|
||||
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(request, recorder)
|
||||
c.Set("logger", zap.NewNop())
|
||||
|
||||
err := hardTimeoutMiddleware(tc.timeout)(tc.next)(c)
|
||||
|
||||
if tc.expectErr && err == nil {
|
||||
t.Errorf("test %d: expected error but got: %v", i, err)
|
||||
}
|
||||
|
||||
if !tc.expectErr && err != nil {
|
||||
t.Errorf("test %d: expected no error but got: %v", i, err)
|
||||
}
|
||||
|
||||
var isHardTimeout bool
|
||||
if err != nil {
|
||||
isHardTimeout = strings.Contains(err.Error(), "hard timeout")
|
||||
}
|
||||
|
||||
if tc.expectHardTimeout && !isHardTimeout {
|
||||
t.Errorf("test %d: expected hard timeout error but got: %v", i, err)
|
||||
}
|
||||
|
||||
if !tc.expectHardTimeout && isHardTimeout {
|
||||
t.Errorf("test %d: expected no hard timeout error but got one: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/alexliesenfeld/health"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestContextMock_SetDirPath(t *testing.T) {
|
||||
mock := &ContextMock{&Context{}}
|
||||
mock.SetDirPath("/foo")
|
||||
|
||||
actual := mock.dirPath
|
||||
expect := "/foo"
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_DirPath(t *testing.T) {
|
||||
mock := &ContextMock{&Context{}}
|
||||
mock.SetDirPath("/foo")
|
||||
|
||||
actual := mock.DirPath()
|
||||
expect := "/foo"
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_SetValues(t *testing.T) {
|
||||
mock := &ContextMock{&Context{}}
|
||||
mock.SetValues(map[string][]string{
|
||||
"foo": {"foo"},
|
||||
})
|
||||
|
||||
actual := mock.values
|
||||
expect := map[string][]string{
|
||||
"foo": {"foo"},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf("expected %+v but got: %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_SetFiles(t *testing.T) {
|
||||
mock := &ContextMock{&Context{}}
|
||||
mock.SetFiles(map[string]string{
|
||||
"foo": "/foo",
|
||||
})
|
||||
|
||||
actual := mock.files
|
||||
expect := map[string]string{
|
||||
"foo": "/foo",
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf("expected %+v but got: %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_SetCancelled(t *testing.T) {
|
||||
mock := &ContextMock{&Context{}}
|
||||
mock.SetCancelled(true)
|
||||
|
||||
actual := mock.cancelled
|
||||
|
||||
if !actual {
|
||||
t.Errorf("expected %t but got %t", true, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_OutputPaths(t *testing.T) {
|
||||
mock := ContextMock{
|
||||
&Context{
|
||||
outputPaths: []string{"/foo"},
|
||||
},
|
||||
}
|
||||
|
||||
actual := mock.OutputPaths()
|
||||
expect := []string{"/foo"}
|
||||
|
||||
if !reflect.DeepEqual(actual, expect) {
|
||||
t.Errorf("expected %+v but got: %+v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_SetLogger(t *testing.T) {
|
||||
mock := ContextMock{&Context{}}
|
||||
|
||||
expect := zap.NewNop()
|
||||
mock.SetLogger(expect)
|
||||
|
||||
actual := mock.logger
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected %v but got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextMock_SetEchoContext(t *testing.T) {
|
||||
mock := ContextMock{&Context{}}
|
||||
|
||||
expect := echo.New().NewContext(nil, nil)
|
||||
mock.SetEchoContext(expect)
|
||||
|
||||
actual := mock.echoCtx
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected %v but got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
mock := ContextMock{&Context{}}
|
||||
|
||||
expect := new(gotenberg.OsPathRename)
|
||||
mock.SetPathRename(expect)
|
||||
|
||||
actual := mock.pathRename
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected %v but got %v", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouterMock(t *testing.T) {
|
||||
mock := &RouterMock{
|
||||
RoutesMock: func() ([]Route, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.Routes()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from RouterMock.Routes, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareProviderMock(t *testing.T) {
|
||||
mock := &MiddlewareProviderMock{
|
||||
MiddlewaresMock: func() ([]Middleware, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.Middlewares()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from MiddlewareProviderMock.Middlewares, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthCheckerMock(t *testing.T) {
|
||||
mock := &HealthCheckerMock{
|
||||
ChecksMock: func() ([]health.CheckerOption, error) {
|
||||
return nil, nil
|
||||
},
|
||||
ReadyMock: func() error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.Checks()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from HealthCheckerMock.Checks, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.Ready()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from HealthCheckerMock.Ready, but got: %v", err)
|
||||
}
|
||||
}
|
||||
1
pkg/modules/api/testdata/sample.txt
vendored
Normal file
1
pkg/modules/api/testdata/sample.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This is a text from a text file.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,637 +0,0 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alexliesenfeld/health"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestDefaultOptions(t *testing.T) {
|
||||
actual := DefaultOptions()
|
||||
notExpect := Options{}
|
||||
|
||||
if reflect.DeepEqual(actual, notExpect) {
|
||||
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultPdfOptions(t *testing.T) {
|
||||
actual := DefaultPdfOptions()
|
||||
notExpect := PdfOptions{}
|
||||
|
||||
if reflect.DeepEqual(actual, notExpect) {
|
||||
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultScreenshotOptions(t *testing.T) {
|
||||
actual := DefaultScreenshotOptions()
|
||||
notExpect := ScreenshotOptions{}
|
||||
|
||||
if reflect.DeepEqual(actual, notExpect) {
|
||||
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Descriptor(t *testing.T) {
|
||||
descriptor := new(Chromium).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(Chromium))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Provision(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no logger from logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine from PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
gotenberg.PdfEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
gotenberg.PdfEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) {
|
||||
return new(gotenberg.PdfEngineMock), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Chromium).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
err := mod.Provision(tc.ctx)
|
||||
|
||||
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 TestChromium_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty bin path",
|
||||
binPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "bin path does not exist",
|
||||
binPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.args = browserArguments{
|
||||
binPath: tc.binPath,
|
||||
}
|
||||
err := mod.Validate()
|
||||
|
||||
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 TestChromium_Start(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
autoStart bool
|
||||
supervisor *gotenberg.ProcessSupervisorMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no auto-start",
|
||||
autoStart: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start success",
|
||||
autoStart: true,
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start failed",
|
||||
autoStart: true,
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error {
|
||||
return errors.New("foo")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.autoStart = tc.autoStart
|
||||
mod.supervisor = tc.supervisor
|
||||
|
||||
err := mod.Start()
|
||||
|
||||
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 TestChromium_StartupMessage(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
|
||||
mod.autoStart = true
|
||||
autoStartMsg := mod.StartupMessage()
|
||||
|
||||
mod.autoStart = false
|
||||
noAutoStartMsg := mod.StartupMessage()
|
||||
|
||||
if autoStartMsg == noAutoStartMsg {
|
||||
t.Errorf("expected differrent startup messages based on auto start, but got '%s'", autoStartMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Stop(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor *gotenberg.ProcessSupervisorMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "stop success",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "stop failed",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error {
|
||||
return errors.New("foo")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.logger = zap.NewNop()
|
||||
mod.supervisor = tc.supervisor
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second)
|
||||
cancel()
|
||||
|
||||
err := mod.Stop(ctx)
|
||||
|
||||
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 TestChromium_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
mod *Chromium
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version",
|
||||
mod: &Chromium{
|
||||
args: browserArguments{
|
||||
binPath: "foo",
|
||||
},
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
mod: &Chromium{
|
||||
args: browserArguments{
|
||||
binPath: "echo",
|
||||
},
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": `exec: "echo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.mod.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Metrics(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.supervisor = &gotenberg.ProcessSupervisorMock{
|
||||
ReqQueueSizeMock: func() int64 {
|
||||
return 10
|
||||
},
|
||||
RestartsCountMock: func() int64 {
|
||||
return 0
|
||||
},
|
||||
}
|
||||
|
||||
metrics, err := mod.Metrics()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if len(metrics) != 2 {
|
||||
t.Fatalf("expected %d metrics, but got %d", 2, len(metrics))
|
||||
}
|
||||
|
||||
actual := metrics[0].Read()
|
||||
if actual != float64(10) {
|
||||
t.Errorf("expected %f for chromium_requests_queue_size, but got %f", float64(10), actual)
|
||||
}
|
||||
|
||||
actual = metrics[1].Read()
|
||||
if actual != float64(0) {
|
||||
t.Errorf("expected %f for chromium_restarts_count, but got %f", float64(0), actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Checks(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
expectAvailabilityStatus health.AvailabilityStatus
|
||||
}{
|
||||
{
|
||||
scenario: "healthy module",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool {
|
||||
return true
|
||||
}},
|
||||
expectAvailabilityStatus: health.StatusUp,
|
||||
},
|
||||
{
|
||||
scenario: "unhealthy module",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool {
|
||||
return false
|
||||
}},
|
||||
expectAvailabilityStatus: health.StatusDown,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.supervisor = tc.supervisor
|
||||
|
||||
checks, err := mod.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
checker := health.NewChecker(checks...)
|
||||
result := checker.Check(context.Background())
|
||||
|
||||
if result.Status != tc.expectAvailabilityStatus {
|
||||
t.Errorf("expected '%s' as availability status, but got '%s'", tc.expectAvailabilityStatus, result.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Ready(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
autoStart bool
|
||||
startTimeout time.Duration
|
||||
browser browser
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no auto-start",
|
||||
autoStart: false,
|
||||
startTimeout: time.Duration(30) * time.Second,
|
||||
browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return false
|
||||
}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start: context done",
|
||||
autoStart: true,
|
||||
startTimeout: time.Duration(200) * time.Millisecond,
|
||||
browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return false
|
||||
}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start success",
|
||||
autoStart: true,
|
||||
startTimeout: time.Duration(30) * time.Second,
|
||||
browser: &browserMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return true
|
||||
}}},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.autoStart = tc.autoStart
|
||||
mod.args = browserArguments{wsUrlReadTimeout: tc.startTimeout}
|
||||
mod.browser = tc.browser
|
||||
|
||||
err := mod.Ready()
|
||||
|
||||
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 TestChromium_Chromium(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
|
||||
_, err := mod.Chromium()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Routes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
expectRoutes int
|
||||
disableRoutes bool
|
||||
}{
|
||||
{
|
||||
scenario: "routes not disabled",
|
||||
expectRoutes: 6,
|
||||
disableRoutes: false,
|
||||
},
|
||||
{
|
||||
scenario: "routes disabled",
|
||||
expectRoutes: 0,
|
||||
disableRoutes: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.disableRoutes = tc.disableRoutes
|
||||
|
||||
routes, err := mod.Routes()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectRoutes != len(routes) {
|
||||
t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Pdf(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
browser browser
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "PDF task success",
|
||||
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "PDF task error",
|
||||
browser: &browserMock{pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return errors.New("PDF task error")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error {
|
||||
return task()
|
||||
}}
|
||||
mod.browser = tc.browser
|
||||
|
||||
err := mod.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
||||
|
||||
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 TestChromium_Screenshot(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
browser browser
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "Screenshot task success",
|
||||
browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "Screenshot task error",
|
||||
browser: &browserMock{screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return errors.New("screenshot task error")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Chromium)
|
||||
mod.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error {
|
||||
return task()
|
||||
}}
|
||||
mod.browser = tc.browser
|
||||
|
||||
err := mod.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
||||
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestDebugLogger_Write(t *testing.T) {
|
||||
actual, err := (&debugLogger{logger: zap.NewNop()}).Write([]byte("foo"))
|
||||
expected := len([]byte("foo"))
|
||||
|
||||
if actual != expected {
|
||||
t.Errorf("expected %d but got %d", expected, actual)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("expected not error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugLogger_Printf(t *testing.T) {
|
||||
(&debugLogger{logger: zap.NewNop()}).Printf("%s", "foo")
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package chromium
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestApiMock(t *testing.T) {
|
||||
mock := &ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return nil
|
||||
},
|
||||
ScreenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.Screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ApiMock.Screenshot, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBrowserMock(t *testing.T) {
|
||||
mock := &browserMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options PdfOptions) error {
|
||||
return nil
|
||||
},
|
||||
screenshotMock: func(ctx context.Context, logger *zap.Logger, url, outputPath string, options ScreenshotOptions) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.pdf(context.Background(), zap.NewNop(), "", "", PdfOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from browserMock.pdf, but got: %v", err)
|
||||
}
|
||||
|
||||
err = mock.screenshot(context.Background(), zap.NewNop(), "", "", ScreenshotOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from browserMock.screenshot, but got: %v", err)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,415 +0,0 @@
|
||||
package exiftool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestExifTool_Descriptor(t *testing.T) {
|
||||
descriptor := new(ExifTool).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(ExifTool))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExifTool_Provision(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil)
|
||||
|
||||
err := engine.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExifTool_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty bin path",
|
||||
binPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "bin path does not exist",
|
||||
binPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("EXIFTOOL_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
engine.binPath = tc.binPath
|
||||
err := engine.Validate()
|
||||
|
||||
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 TestExifTool_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *ExifTool
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version",
|
||||
engine: &ExifTool{
|
||||
binPath: "foo",
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
engine: &ExifTool{
|
||||
binPath: "echo",
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": `exec: "echo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.engine.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExiftool_Merge(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
err := engine.Merge(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
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_Flatten(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
err := engine.Flatten(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExiftool_Convert(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExiftool_ReadMetadata(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
inputPath string
|
||||
expectMetadata map[string]interface{}
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
inputPath: "foo",
|
||||
expectMetadata: nil,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
expectMetadata: map[string]interface{}{
|
||||
"FileName": "sample1.pdf",
|
||||
"FileTypeExtension": "pdf",
|
||||
"MIMEType": "application/pdf",
|
||||
"PDFVersion": 1.4,
|
||||
"PageCount": float64(3),
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
err := engine.Provision(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected error but got: %v", err)
|
||||
}
|
||||
|
||||
metadata, err := engine.ReadMetadata(context.Background(), zap.NewNop(), tc.inputPath)
|
||||
|
||||
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.expectMetadata != nil && err == nil {
|
||||
for k, v := range tc.expectMetadata {
|
||||
if v2, ok := metadata[k]; !ok || v != v2 {
|
||||
t.Errorf("expected entry %s with value %v to exists", k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExiftool_WriteMetadata(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
createCopy bool
|
||||
inputPath string
|
||||
metadata map[string]interface{}
|
||||
expectMetadata map[string]interface{}
|
||||
expectError bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
createCopy: false,
|
||||
inputPath: "foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "gotenberg.ErrPdfEngineMetadataValueNotSupported (not string array)",
|
||||
createCopy: true,
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
metadata: map[string]interface{}{
|
||||
"Unsupported": []interface{}{
|
||||
"foo",
|
||||
1,
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectedError: gotenberg.ErrPdfEngineMetadataValueNotSupported,
|
||||
},
|
||||
{
|
||||
scenario: "gotenberg.ErrPdfEngineMetadataValueNotSupported (default)",
|
||||
createCopy: true,
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
metadata: map[string]interface{}{
|
||||
"Unsupported": map[string]interface{}{},
|
||||
},
|
||||
expectError: true,
|
||||
expectedError: gotenberg.ErrPdfEngineMetadataValueNotSupported,
|
||||
},
|
||||
{
|
||||
scenario: "success (interface array to string array)",
|
||||
createCopy: true,
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
metadata: map[string]interface{}{
|
||||
"Keywords": []interface{}{
|
||||
"first",
|
||||
"second",
|
||||
},
|
||||
},
|
||||
expectMetadata: map[string]interface{}{
|
||||
"Keywords": []interface{}{
|
||||
"first",
|
||||
"second",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
createCopy: true,
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
metadata: map[string]interface{}{
|
||||
"Author": "Julien Neuhart",
|
||||
"Copyright": "Julien Neuhart",
|
||||
"CreationDate": "2006-09-18T16:27:50-04:00",
|
||||
"Creator": "Gotenberg",
|
||||
"Keywords": []string{
|
||||
"first",
|
||||
"second",
|
||||
},
|
||||
"Marked": true,
|
||||
"ModDate": "2006-09-18T16:27:50-04:00",
|
||||
"PDFVersion": 1.7,
|
||||
"Producer": "Gotenberg",
|
||||
"Subject": "Sample",
|
||||
"Title": "Sample",
|
||||
"Trapped": "Unknown",
|
||||
// Those are not valid PDF metadata.
|
||||
"int": 1,
|
||||
"int64": int64(2),
|
||||
"float32": float32(2.2),
|
||||
"float64": 3.3,
|
||||
},
|
||||
expectMetadata: map[string]interface{}{
|
||||
"Author": "Julien Neuhart",
|
||||
"Copyright": "Julien Neuhart",
|
||||
"CreationDate": "2006:09:18 16:27:50-04:00",
|
||||
"Creator": "Gotenberg",
|
||||
"Keywords": []interface{}{
|
||||
"first",
|
||||
"second",
|
||||
},
|
||||
"Marked": true,
|
||||
"ModDate": "2006:09:18 16:27:50-04:00",
|
||||
"PDFVersion": 1.7,
|
||||
"Producer": "Gotenberg",
|
||||
"Subject": "Sample",
|
||||
"Title": "Sample",
|
||||
"Trapped": "Unknown",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(ExifTool)
|
||||
err := engine.Provision(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
var destinationPath string
|
||||
if tc.createCopy {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
outputDir, err := fs.MkdirAll()
|
||||
if err != nil {
|
||||
t.Fatalf("expected error no 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)
|
||||
}
|
||||
}()
|
||||
|
||||
destinationPath = fmt.Sprintf("%s/copy_temp.pdf", outputDir)
|
||||
source, err := os.Open(tc.inputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open source file: %v", err)
|
||||
}
|
||||
|
||||
defer func(source *os.File) {
|
||||
err := source.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("close file: %v", err)
|
||||
}
|
||||
}(source)
|
||||
|
||||
destination, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
t.Fatalf("create destination file: %v", err)
|
||||
}
|
||||
|
||||
defer func(destination *os.File) {
|
||||
err := destination.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("close file: %v", err)
|
||||
}
|
||||
}(destination)
|
||||
|
||||
_, err = io.Copy(destination, source)
|
||||
if err != nil {
|
||||
t.Fatalf("copy source into destination: %v", err)
|
||||
}
|
||||
} else {
|
||||
destinationPath = tc.inputPath
|
||||
}
|
||||
|
||||
err = engine.WriteMetadata(context.Background(), zap.NewNop(), tc.metadata, destinationPath)
|
||||
|
||||
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.expectError {
|
||||
return
|
||||
}
|
||||
|
||||
metadata, err := engine.ReadMetadata(context.Background(), zap.NewNop(), destinationPath)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectMetadata != nil && err == nil {
|
||||
for k, v := range tc.expectMetadata {
|
||||
v2, ok := metadata[k]
|
||||
if !ok {
|
||||
t.Errorf("expected entry %s with value %v to exists, but got none", k, v)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v2.(type) {
|
||||
case []interface{}:
|
||||
for i, entry := range v.([]interface{}) {
|
||||
if entry != v2.([]interface{})[i] {
|
||||
t.Errorf("expected entry %s to contain value %v, but got %v", k, entry, v2.([]interface{})[i])
|
||||
}
|
||||
}
|
||||
default:
|
||||
if v != v2 {
|
||||
t.Errorf("expected entry %s with value %v to exists, but got %v", k, v, v2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,522 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alexliesenfeld/health"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestDefaultOptions(t *testing.T) {
|
||||
actual := DefaultOptions()
|
||||
notExpect := Options{}
|
||||
|
||||
if reflect.DeepEqual(actual, notExpect) {
|
||||
t.Errorf("expected %v and got identical %v", actual, notExpect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Descriptor(t *testing.T) {
|
||||
descriptor := new(Api).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(Api))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Provision(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Api).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no logger from logger provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Api).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.LoggerProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LoggerMock = func(mod gotenberg.Module) (*zap.Logger, error) {
|
||||
return zap.NewNop(), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Api).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
err := a.Provision(tc.ctx)
|
||||
|
||||
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 TestApi_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
unoBinPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty LibreOffice bin path",
|
||||
binPath: "",
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "LibreOffice bin path does not exist",
|
||||
binPath: "/foo",
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "empty uno bin path",
|
||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||
unoBinPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "uno bin path does not exist",
|
||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||
unoBinPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("CHROMIUM_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.args = libreOfficeArguments{
|
||||
binPath: tc.binPath,
|
||||
unoBinPath: tc.unoBinPath,
|
||||
}
|
||||
err := a.Validate()
|
||||
|
||||
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 TestApi_Start(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
autoStart bool
|
||||
supervisor *gotenberg.ProcessSupervisorMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no auto-start",
|
||||
autoStart: false,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start success",
|
||||
autoStart: true,
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start failed",
|
||||
autoStart: true,
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{LaunchMock: func() error {
|
||||
return errors.New("foo")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.autoStart = tc.autoStart
|
||||
a.supervisor = tc.supervisor
|
||||
|
||||
err := a.Start()
|
||||
|
||||
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 TestApi_StartupMessage(t *testing.T) {
|
||||
a := new(Api)
|
||||
|
||||
a.autoStart = true
|
||||
autoStartMsg := a.StartupMessage()
|
||||
|
||||
a.autoStart = false
|
||||
noAutoStartMsg := a.StartupMessage()
|
||||
|
||||
if autoStartMsg == noAutoStartMsg {
|
||||
t.Errorf("expected differrent startup messages based on auto start, but got '%s'", autoStartMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Stop(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor *gotenberg.ProcessSupervisorMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "stop success",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "stop failed",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{ShutdownMock: func() error {
|
||||
return errors.New("foo")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.logger = zap.NewNop()
|
||||
a.supervisor = tc.supervisor
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 0*time.Second)
|
||||
cancel()
|
||||
|
||||
err := a.Stop(ctx)
|
||||
|
||||
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 TestPdfTk_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
a *Api
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version",
|
||||
a: &Api{
|
||||
args: libreOfficeArguments{
|
||||
binPath: "foo",
|
||||
},
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
a: &Api{
|
||||
args: libreOfficeArguments{
|
||||
binPath: "echo",
|
||||
},
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": `exec: "echo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.a.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Metrics(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.supervisor = &gotenberg.ProcessSupervisorMock{
|
||||
ReqQueueSizeMock: func() int64 {
|
||||
return 10
|
||||
},
|
||||
RestartsCountMock: func() int64 {
|
||||
return 0
|
||||
},
|
||||
}
|
||||
|
||||
metrics, err := a.Metrics()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if len(metrics) != 2 {
|
||||
t.Fatalf("expected %d metrics, but got %d", 2, len(metrics))
|
||||
}
|
||||
|
||||
actual := metrics[0].Read()
|
||||
if actual != float64(10) {
|
||||
t.Errorf("expected %f for libreoffice_requests_queue_size, but got %f", float64(10), actual)
|
||||
}
|
||||
|
||||
actual = metrics[1].Read()
|
||||
if actual != float64(0) {
|
||||
t.Errorf("expected %f for libreoffice_restarts_count, but got %f", float64(0), actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Checks(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
expectAvailabilityStatus health.AvailabilityStatus
|
||||
}{
|
||||
{
|
||||
scenario: "healthy module",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool {
|
||||
return true
|
||||
}},
|
||||
expectAvailabilityStatus: health.StatusUp,
|
||||
},
|
||||
{
|
||||
scenario: "unhealthy module",
|
||||
supervisor: &gotenberg.ProcessSupervisorMock{HealthyMock: func() bool {
|
||||
return false
|
||||
}},
|
||||
expectAvailabilityStatus: health.StatusDown,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.supervisor = tc.supervisor
|
||||
|
||||
checks, err := a.Checks()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
checker := health.NewChecker(checks...)
|
||||
result := checker.Check(context.Background())
|
||||
|
||||
if result.Status != tc.expectAvailabilityStatus {
|
||||
t.Errorf("expected '%s' as availability status, but got '%s'", tc.expectAvailabilityStatus, result.Status)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChromium_Ready(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
autoStart bool
|
||||
startTimeout time.Duration
|
||||
libreOffice libreOffice
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no auto-start",
|
||||
autoStart: false,
|
||||
startTimeout: time.Duration(30) * time.Second,
|
||||
libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return false
|
||||
}}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start: context done",
|
||||
autoStart: true,
|
||||
startTimeout: time.Duration(200) * time.Millisecond,
|
||||
libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return false
|
||||
}}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "auto-start success",
|
||||
autoStart: true,
|
||||
startTimeout: time.Duration(30) * time.Second,
|
||||
libreOffice: &libreOfficeMock{ProcessMock: gotenberg.ProcessMock{HealthyMock: func(logger *zap.Logger) bool {
|
||||
return true
|
||||
}}},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.autoStart = tc.autoStart
|
||||
a.args = libreOfficeArguments{startTimeout: tc.startTimeout}
|
||||
a.libreOffice = tc.libreOffice
|
||||
|
||||
err := a.Ready()
|
||||
|
||||
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 TestApi_LibreOffice(t *testing.T) {
|
||||
a := new(Api)
|
||||
|
||||
_, err := a.LibreOffice()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApi_Pdf(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
supervisor gotenberg.ProcessSupervisor
|
||||
libreOffice libreOffice
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "PDF task success",
|
||||
libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return nil
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "PDF task error",
|
||||
libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return errors.New("PDF task error")
|
||||
}},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrCoreDumped",
|
||||
libreOffice: &libreOfficeMock{pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
}},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
a := new(Api)
|
||||
a.supervisor = &gotenberg.ProcessSupervisorMock{RunMock: func(ctx context.Context, logger *zap.Logger, task func() error) error {
|
||||
return task()
|
||||
}}
|
||||
a.libreOffice = tc.libreOffice
|
||||
|
||||
err := a.Pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
|
||||
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 TestApi_Extensions(t *testing.T) {
|
||||
a := new(Api)
|
||||
extensions := a.Extensions()
|
||||
|
||||
actual := len(extensions)
|
||||
expect := 130
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected %d extensions, but got %d", expect, actual)
|
||||
}
|
||||
}
|
||||
@@ -1,699 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestLibreOfficeProcess_Start(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
libreOffice libreOffice
|
||||
expectError bool
|
||||
cleanup bool
|
||||
}{
|
||||
{
|
||||
scenario: "successful start",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
expectError: false,
|
||||
cleanup: true,
|
||||
},
|
||||
{
|
||||
scenario: "LibreOffice already started",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.isStarted.Store(true)
|
||||
return p
|
||||
}(),
|
||||
expectError: true,
|
||||
cleanup: false,
|
||||
},
|
||||
{
|
||||
scenario: "non-exit code 81 on first start",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: "foo",
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
expectError: true,
|
||||
cleanup: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
err := tc.libreOffice.Start(logger)
|
||||
|
||||
if tc.cleanup {
|
||||
defer func(p libreOffice, logger *zap.Logger) {
|
||||
err = p.Stop(logger)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error while cleaning up, but got: %v", err)
|
||||
}
|
||||
}(tc.libreOffice, logger)
|
||||
}
|
||||
|
||||
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 TestLibreOfficeProcess_Stop(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
libreOffice libreOffice
|
||||
setup func(libreOffice libreOffice, logger *zap.Logger) error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "successful stop",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
setup: func(p libreOffice, logger *zap.Logger) error {
|
||||
return p.Start(logger)
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "LibreOffice already stopped",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.isStarted.Store(false)
|
||||
return p
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
|
||||
if tc.setup != nil {
|
||||
err := tc.setup(tc.libreOffice, logger)
|
||||
if err != nil {
|
||||
t.Fatalf("setup error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err := tc.libreOffice.Stop(logger)
|
||||
|
||||
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 TestLibreOfficeProcess_Healthy(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
libreOffice libreOffice
|
||||
setup func(libreOffice libreOffice, logger *zap.Logger) error
|
||||
expectHealthy bool
|
||||
cleanup bool
|
||||
}{
|
||||
{
|
||||
scenario: "healthy LibreOffice",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
setup: func(p libreOffice, logger *zap.Logger) error {
|
||||
return p.Start(logger)
|
||||
},
|
||||
expectHealthy: true,
|
||||
cleanup: true,
|
||||
},
|
||||
{
|
||||
scenario: "LibreOffice not started",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.isStarted.Store(false)
|
||||
return p
|
||||
}(),
|
||||
expectHealthy: false,
|
||||
cleanup: false,
|
||||
},
|
||||
{
|
||||
scenario: "unhealthy LibreOffice",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.isStarted.Store(true)
|
||||
p.socketPort = 12345
|
||||
return p
|
||||
}(),
|
||||
expectHealthy: false,
|
||||
cleanup: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
logger := zap.NewNop()
|
||||
|
||||
if tc.setup != nil {
|
||||
err := tc.setup(tc.libreOffice, logger)
|
||||
if err != nil {
|
||||
t.Fatalf("setup error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.cleanup {
|
||||
defer func(p libreOffice, logger *zap.Logger) {
|
||||
err := p.Stop(logger)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error while cleaning up, but got: %v", err)
|
||||
}
|
||||
}(tc.libreOffice, logger)
|
||||
}
|
||||
|
||||
healthy := tc.libreOffice.Healthy(logger)
|
||||
|
||||
if !tc.expectHealthy && healthy {
|
||||
t.Fatal("expected unhealthy LibreOffice but got an healthy one")
|
||||
}
|
||||
|
||||
if tc.expectHealthy && !healthy {
|
||||
t.Fatal("expected a healthy LibreOffice but got an unhealthy one")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficeProcess_pdf(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
libreOffice libreOffice
|
||||
fs *gotenberg.FileSystem
|
||||
options Options
|
||||
cancelledCtx bool
|
||||
start bool
|
||||
expectError bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
scenario: "LibreOffice not started",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.isStarted.Store(false)
|
||||
return p
|
||||
}(),
|
||||
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||
cancelledCtx: false,
|
||||
start: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrInvalidPdfFormats",
|
||||
libreOffice: func() libreOffice {
|
||||
p := new(libreOfficeProcess)
|
||||
p.socketPort = 12345
|
||||
p.isStarted.Store(true)
|
||||
return p
|
||||
}(),
|
||||
fs: gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll)),
|
||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: "foo"}},
|
||||
cancelledCtx: false,
|
||||
start: false,
|
||||
expectError: true,
|
||||
expectedError: ErrInvalidPdfFormats,
|
||||
},
|
||||
{
|
||||
scenario: "ErrUnoException",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
options: Options{PageRanges: "foo"},
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: true,
|
||||
expectedError: ErrUnoException,
|
||||
},
|
||||
{
|
||||
scenario: "ErrRuntimeException",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
options: Options{Password: "foo"},
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
in, err := os.Open("/tests/test/testdata/libreoffice/protected.docx")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := in.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
out, err := os.Create(fmt.Sprintf("%s/protected.docx", fs.WorkingDirPath()))
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err := out.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: true,
|
||||
expectedError: ErrRuntimeException,
|
||||
},
|
||||
{
|
||||
scenario: "context done",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Context done"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
cancelledCtx: true,
|
||||
start: true,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success (default options)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success (not default options)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Success"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
options: Options{
|
||||
Password: "", // Ok, the only exception in this list.
|
||||
Landscape: true,
|
||||
PageRanges: "1",
|
||||
ExportFormFields: false,
|
||||
AllowDuplicateFieldNames: true,
|
||||
ExportBookmarks: false,
|
||||
ExportBookmarksToPdfDestination: true,
|
||||
ExportPlaceholders: true,
|
||||
ExportNotes: true,
|
||||
ExportNotesPages: true,
|
||||
ExportOnlyNotesPages: true,
|
||||
ExportNotesInMargin: true,
|
||||
ConvertOooTargetToPdfTarget: true,
|
||||
ExportLinksRelativeFsys: true,
|
||||
ExportHiddenSlides: true,
|
||||
SkipEmptyPages: true,
|
||||
AddOriginalDocumentAsStream: true,
|
||||
SinglePageSheets: true,
|
||||
LosslessImageCompression: true,
|
||||
Quality: 100,
|
||||
ReduceImageResolution: true,
|
||||
MaxImageResolution: 600,
|
||||
},
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success (PDF/A-1b)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA1b}},
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success (PDF/A-2b)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA2b}},
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success (PDF/A-3b)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfA: gotenberg.PdfA3b}},
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success (PDF/UA)",
|
||||
libreOffice: newLibreOfficeProcess(
|
||||
libreOfficeArguments{
|
||||
binPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
|
||||
unoBinPath: os.Getenv("UNOCONVERTER_BIN_PATH"),
|
||||
startTimeout: 5 * time.Second,
|
||||
},
|
||||
),
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Landscape"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
options: Options{PdfFormats: gotenberg.PdfFormats{PdfUa: true}},
|
||||
cancelledCtx: false,
|
||||
start: true,
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
// Force the debug level.
|
||||
logger := zap.NewExample()
|
||||
|
||||
defer func() {
|
||||
err := os.RemoveAll(tc.fs.WorkingDirPath())
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error while cleaning up, but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if tc.start {
|
||||
err := tc.libreOffice.Start(logger)
|
||||
if err != nil {
|
||||
t.Fatalf("setup error: %v", err)
|
||||
}
|
||||
|
||||
defer func(p libreOffice, logger *zap.Logger) {
|
||||
err = p.Stop(logger)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error while cleaning up, but got: %v", err)
|
||||
}
|
||||
}(tc.libreOffice, logger)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if tc.cancelledCtx {
|
||||
cancel()
|
||||
}
|
||||
|
||||
err := tc.libreOffice.pdf(
|
||||
ctx,
|
||||
logger,
|
||||
fmt.Sprintf("%s/document.txt", tc.fs.WorkingDirPath()),
|
||||
fmt.Sprintf("%s/%s.pdf", tc.fs.WorkingDirPath(), uuid.NewString()),
|
||||
tc.options,
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNonBasicLatinCharactersGuard(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
fs *gotenberg.FileSystem
|
||||
filename string
|
||||
expectSameInputPath bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "basic latin characters",
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/document.txt", fs.WorkingDirPath()), []byte("Basic latin characters"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
filename: "document.txt",
|
||||
expectSameInputPath: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "non-basic latin characters",
|
||||
fs: func() *gotenberg.FileSystem {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
|
||||
err := os.MkdirAll(fs.WorkingDirPath(), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(fmt.Sprintf("%s/éèßàùä.txt", fs.WorkingDirPath()), []byte("Non-basic latin characters"), 0o755)
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return fs
|
||||
}(),
|
||||
filename: "éèßàùä.txt",
|
||||
expectSameInputPath: false,
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
defer func() {
|
||||
err := os.RemoveAll(tc.fs.WorkingDirPath())
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error while cleaning up, but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
inputPath := fmt.Sprintf("%s/%s", tc.fs.WorkingDirPath(), tc.filename)
|
||||
newInputPath, err := nonBasicLatinCharactersGuard(
|
||||
zap.NewNop(),
|
||||
inputPath,
|
||||
)
|
||||
|
||||
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.expectSameInputPath && newInputPath != inputPath {
|
||||
t.Fatalf("expected same input path, but got '%s'", newInputPath)
|
||||
}
|
||||
|
||||
if !tc.expectSameInputPath && newInputPath == inputPath {
|
||||
t.Fatalf("expected different input path, but got same '%s'", newInputPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestApiMock(t *testing.T) {
|
||||
mock := &ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return nil
|
||||
},
|
||||
ExtensionsMock: func() []string {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
err := mock.Pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ApiMock.Pdf, but got: %v", err)
|
||||
}
|
||||
|
||||
ext := mock.Extensions()
|
||||
if ext != nil {
|
||||
t.Errorf("expected nil result from ApiMock.Extensions, but got: %v", ext)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderMock(t *testing.T) {
|
||||
mock := &ProviderMock{
|
||||
LibreOfficeMock: func() (Uno, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
_, err := mock.LibreOffice()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error from ProviderMock.LibreOffice, but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficeMock(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
mock *libreOfficeMock
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "success",
|
||||
mock: &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "ErrCoreDumped (first call)",
|
||||
mock: &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "ErrCoreDumped (second call)",
|
||||
mock: func() *libreOfficeMock {
|
||||
m := &libreOfficeMock{
|
||||
pdfMock: func(ctx context.Context, logger *zap.Logger, input, outputPath string, options Options) error {
|
||||
return ErrCoreDumped
|
||||
},
|
||||
}
|
||||
m.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
return m
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
err := tc.mock.pdf(context.Background(), zap.NewNop(), "", "", Options{})
|
||||
|
||||
if !tc.expectError && err != nil {
|
||||
t.Fatalf("expected no error from libreOfficeMock.pdf but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectError && err == nil {
|
||||
t.Fatal("expected error from libreOfficeMock.pdf but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package libreoffice
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
||||
)
|
||||
|
||||
func TestLibreOffice_Descriptor(t *testing.T) {
|
||||
descriptor := new(LibreOffice).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(LibreOffice))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOffice_Provision(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no LibreOffice API provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOffice).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no LibreOffice API from LibreOffice API provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
libreofficeapi.ProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOffice).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
libreofficeapi.ProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) {
|
||||
return new(libreofficeapi.ApiMock), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOffice).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine from PDF engine provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
libreofficeapi.ProviderMock
|
||||
gotenberg.PdfEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) {
|
||||
return new(libreofficeapi.ApiMock), nil
|
||||
}
|
||||
mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOffice).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
libreofficeapi.ProviderMock
|
||||
gotenberg.PdfEngineProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.LibreOfficeMock = func() (libreofficeapi.Uno, error) {
|
||||
return new(libreofficeapi.ApiMock), nil
|
||||
}
|
||||
mod.PdfEngineMock = func() (gotenberg.PdfEngine, error) {
|
||||
return new(gotenberg.PdfEngineMock), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOffice).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(LibreOffice)
|
||||
err := mod.Provision(tc.ctx)
|
||||
|
||||
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 TestLibreOffice_Routes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
expectRoutes int
|
||||
disableRoutes bool
|
||||
}{
|
||||
{
|
||||
scenario: "routes not disabled",
|
||||
expectRoutes: 1,
|
||||
disableRoutes: false,
|
||||
},
|
||||
{
|
||||
scenario: "routes disabled",
|
||||
expectRoutes: 0,
|
||||
disableRoutes: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(LibreOffice)
|
||||
mod.disableRoutes = tc.disableRoutes
|
||||
|
||||
routes, err := mod.Routes()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectRoutes != len(routes) {
|
||||
t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
package pdfengine
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
||||
)
|
||||
|
||||
func TestLibreOfficePdfEngine_Descriptor(t *testing.T) {
|
||||
descriptor := new(LibreOfficePdfEngine).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(LibreOfficePdfEngine))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_Provider(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no LibreOffice API provider",
|
||||
ctx: gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{},
|
||||
),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no API from LibreOffice API provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
provider := &struct {
|
||||
gotenberg.ModuleMock
|
||||
api.ProviderMock
|
||||
}{}
|
||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||
return provider
|
||||
}}
|
||||
}
|
||||
provider.LibreOfficeMock = func() (api.Uno, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
provider.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
provider := &struct {
|
||||
gotenberg.ModuleMock
|
||||
api.ProviderMock
|
||||
}{}
|
||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||
return provider
|
||||
}}
|
||||
}
|
||||
provider.LibreOfficeMock = func() (api.Uno, error) {
|
||||
return new(api.ApiMock), nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(LibreOfficePdfEngine).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
provider.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(LibreOfficePdfEngine)
|
||||
err := engine.Provision(tc.ctx)
|
||||
|
||||
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 TestLibreOfficePdfEngine_Merge(t *testing.T) {
|
||||
engine := new(LibreOfficePdfEngine)
|
||||
err := engine.Merge(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
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_Flatten(t *testing.T) {
|
||||
engine := new(LibreOfficePdfEngine)
|
||||
err := engine.Flatten(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_Convert(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
api api.Uno
|
||||
expectError bool
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
scenario: "convert success",
|
||||
api: &api.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "ErrInvalidPdfFormats",
|
||||
api: &api.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error {
|
||||
return api.ErrInvalidPdfFormats
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectedError: gotenberg.ErrPdfFormatNotSupported,
|
||||
},
|
||||
{
|
||||
scenario: "convert fail",
|
||||
api: &api.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options api.Options) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := &LibreOfficePdfEngine{unoApi: tc.api}
|
||||
err := engine.Convert(context.Background(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) {
|
||||
engine := new(LibreOfficePdfEngine)
|
||||
_, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) {
|
||||
engine := new(LibreOfficePdfEngine)
|
||||
err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package libreoffice
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -129,15 +128,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap
|
||||
}).
|
||||
Bool("nativePdfFormats", &nativePdfFormats, true).
|
||||
Bool("merge", &merge, false).
|
||||
Custom("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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}).
|
||||
Bool("flatten", &flatten, false).
|
||||
Validate()
|
||||
if err != nil {
|
||||
|
||||
@@ -1,817 +0,0 @@
|
||||
package libreoffice
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||
libreofficeapi "github.com/gotenberg/gotenberg/v8/pkg/modules/libreoffice/api"
|
||||
)
|
||||
|
||||
func TestConvertRoute(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *api.ContextMock
|
||||
libreOffice libreofficeapi.Uno
|
||||
engine gotenberg.PdfEngine
|
||||
expectOptions libreofficeapi.Options
|
||||
expectError bool
|
||||
expectHttpError bool
|
||||
expectHttpStatus int
|
||||
expectOutputPathsCount int
|
||||
expectOutputPaths []string
|
||||
}{
|
||||
{
|
||||
scenario: "missing at least one mandatory file",
|
||||
ctx: &api.ContextMock{Context: new(api.Context)},
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid quality form field (not an integer)",
|
||||
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{
|
||||
"quality": {
|
||||
"foo",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid quality form field (< 1)",
|
||||
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{
|
||||
"quality": {
|
||||
"0",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid quality form field (> 100)",
|
||||
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{
|
||||
"quality": {
|
||||
"101",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid maxImageResolution form field (not an integer)",
|
||||
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{
|
||||
"maxImageResolution": {
|
||||
"foo",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid maxImageResolution form field (not in range)",
|
||||
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{
|
||||
"maxImageResolution": {
|
||||
"1",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid metadata form field",
|
||||
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{
|
||||
"metadata": {
|
||||
"foo",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
}},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "ErrPdfFormatNotSupported (nativePdfFormats)",
|
||||
ctx: func() *api.ContextMock {
|
||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||
ctx.SetFiles(map[string]string{
|
||||
"document.docx": "/document.docx",
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||
return libreofficeapi.ErrInvalidPdfFormats
|
||||
},
|
||||
ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "ErrUnoException",
|
||||
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{
|
||||
"nativePageRanges": {
|
||||
"foo",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||
return libreofficeapi.ErrUnoException
|
||||
},
|
||||
ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "ErrRuntimeException",
|
||||
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{
|
||||
"password": {
|
||||
"invalid",
|
||||
},
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||
return libreofficeapi.ErrRuntimeException
|
||||
},
|
||||
ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "error from LibreOffice",
|
||||
ctx: func() *api.ContextMock {
|
||||
ctx := &api.ContextMock{Context: new(api.Context)}
|
||||
ctx.SetFiles(map[string]string{
|
||||
"document.docx": "/document.docx",
|
||||
})
|
||||
return ctx
|
||||
}(),
|
||||
libreOffice: &libreofficeapi.ApiMock{
|
||||
PdfMock: func(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options libreofficeapi.Options) error {
|
||||
return errors.New("foo")
|
||||
},
|
||||
ExtensionsMock: func() []string {
|
||||
return []string{".docx"}
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: false,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "PDF engine merge error",
|
||||
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",
|
||||
},
|
||||
})
|
||||
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 errors.New("foo")
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: false,
|
||||
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",
|
||||
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{
|
||||
"pdfa": {
|
||||
gotenberg.PdfA1b,
|
||||
},
|
||||
"nativePdfFormats": {
|
||||
"false",
|
||||
},
|
||||
})
|
||||
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{
|
||||
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: "PDF engine write metadata 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{
|
||||
"metadata": {
|
||||
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||
},
|
||||
})
|
||||
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{
|
||||
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: "PDF engine flatten 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{
|
||||
"flatten": {
|
||||
"true",
|
||||
},
|
||||
})
|
||||
ctx.SetCancelled(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{
|
||||
FlattenMock: func(ctx context.Context, logger *zap.Logger, 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{
|
||||
"document.docx": "/document.docx",
|
||||
})
|
||||
ctx.SetCancelled(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"}
|
||||
},
|
||||
},
|
||||
expectError: true,
|
||||
expectHttpError: false,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "success (single file)",
|
||||
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{
|
||||
"quality": {
|
||||
"100",
|
||||
},
|
||||
"maxImageResolution": {
|
||||
"1200",
|
||||
},
|
||||
"merge": {
|
||||
"true",
|
||||
},
|
||||
"pdfa": {
|
||||
gotenberg.PdfA1b,
|
||||
},
|
||||
"pdfua": {
|
||||
"true",
|
||||
},
|
||||
"nativePdfFormats": {
|
||||
"false",
|
||||
},
|
||||
"metadata": {
|
||||
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||
},
|
||||
"flatten": {
|
||||
"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{
|
||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return nil
|
||||
},
|
||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||
return nil
|
||||
},
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
expectHttpError: false,
|
||||
expectOutputPathsCount: 1,
|
||||
},
|
||||
{
|
||||
scenario: "success (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.SetValues(map[string][]string{
|
||||
"pdfa": {
|
||||
gotenberg.PdfA1b,
|
||||
},
|
||||
"pdfua": {
|
||||
"true",
|
||||
},
|
||||
"nativePdfFormats": {
|
||||
"false",
|
||||
},
|
||||
"metadata": {
|
||||
"{\"Creator\": \"foo\", \"Producer\": \"bar\" }",
|
||||
},
|
||||
"flatten": {
|
||||
"true",
|
||||
},
|
||||
})
|
||||
ctx.SetPathRename(&gotenberg.PathRenameMock{RenameMock: func(oldpath, newpath string) error {
|
||||
return nil
|
||||
}})
|
||||
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"}
|
||||
},
|
||||
},
|
||||
engine: &gotenberg.PdfEngineMock{
|
||||
ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error {
|
||||
return nil
|
||||
},
|
||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||
return nil
|
||||
},
|
||||
WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
FlattenMock: func(ctx context.Context, logger *zap.Logger, inputPath string) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
expectHttpError: false,
|
||||
expectOutputPathsCount: 3,
|
||||
expectOutputPaths: []string{"/document.docx.pdf", "/document2.docx.pdf", "/document2.doc.pdf"},
|
||||
},
|
||||
{
|
||||
scenario: "success with native PDF/A & PDF/UA",
|
||||
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{
|
||||
"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"}
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
expectHttpError: false,
|
||||
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) {
|
||||
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 := convertRoute(tc.libreOffice, 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())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,329 +0,0 @@
|
||||
package pdfcpu
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestPdfCpu_Descriptor(t *testing.T) {
|
||||
descriptor := new(PdfCpu).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(PdfCpu))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfCpu_Provision(t *testing.T) {
|
||||
engine := new(PdfCpu)
|
||||
ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil)
|
||||
|
||||
err := engine.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfCpu_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty bin path",
|
||||
binPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "bin path does not exist",
|
||||
binPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("PDFTK_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(PdfCpu)
|
||||
engine.binPath = tc.binPath
|
||||
err := engine.Validate()
|
||||
|
||||
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 TestPdfCpu_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *PdfCpu
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version (command error)",
|
||||
engine: &PdfCpu{
|
||||
binPath: "foo",
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "cannot determine version (no pdfcpu)",
|
||||
engine: &PdfCpu{
|
||||
binPath: "echo",
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": "Unable to determine pdfcpu version",
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
engine: &PdfCpu{
|
||||
binPath: "pdfcpu",
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": "Unable to determine pdfcpu version",
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.engine.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfCpu_Merge(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx context.Context
|
||||
inputPaths []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"foo",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "single file success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "many files success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
"/tests/test/testdata/pdfengines/sample2.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf")
|
||||
|
||||
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 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/sample4.pdf",
|
||||
expectError: false,
|
||||
expectOutputPathsCount: 20,
|
||||
},
|
||||
{
|
||||
scenario: "success (pages)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1"},
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample4.pdf",
|
||||
expectError: false,
|
||||
expectOutputPathsCount: 1,
|
||||
},
|
||||
{
|
||||
scenario: "success (pages & unify)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample4.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_Flatten(t *testing.T) {
|
||||
mod := new(PdfCpu)
|
||||
err := mod.Flatten(context.TODO(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfCpu_Convert(t *testing.T) {
|
||||
mod := new(PdfCpu)
|
||||
err := mod.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) {
|
||||
engine := new(PdfCpu)
|
||||
_, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) {
|
||||
engine := new(PdfCpu)
|
||||
err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
package pdfengines
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestPdfEngines_Descriptor(t *testing.T) {
|
||||
descriptor := new(PdfEngines).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(PdfEngines))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfEngines_Provision(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectedMergePdfEngines []string
|
||||
expectedSplitPdfEngines []string
|
||||
expectedFlattenPdfEngines []string
|
||||
expectedConvertPdfEngines []string
|
||||
expectedReadMetadataPdfEngines []string
|
||||
expectedWriteMetadataPdfEngines []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "no selection from user",
|
||||
ctx: func() *gotenberg.Context {
|
||||
provider := &struct {
|
||||
gotenberg.ModuleMock
|
||||
}{}
|
||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||
return provider
|
||||
}}
|
||||
}
|
||||
|
||||
engine := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine }}
|
||||
}
|
||||
engine.ValidateMock = func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(PdfEngines).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
provider.Descriptor(),
|
||||
engine.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectedMergePdfEngines: []string{"qpdf", "pdfcpu", "pdftk"},
|
||||
expectedSplitPdfEngines: []string{"pdfcpu", "qpdf", "pdftk"},
|
||||
expectedFlattenPdfEngines: []string{"qpdf"},
|
||||
expectedConvertPdfEngines: []string{"libreoffice-pdfengine"},
|
||||
expectedReadMetadataPdfEngines: []string{"exiftool"},
|
||||
expectedWriteMetadataPdfEngines: []string{"exiftool"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "selection from user",
|
||||
ctx: func() *gotenberg.Context {
|
||||
provider := &struct {
|
||||
gotenberg.ModuleMock
|
||||
}{}
|
||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||
return provider
|
||||
}}
|
||||
}
|
||||
engine1 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine1.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "a", New: func() gotenberg.Module { return engine1 }}
|
||||
}
|
||||
engine1.ValidateMock = func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
engine2 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine2.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "b", New: func() gotenberg.Module { return engine2 }}
|
||||
}
|
||||
engine2.ValidateMock = func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
fs := new(PdfEngines).Descriptor().FlagSet
|
||||
err := fs.Parse([]string{"--pdfengines-merge-engines=b", "--pdfengines-split-engines=a", "--pdfengines-flatten-engines=c", "--pdfengines-convert-engines=b", "--pdfengines-read-metadata-engines=a", "--pdfengines-write-metadata-engines=a"})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: fs,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
provider.Descriptor(),
|
||||
engine1.Descriptor(),
|
||||
engine2.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
|
||||
expectedMergePdfEngines: []string{"b"},
|
||||
expectedSplitPdfEngines: []string{"a"},
|
||||
expectedFlattenPdfEngines: []string{"c"},
|
||||
expectedConvertPdfEngines: []string{"b"},
|
||||
expectedReadMetadataPdfEngines: []string{"a"},
|
||||
expectedWriteMetadataPdfEngines: []string{"a"},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "no valid PDF engine",
|
||||
ctx: func() *gotenberg.Context {
|
||||
provider := &struct {
|
||||
gotenberg.ModuleMock
|
||||
}{}
|
||||
provider.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module {
|
||||
return provider
|
||||
}}
|
||||
}
|
||||
engine := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine }}
|
||||
}
|
||||
engine.ValidateMock = func() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(PdfEngines).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
provider.Descriptor(),
|
||||
engine.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(PdfEngines)
|
||||
err := mod.Provision(tc.ctx)
|
||||
|
||||
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 len(tc.expectedMergePdfEngines) != len(mod.mergeNames) {
|
||||
t.Fatalf("expected %d merge names but got %d", len(tc.expectedMergePdfEngines), len(mod.mergeNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedFlattenPdfEngines) != len(mod.flattenNames) {
|
||||
t.Fatalf("expected %d flatten names but got %d", len(tc.expectedFlattenPdfEngines), len(mod.flattenNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedConvertPdfEngines) != len(mod.convertNames) {
|
||||
t.Fatalf("expected %d convert names but got %d", len(tc.expectedConvertPdfEngines), len(mod.convertNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedReadMetadataPdfEngines) != len(mod.readMetadataNames) {
|
||||
t.Fatalf("expected %d read metadata names but got %d", len(tc.expectedReadMetadataPdfEngines), len(mod.readMetadataNames))
|
||||
}
|
||||
|
||||
if len(tc.expectedWriteMetadataPdfEngines) != len(mod.writeMetadataNames) {
|
||||
t.Fatalf("expected %d write metadata names but got %d", len(tc.expectedWriteMetadataPdfEngines), len(mod.writeMetadataNames))
|
||||
}
|
||||
|
||||
for index, name := range mod.mergeNames {
|
||||
if name != tc.expectedMergePdfEngines[index] {
|
||||
t.Fatalf("expected merge name at index %d to be %s, but got: %s", index, name, tc.expectedMergePdfEngines[index])
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if name != tc.expectedConvertPdfEngines[index] {
|
||||
t.Fatalf("expected convert name at index %d to be %s, but got: %s", index, name, tc.expectedConvertPdfEngines[index])
|
||||
}
|
||||
}
|
||||
|
||||
for index, name := range mod.readMetadataNames {
|
||||
if name != tc.expectedReadMetadataPdfEngines[index] {
|
||||
t.Fatalf("expected read metadata name at index %d to be %s, but got: %s", index, name, tc.expectedReadMetadataPdfEngines[index])
|
||||
}
|
||||
}
|
||||
|
||||
for index, name := range mod.writeMetadataNames {
|
||||
if name != tc.expectedWriteMetadataPdfEngines[index] {
|
||||
t.Fatalf("expected write metadat name at index %d to be %s, but got: %s", index, name, tc.expectedWriteMetadataPdfEngines[index])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfEngines_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
names []string
|
||||
engines []gotenberg.PdfEngine
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "existing PDF engine",
|
||||
names: []string{"foo"},
|
||||
engines: func() []gotenberg.PdfEngine {
|
||||
engine := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine }}
|
||||
}
|
||||
|
||||
return []gotenberg.PdfEngine{
|
||||
engine,
|
||||
}
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "non-existing bar PDF engine",
|
||||
names: []string{"foo", "bar", "baz"},
|
||||
engines: func() []gotenberg.PdfEngine {
|
||||
engine1 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine1.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine1 }}
|
||||
}
|
||||
|
||||
engine2 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine2.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "baz", New: func() gotenberg.Module { return engine2 }}
|
||||
}
|
||||
|
||||
return []gotenberg.PdfEngine{
|
||||
engine1,
|
||||
engine2,
|
||||
}
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "no PDF engine",
|
||||
expectError: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := PdfEngines{
|
||||
mergeNames: tc.names,
|
||||
convertNames: tc.names,
|
||||
readMetadataNames: tc.names,
|
||||
writeMetadataNames: tc.names,
|
||||
engines: tc.engines,
|
||||
}
|
||||
|
||||
err := mod.Validate()
|
||||
|
||||
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 TestPdfEngines_SystemMessages(t *testing.T) {
|
||||
mod := new(PdfEngines)
|
||||
mod.mergeNames = []string{"foo", "bar"}
|
||||
mod.splitNames = []string{"foo", "bar"}
|
||||
mod.convertNames = []string{"foo", "bar"}
|
||||
mod.readMetadataNames = []string{"foo", "bar"}
|
||||
mod.writeMetadataNames = []string{"foo", "bar"}
|
||||
|
||||
expectedMessages := 6
|
||||
messages := mod.SystemMessages()
|
||||
if len(messages) != expectedMessages {
|
||||
t.Errorf("expected %d message(s), but got %d", expectedMessages, len(messages))
|
||||
}
|
||||
|
||||
expect := []string{
|
||||
fmt.Sprintf("merge engines - %s", strings.Join(mod.mergeNames[:], " ")),
|
||||
fmt.Sprintf("split engines - %s", strings.Join(mod.splitNames[:], " ")),
|
||||
fmt.Sprintf("flatten engines - %s", strings.Join(mod.flattenNames[:], " ")),
|
||||
fmt.Sprintf("convert engines - %s", strings.Join(mod.convertNames[:], " ")),
|
||||
fmt.Sprintf("read metadata engines - %s", strings.Join(mod.readMetadataNames[:], " ")),
|
||||
fmt.Sprintf("write metadata engines - %s", strings.Join(mod.writeMetadataNames[:], " ")),
|
||||
}
|
||||
|
||||
for i, message := range messages {
|
||||
if message != expect[i] {
|
||||
t.Errorf("expected message at index %d to be %s, but got %s", i, message, expect[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfEngines_PdfEngine(t *testing.T) {
|
||||
mod := PdfEngines{
|
||||
mergeNames: []string{"foo", "bar"},
|
||||
splitNames: []string{"foo", "bar"},
|
||||
convertNames: []string{"foo", "bar"},
|
||||
readMetadataNames: []string{"foo", "bar"},
|
||||
writeMetadataNames: []string{"foo", "bar"},
|
||||
engines: func() []gotenberg.PdfEngine {
|
||||
engine1 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine1.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return engine1 }}
|
||||
}
|
||||
|
||||
engine2 := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.PdfEngineMock
|
||||
}{}
|
||||
engine2.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return engine2 }}
|
||||
}
|
||||
|
||||
return []gotenberg.PdfEngine{
|
||||
engine1,
|
||||
engine2,
|
||||
}
|
||||
}(),
|
||||
}
|
||||
|
||||
_, err := mod.PdfEngine()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfEngines_Routes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
expectRoutes int
|
||||
disableRoutes bool
|
||||
}{
|
||||
{
|
||||
scenario: "routes not disabled",
|
||||
expectRoutes: 6,
|
||||
disableRoutes: false,
|
||||
},
|
||||
{
|
||||
scenario: "routes disabled",
|
||||
expectRoutes: 0,
|
||||
disableRoutes: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(PdfEngines)
|
||||
mod.disableRoutes = tc.disableRoutes
|
||||
|
||||
routes, err := mod.Routes()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectRoutes != len(routes) {
|
||||
t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,313 +0,0 @@
|
||||
package pdftk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestPdfTk_Descriptor(t *testing.T) {
|
||||
descriptor := new(PdfTk).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(PdfTk))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfTk_Provision(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil)
|
||||
|
||||
err := engine.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfTk_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty bin path",
|
||||
binPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "bin path does not exist",
|
||||
binPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("PDFTK_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
engine.binPath = tc.binPath
|
||||
err := engine.Validate()
|
||||
|
||||
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 TestPdfTk_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *PdfTk
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version",
|
||||
engine: &PdfTk{
|
||||
binPath: "foo",
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
engine: &PdfTk{
|
||||
binPath: "echo",
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": `exec: "echo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.engine.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfTk_Merge(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx context.Context
|
||||
inputPaths []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"foo",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "single file success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "many files success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
"/tests/test/testdata/pdfengines/sample2.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf")
|
||||
|
||||
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 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: "ErrPdfSplitModeNotSupported (no unify with pages)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false},
|
||||
expectError: true,
|
||||
expectedError: gotenberg.ErrPdfSplitModeNotSupported,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
expectError: true,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
inputPath: "",
|
||||
expectError: true,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "success (pages & unify)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
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_Flatten(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
err := engine.Flatten(context.TODO(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPdfTk_Convert(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
_, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) {
|
||||
engine := new(PdfTk)
|
||||
err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
@@ -1,360 +0,0 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestPrometheus_Descriptor(t *testing.T) {
|
||||
descriptor := new(Prometheus).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(Prometheus))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheus_Provision(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx *gotenberg.Context
|
||||
expectMetrics []gotenberg.Metric
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "disable collect",
|
||||
ctx: func() *gotenberg.Context {
|
||||
fs := new(Prometheus).Descriptor().FlagSet
|
||||
err := fs.Parse([]string{"--prometheus-disable-collect=true"})
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: fs,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
}(),
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "invalid metrics provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.MetricsProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.ValidateMock = func() error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
mod.MetricsMock = func() ([]gotenberg.Metric, error) {
|
||||
return nil, nil
|
||||
}
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Prometheus).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid metrics from metrics provider",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.MetricsProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.ValidateMock = func() error {
|
||||
return nil
|
||||
}
|
||||
mod.MetricsMock = func() ([]gotenberg.Metric, error) {
|
||||
return nil, errors.New("foo")
|
||||
}
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Prometheus).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "provision success",
|
||||
ctx: func() *gotenberg.Context {
|
||||
mod := &struct {
|
||||
gotenberg.ModuleMock
|
||||
gotenberg.ValidatorMock
|
||||
gotenberg.MetricsProviderMock
|
||||
}{}
|
||||
mod.DescriptorMock = func() gotenberg.ModuleDescriptor {
|
||||
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
||||
}
|
||||
mod.ValidateMock = func() error {
|
||||
return nil
|
||||
}
|
||||
mod.MetricsMock = func() ([]gotenberg.Metric, error) {
|
||||
return []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "Bar.",
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Prometheus).Descriptor().FlagSet,
|
||||
},
|
||||
[]gotenberg.ModuleDescriptor{
|
||||
mod.Descriptor(),
|
||||
},
|
||||
)
|
||||
}(),
|
||||
expectMetrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Description: "Bar.",
|
||||
},
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := new(Prometheus)
|
||||
err := mod.Provision(tc.ctx)
|
||||
|
||||
if !reflect.DeepEqual(mod.metrics, tc.expectMetrics) {
|
||||
t.Fatalf("expected metrics %+v, but got: %+v", tc.expectMetrics, mod.metrics)
|
||||
}
|
||||
|
||||
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 TestPrometheus_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
namespace string
|
||||
metrics []gotenberg.Metric
|
||||
disableCollect bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "collect disabled",
|
||||
namespace: "foo",
|
||||
disableCollect: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "empty namespace",
|
||||
namespace: "",
|
||||
disableCollect: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "empty metric name",
|
||||
namespace: "foo",
|
||||
metrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "",
|
||||
},
|
||||
},
|
||||
disableCollect: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "nil read metric method",
|
||||
namespace: "foo",
|
||||
metrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Read: nil,
|
||||
},
|
||||
},
|
||||
disableCollect: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "already registered metric",
|
||||
namespace: "foo",
|
||||
metrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Read: func() float64 {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "foo",
|
||||
Read: func() float64 {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
},
|
||||
disableCollect: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
namespace: "foo",
|
||||
metrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Read: func() float64 {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
Read: func() float64 {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
},
|
||||
disableCollect: false,
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := &Prometheus{
|
||||
namespace: tc.namespace,
|
||||
metrics: tc.metrics,
|
||||
disableCollect: tc.disableCollect,
|
||||
}
|
||||
err := mod.Validate()
|
||||
|
||||
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 TestPrometheus_Start(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
metrics []gotenberg.Metric
|
||||
disableCollect bool
|
||||
}{
|
||||
{
|
||||
scenario: "collect disabled",
|
||||
disableCollect: true,
|
||||
},
|
||||
{
|
||||
scenario: "start success",
|
||||
metrics: []gotenberg.Metric{
|
||||
{
|
||||
Name: "foo",
|
||||
Read: func() float64 {
|
||||
return 0
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := &Prometheus{
|
||||
namespace: "foo",
|
||||
interval: time.Duration(1) * time.Second,
|
||||
metrics: tc.metrics,
|
||||
disableCollect: tc.disableCollect,
|
||||
registry: prometheus.NewRegistry(),
|
||||
}
|
||||
|
||||
err := mod.Start()
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheus_StartupMessage(t *testing.T) {
|
||||
mod := new(Prometheus)
|
||||
|
||||
mod.disableCollect = true
|
||||
disableCollectMsg := mod.StartupMessage()
|
||||
|
||||
mod.disableCollect = false
|
||||
noDisableCollectMsg := mod.StartupMessage()
|
||||
|
||||
if disableCollectMsg == noDisableCollectMsg {
|
||||
t.Errorf("expected differrent startup messages if collect is disabled or not, but got '%s'", disableCollectMsg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheus_Stop(t *testing.T) {
|
||||
err := new(Prometheus).Stop(nil)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrometheus_Routes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
disableCollect bool
|
||||
expectRoutes int
|
||||
}{
|
||||
{
|
||||
scenario: "collect disabled",
|
||||
disableCollect: true,
|
||||
expectRoutes: 0,
|
||||
},
|
||||
{
|
||||
scenario: "routes not disabled",
|
||||
disableCollect: false,
|
||||
expectRoutes: 1,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
mod := &Prometheus{
|
||||
disableCollect: tc.disableCollect,
|
||||
registry: prometheus.NewRegistry(),
|
||||
}
|
||||
|
||||
routes, err := mod.Routes()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectRoutes != len(routes) {
|
||||
t.Errorf("expected %d routes but got %d", tc.expectRoutes, len(routes))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
package qpdf
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestQPdf_Descriptor(t *testing.T) {
|
||||
descriptor := new(QPdf).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(QPdf))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQPdf_Provision(t *testing.T) {
|
||||
engine := new(QPdf)
|
||||
ctx := gotenberg.NewContext(gotenberg.ParsedFlags{}, nil)
|
||||
|
||||
err := engine.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQPdf_Validate(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
binPath string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "empty bin path",
|
||||
binPath: "",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "bin path does not exist",
|
||||
binPath: "/foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "validate success",
|
||||
binPath: os.Getenv("QPDF_BIN_PATH"),
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
engine := new(QPdf)
|
||||
engine.binPath = tc.binPath
|
||||
err := engine.Validate()
|
||||
|
||||
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 TestQPdf_Debug(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
engine *QPdf
|
||||
expect map[string]interface{}
|
||||
doNotExpect map[string]interface{}
|
||||
}{
|
||||
{
|
||||
scenario: "cannot determine version",
|
||||
engine: &QPdf{
|
||||
binPath: "foo",
|
||||
},
|
||||
expect: map[string]interface{}{
|
||||
"version": `exec: "foo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
engine: &QPdf{
|
||||
binPath: "echo",
|
||||
},
|
||||
doNotExpect: map[string]interface{}{
|
||||
"version": `exec: "echo": executable file not found in $PATH`,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
d := tc.engine.Debug()
|
||||
|
||||
if tc.expect != nil {
|
||||
if !reflect.DeepEqual(d, tc.expect) {
|
||||
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||
}
|
||||
}
|
||||
|
||||
if tc.doNotExpect != nil {
|
||||
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||
t.Errorf("did not expect '%v'", d)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestQPdf_Merge(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx context.Context
|
||||
inputPaths []string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"foo",
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "single file success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "many files success",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
"/tests/test/testdata/pdfengines/sample2.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success even with warnings",
|
||||
ctx: context.TODO(),
|
||||
inputPaths: []string{
|
||||
"/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
"/tests/test/testdata/pdfengines/sample5.pdf",
|
||||
},
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
}()
|
||||
|
||||
err = engine.Merge(tc.ctx, zap.NewNop(), tc.inputPaths, outputDir+"/foo.pdf")
|
||||
|
||||
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 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: "ErrPdfSplitModeNotSupported (no unify with pages)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1", Unify: false},
|
||||
expectError: true,
|
||||
expectedError: gotenberg.ErrPdfSplitModeNotSupported,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
expectError: true,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
inputPath: "",
|
||||
expectError: true,
|
||||
expectOutputPathsCount: 0,
|
||||
},
|
||||
{
|
||||
scenario: "success (pages & unify)",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample1.pdf",
|
||||
expectError: false,
|
||||
expectOutputPathsCount: 1,
|
||||
},
|
||||
{
|
||||
scenario: "success even with warnings",
|
||||
ctx: context.TODO(),
|
||||
mode: gotenberg.SplitMode{Mode: gotenberg.SplitModePages, Span: "1-2", Unify: true},
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample5.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_Flatten(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
ctx context.Context
|
||||
inputPath string
|
||||
createCopy bool
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
scenario: "invalid context",
|
||||
ctx: nil,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid input path",
|
||||
ctx: context.TODO(),
|
||||
inputPath: "foo.pdf",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
ctx: context.TODO(),
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample3.pdf",
|
||||
createCopy: true,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
scenario: "success even with warnings",
|
||||
ctx: context.TODO(),
|
||||
inputPath: "/tests/test/testdata/pdfengines/sample5.pdf",
|
||||
createCopy: true,
|
||||
expectError: false,
|
||||
},
|
||||
} {
|
||||
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)
|
||||
}
|
||||
|
||||
var destinationPath string
|
||||
if tc.createCopy {
|
||||
fs := gotenberg.NewFileSystem(new(gotenberg.OsMkdirAll))
|
||||
outputDir, err := fs.MkdirAll()
|
||||
if err != nil {
|
||||
t.Fatalf("expected error no 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)
|
||||
}
|
||||
}()
|
||||
|
||||
destinationPath = fmt.Sprintf("%s/copy_temp.pdf", outputDir)
|
||||
source, err := os.Open(tc.inputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open source file: %v", err)
|
||||
}
|
||||
|
||||
defer func(source *os.File) {
|
||||
err := source.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("close file: %v", err)
|
||||
}
|
||||
}(source)
|
||||
|
||||
destination, err := os.Create(destinationPath)
|
||||
if err != nil {
|
||||
t.Fatalf("create destination file: %v", err)
|
||||
}
|
||||
|
||||
defer func(destination *os.File) {
|
||||
err := destination.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("close file: %v", err)
|
||||
}
|
||||
}(destination)
|
||||
|
||||
_, err = io.Copy(destination, source)
|
||||
if err != nil {
|
||||
t.Fatalf("copy source into destination: %v", err)
|
||||
}
|
||||
} else {
|
||||
destinationPath = tc.inputPath
|
||||
}
|
||||
|
||||
err = engine.Flatten(tc.ctx, zap.NewNop(), destinationPath)
|
||||
|
||||
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 TestQPdf_Convert(t *testing.T) {
|
||||
engine := new(QPdf)
|
||||
err := engine.Convert(context.TODO(), zap.NewNop(), gotenberg.PdfFormats{}, "", "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_ReadMetadata(t *testing.T) {
|
||||
engine := new(QPdf)
|
||||
_, err := engine.ReadMetadata(context.Background(), zap.NewNop(), "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLibreOfficePdfEngine_WriteMetadata(t *testing.T) {
|
||||
engine := new(QPdf)
|
||||
err := engine.WriteMetadata(context.Background(), zap.NewNop(), nil, "")
|
||||
|
||||
if !errors.Is(err, gotenberg.ErrPdfEngineMethodNotSupported) {
|
||||
t.Errorf("expected error %v, but got: %v", gotenberg.ErrPdfEngineMethodNotSupported, err)
|
||||
}
|
||||
}
|
||||
@@ -1,582 +0,0 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"github.com/labstack/echo/v4"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/modules/api"
|
||||
)
|
||||
|
||||
func TestWebhookMiddlewareGuards(t *testing.T) {
|
||||
buildMultipartFormDataRequest := func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err := writer.WriteField("foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
buildWebhookModule := func() *Webhook {
|
||||
return &Webhook{
|
||||
allowList: regexp2.MustCompile("", 0),
|
||||
denyList: regexp2.MustCompile("", 0),
|
||||
errorAllowList: regexp2.MustCompile("", 0),
|
||||
errorDenyList: regexp2.MustCompile("", 0),
|
||||
maxRetry: 0,
|
||||
retryMinWait: 0,
|
||||
retryMaxWait: 0,
|
||||
disable: false,
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
request *http.Request
|
||||
mod *Webhook
|
||||
next echo.HandlerFunc
|
||||
noDeadline bool
|
||||
expectError bool
|
||||
expectHttpError bool
|
||||
expectHttpStatus int
|
||||
}{
|
||||
{
|
||||
scenario: "no webhook URL, skip middleware",
|
||||
request: buildMultipartFormDataRequest(),
|
||||
mod: buildWebhookModule(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
noDeadline: false,
|
||||
expectError: false,
|
||||
expectHttpError: false,
|
||||
},
|
||||
{
|
||||
scenario: "no webhook error URL",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "context has no deadline",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: true,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "webhook URL is not allowed",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: func() *Webhook {
|
||||
mod := buildWebhookModule()
|
||||
mod.allowList = regexp2.MustCompile("bar", 0)
|
||||
return mod
|
||||
}(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "webhook URL is denied",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: func() *Webhook {
|
||||
mod := buildWebhookModule()
|
||||
mod.denyList = regexp2.MustCompile("foo", 0)
|
||||
return mod
|
||||
}(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "webhook error URL is not allowed",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: func() *Webhook {
|
||||
mod := buildWebhookModule()
|
||||
mod.errorAllowList = regexp2.MustCompile("foo", 0)
|
||||
return mod
|
||||
}(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "webhook error URL is denied",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: func() *Webhook {
|
||||
mod := buildWebhookModule()
|
||||
mod.errorDenyList = regexp2.MustCompile("bar", 0)
|
||||
return mod
|
||||
}(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
scenario: "invalid webhook method (GET)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Method", http.MethodGet)
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid webhook error method (GET)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "valid webhook method (POST) but invalid webhook error method (GET)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPost)
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "valid webhook method (PATH) but invalid webhook error method (GET)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPatch)
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "valid webhook method (PUT) but invalid webhook error method (GET)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Method", http.MethodPut)
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Method", http.MethodGet)
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
scenario: "invalid webhook extra HTTP headers",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Webhook-Url", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Error-Url", "bar")
|
||||
req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", "foo")
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
noDeadline: false,
|
||||
expectError: true,
|
||||
expectHttpError: true,
|
||||
expectHttpStatus: http.StatusBadRequest,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.scenario, func(t *testing.T) {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(tc.request, httptest.NewRecorder())
|
||||
|
||||
if tc.noDeadline {
|
||||
ctx := &api.ContextMock{Context: &api.Context{Context: context.Background()}}
|
||||
ctx.SetEchoContext(c)
|
||||
c.Set("context", ctx.Context)
|
||||
c.Set("cancel", func() context.CancelFunc {
|
||||
return nil
|
||||
}())
|
||||
} else {
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
|
||||
ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
|
||||
ctx.SetEchoContext(c)
|
||||
c.Set("context", ctx.Context)
|
||||
c.Set("cancel", cancel)
|
||||
}
|
||||
|
||||
err := webhookMiddleware(tc.mod).Handler(tc.next)(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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookMiddlewareAsynchronousProcess(t *testing.T) {
|
||||
buildMultipartFormDataRequest := func() *http.Request {
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
defer func() {
|
||||
err := writer.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err := writer.WriteField("foo", "foo")
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", body)
|
||||
req.Header.Set(echo.HeaderContentType, writer.FormDataContentType())
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
buildWebhookModule := func() *Webhook {
|
||||
return &Webhook{
|
||||
allowList: regexp2.MustCompile("", 0),
|
||||
denyList: regexp2.MustCompile("", 0),
|
||||
errorAllowList: regexp2.MustCompile("", 0),
|
||||
errorDenyList: regexp2.MustCompile("", 0),
|
||||
maxRetry: 0,
|
||||
retryMinWait: 0,
|
||||
retryMaxWait: 0,
|
||||
clientTimeout: time.Duration(30) * time.Second,
|
||||
disable: false,
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
request *http.Request
|
||||
mod *Webhook
|
||||
next echo.HandlerFunc
|
||||
expectWebhookContentType string
|
||||
expectWebhookMethod string
|
||||
expectWebhookExtraHttpHeaders map[string]string
|
||||
expectWebhookFilename string
|
||||
expectWebhookErrorStatus int
|
||||
expectWebhookErrorMessage string
|
||||
returnedError *echo.HTTPError
|
||||
}{
|
||||
{
|
||||
scenario: "next handler return an error",
|
||||
request: buildMultipartFormDataRequest(),
|
||||
mod: buildWebhookModule(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return errors.New("foo")
|
||||
}
|
||||
}(),
|
||||
expectWebhookContentType: echo.MIMEApplicationJSON,
|
||||
expectWebhookMethod: http.MethodPost,
|
||||
expectWebhookErrorStatus: http.StatusInternalServerError,
|
||||
expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError),
|
||||
},
|
||||
{
|
||||
scenario: "next handler return an HTTP error",
|
||||
request: buildMultipartFormDataRequest(),
|
||||
mod: buildWebhookModule(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return api.NewSentinelHttpError(http.StatusBadRequest, http.StatusText(http.StatusBadRequest))
|
||||
}
|
||||
}(),
|
||||
expectWebhookContentType: echo.MIMEApplicationJSON,
|
||||
expectWebhookMethod: http.MethodPost,
|
||||
expectWebhookErrorStatus: http.StatusBadRequest,
|
||||
expectWebhookErrorMessage: http.StatusText(http.StatusBadRequest),
|
||||
},
|
||||
{
|
||||
scenario: "success",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Output-Filename", "foo")
|
||||
req.Header.Set("Gotenberg-Webhook-Extra-Http-Headers", `{ "foo": "bar" }`)
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
return ctx.AddOutputPaths("/tests/test/testdata/api/sample2.pdf")
|
||||
}
|
||||
}(),
|
||||
expectWebhookContentType: "application/pdf",
|
||||
expectWebhookMethod: http.MethodPost,
|
||||
expectWebhookFilename: "foo",
|
||||
expectWebhookExtraHttpHeaders: map[string]string{"foo": "bar"},
|
||||
},
|
||||
{
|
||||
scenario: "success (return an error)",
|
||||
request: func() *http.Request {
|
||||
req := buildMultipartFormDataRequest()
|
||||
req.Header.Set("Gotenberg-Output-Filename", "foo")
|
||||
return req
|
||||
}(),
|
||||
mod: buildWebhookModule(),
|
||||
next: func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := c.Get("context").(*api.Context)
|
||||
return ctx.AddOutputPaths("/tests/test/testdata/api/sample1.pdf")
|
||||
}
|
||||
}(),
|
||||
returnedError: echo.ErrInternalServerError,
|
||||
expectWebhookContentType: echo.MIMEApplicationJSON,
|
||||
expectWebhookMethod: http.MethodPost,
|
||||
expectWebhookErrorStatus: http.StatusInternalServerError,
|
||||
expectWebhookErrorMessage: http.StatusText(http.StatusInternalServerError),
|
||||
expectWebhookFilename: "foo",
|
||||
},
|
||||
} {
|
||||
func() {
|
||||
srv := echo.New()
|
||||
srv.HideBanner = true
|
||||
srv.HidePort = true
|
||||
|
||||
c := srv.NewContext(tc.request, httptest.NewRecorder())
|
||||
c.Set("logger", zap.NewNop())
|
||||
c.Set("traceHeader", "Gotenberg-Trace")
|
||||
c.Set("trace", "foo")
|
||||
c.Set("startTime", time.Now())
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), time.Duration(10)*time.Second)
|
||||
ctx := &api.ContextMock{Context: &api.Context{Context: timeoutCtx}}
|
||||
ctx.SetLogger(zap.NewNop())
|
||||
ctx.SetEchoContext(c)
|
||||
|
||||
c.Set("context", ctx.Context)
|
||||
c.Set("cancel", cancel)
|
||||
|
||||
webhook := echo.New()
|
||||
webhook.HideBanner = true
|
||||
webhook.HidePort = true
|
||||
webhookPort := rand.Intn(65535-1025+1) + 1025
|
||||
|
||||
c.Request().Header.Set("Gotenberg-Webhook-Url", fmt.Sprintf("http://localhost:%d/", webhookPort))
|
||||
c.Request().Header.Set("Gotenberg-Webhook-Error-Url", fmt.Sprintf("http://localhost:%d/", webhookPort))
|
||||
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
webhook.POST(
|
||||
"/",
|
||||
func() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
contentType := c.Request().Header.Get(echo.HeaderContentType)
|
||||
if contentType != tc.expectWebhookContentType {
|
||||
t.Errorf("expected '%s' '%s' but got '%s'", echo.HeaderContentType, tc.expectWebhookContentType, contentType)
|
||||
}
|
||||
|
||||
trace := c.Request().Header.Get("Gotenberg-Trace")
|
||||
if trace != "foo" {
|
||||
t.Errorf("expected '%s' '%s' but got '%s'", "Gotenberg-Trace", "foo", trace)
|
||||
}
|
||||
|
||||
method := c.Request().Method
|
||||
if method != tc.expectWebhookMethod {
|
||||
t.Errorf("expected HTTP method '%s' but got '%s'", tc.expectWebhookMethod, method)
|
||||
}
|
||||
|
||||
for key, expect := range tc.expectWebhookExtraHttpHeaders {
|
||||
actual := c.Request().Header.Get(key)
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' '%s' but got '%s'", key, expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
if contentType == echo.MIMEApplicationJSON {
|
||||
body, err := io.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return nil
|
||||
}
|
||||
|
||||
result := struct {
|
||||
Status int `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return nil
|
||||
}
|
||||
|
||||
if result.Status != tc.expectWebhookErrorStatus {
|
||||
t.Errorf("expected status %d from JSON but got %d", tc.expectWebhookErrorStatus, result.Status)
|
||||
}
|
||||
|
||||
if result.Message != tc.expectWebhookErrorMessage {
|
||||
t.Errorf("expected message '%s' from JSON but got '%s'", tc.expectWebhookErrorMessage, result.Message)
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
return nil
|
||||
}
|
||||
|
||||
contentLength := c.Request().Header.Get(echo.HeaderContentLength)
|
||||
if contentLength == "" {
|
||||
t.Errorf("expected non empty '%s'", echo.HeaderContentLength)
|
||||
}
|
||||
|
||||
contentDisposition := c.Request().Header.Get(echo.HeaderContentDisposition)
|
||||
if !strings.Contains(contentDisposition, tc.expectWebhookFilename) {
|
||||
t.Errorf("expected '%s' '%s' to contain '%s'", echo.HeaderContentDisposition, contentDisposition, tc.expectWebhookFilename)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(c.Request().Body)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return nil
|
||||
}
|
||||
|
||||
if body == nil || len(body) == 0 {
|
||||
t.Error("expected non nil body")
|
||||
}
|
||||
|
||||
errChan <- nil
|
||||
|
||||
if tc.returnedError != nil {
|
||||
return tc.returnedError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}(),
|
||||
)
|
||||
|
||||
go func() {
|
||||
err := webhook.Start(fmt.Sprintf(":%d", webhookPort))
|
||||
if !errors.Is(err, http.ErrServerClosed) {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
defer func() {
|
||||
err := webhook.Shutdown(context.TODO())
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
err := webhookMiddleware(tc.mod).Handler(tc.next)(c)
|
||||
if err != nil && !errors.Is(err, api.ErrAsyncProcess) {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
err = <-errChan
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gotenberg/gotenberg/v8/pkg/gotenberg"
|
||||
)
|
||||
|
||||
func TestWebhook_Descriptor(t *testing.T) {
|
||||
descriptor := new(Webhook).Descriptor()
|
||||
|
||||
actual := reflect.TypeOf(descriptor.New())
|
||||
expect := reflect.TypeOf(new(Webhook))
|
||||
|
||||
if actual != expect {
|
||||
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhook_Provision(t *testing.T) {
|
||||
mod := new(Webhook)
|
||||
ctx := gotenberg.NewContext(
|
||||
gotenberg.ParsedFlags{
|
||||
FlagSet: new(Webhook).Descriptor().FlagSet,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
|
||||
err := mod.Provision(ctx)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error but got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhook_Middlewares(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
scenario string
|
||||
disable bool
|
||||
expectMiddlewares int
|
||||
}{
|
||||
{
|
||||
scenario: "webhook disabled",
|
||||
disable: true,
|
||||
expectMiddlewares: 0,
|
||||
},
|
||||
{
|
||||
scenario: "webhook enabled",
|
||||
disable: false,
|
||||
expectMiddlewares: 1,
|
||||
},
|
||||
} {
|
||||
mod := new(Webhook)
|
||||
mod.disable = tc.disable
|
||||
|
||||
middlewares, err := mod.Middlewares()
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error but got: %v", err)
|
||||
}
|
||||
|
||||
if tc.expectMiddlewares != len(middlewares) {
|
||||
t.Errorf("expected %d middlewares but got %d", tc.expectMiddlewares, len(middlewares))
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user