From 42ee593708277e8ce849a32ceaf579b0f9129fd3 Mon Sep 17 00:00:00 2001 From: Julien Neuhart Date: Mon, 25 Nov 2024 09:28:42 +0100 Subject: [PATCH] chore(pdfengines): add stubs for common workflows --- pkg/modules/chromium/routes.go | 85 ++------ pkg/modules/chromium/routes_test.go | 119 ----------- pkg/modules/libreoffice/routes.go | 60 ++---- pkg/modules/pdfengines/routes.go | 217 +++++++++++--------- pkg/modules/pdfengines/routes_test.go | 282 ++++++++++++++++++++++++++ 5 files changed, 439 insertions(+), 324 deletions(-) diff --git a/pkg/modules/chromium/routes.go b/pkg/modules/chromium/routes.go index a806838c..2ae33b25 100644 --- a/pkg/modules/chromium/routes.go +++ b/pkg/modules/chromium/routes.go @@ -21,6 +21,7 @@ import ( "github.com/gotenberg/gotenberg/v8/pkg/gotenberg" "github.com/gotenberg/gotenberg/v8/pkg/modules/api" + "github.com/gotenberg/gotenberg/v8/pkg/modules/pdfengines" ) // FormDataChromiumOptions creates [Options] from the form data. Fallback to @@ -316,39 +317,6 @@ func FormDataChromiumScreenshotOptions(ctx *api.Context) (*api.FormData, Screens return form, screenshotOptions } -// FormDataChromiumPdfFormats creates [gotenberg.PdfFormats] from the form -// data. Fallback to default value if the considered key is not present. -func FormDataChromiumPdfFormats(form *api.FormData) gotenberg.PdfFormats { - var ( - pdfa string - pdfua bool - ) - - form. - String("pdfa", &pdfa, ""). - Bool("pdfua", &pdfua, false) - - return gotenberg.PdfFormats{ - PdfA: pdfa, - PdfUa: pdfua, - } -} - -// FormDataPdfMetadata creates metadata object from the form data. -func FormDataPdfMetadata(form *api.FormData) map[string]interface{} { - var metadata map[string]interface{} - form.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 - }) - return metadata -} - // convertUrlRoute returns an [api.Route] which can convert a URL to PDF. func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { return api.Route{ @@ -358,8 +326,8 @@ func convertUrlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) - pdfFormats := FormDataChromiumPdfFormats(form) - metadata := FormDataPdfMetadata(form) + pdfFormats := pdfengines.FormDataPdfFormats(form) + metadata := pdfengines.FormDataPdfMetadata(form) var url string err := form. @@ -418,8 +386,8 @@ func convertHtmlRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) - pdfFormats := FormDataChromiumPdfFormats(form) - metadata := FormDataPdfMetadata(form) + pdfFormats := pdfengines.FormDataPdfFormats(form) + metadata := pdfengines.FormDataPdfMetadata(form) var inputPath string err := form. @@ -480,8 +448,8 @@ func convertMarkdownRoute(chromium Api, engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) form, options := FormDataChromiumPdfOptions(ctx) - pdfFormats := FormDataChromiumPdfFormats(form) - metadata := FormDataPdfMetadata(form) + pdfFormats := pdfengines.FormDataPdfFormats(form) + metadata := pdfengines.FormDataPdfMetadata(form) var ( inputPath string @@ -664,34 +632,19 @@ func convertUrl(ctx *api.Context, chromium Api, engine gotenberg.PdfEngine, url return fmt.Errorf("convert to PDF: %w", err) } - // So far so good, the URL has been converted to PDF. - // Now, let's check if the client want to convert the resulting PDF - // to specific formats. - zeroValued := gotenberg.PdfFormats{} - if pdfFormats != zeroValued { - convertInputPath := outputPath - convertOutputPath := ctx.GeneratePath(".pdf") - - err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath) - if err != nil { - return fmt.Errorf("convert PDF: %w", err) - } - - // Important: the output path is now the converted file. - outputPath = convertOutputPath - } - - // Writes and potentially overrides metadata entries, if any. - if len(metadata) > 0 { - err = engine.WriteMetadata(ctx, ctx.Log(), metadata, outputPath) - if err != nil { - return fmt.Errorf("write metadata: %w", err) - } - } - - err = ctx.AddOutputPaths(outputPath) + outputPaths, err := pdfengines.ConvertStub(ctx, engine, pdfFormats, []string{outputPath}) if err != nil { - return fmt.Errorf("add output path: %w", err) + return fmt.Errorf("convert PDF: %w", err) + } + + err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) + if err != nil { + return fmt.Errorf("write metadata: %w", err) + } + + err = ctx.AddOutputPaths(outputPaths...) + if err != nil { + return fmt.Errorf("add output paths: %w", err) } return nil diff --git a/pkg/modules/chromium/routes_test.go b/pkg/modules/chromium/routes_test.go index 629dfbb8..1e7363ba 100644 --- a/pkg/modules/chromium/routes_test.go +++ b/pkg/modules/chromium/routes_test.go @@ -603,125 +603,6 @@ func TestFormDataChromiumScreenshotOptions(t *testing.T) { } } -func TestFormDataChromiumPdfFormats(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedPdfFormats gotenberg.PdfFormats - expectValidationError bool - }{ - { - scenario: "no custom form fields", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedPdfFormats: gotenberg.PdfFormats{}, - expectValidationError: false, - }, - { - scenario: "pdfa and pdfua form fields", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "pdfa": { - "foo", - }, - "pdfua": { - "true", - }, - }) - return ctx - }(), - expectedPdfFormats: gotenberg.PdfFormats{PdfA: "foo", PdfUa: true}, - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form := tc.ctx.Context.FormData() - actual := FormDataChromiumPdfFormats(form) - - if !reflect.DeepEqual(actual, tc.expectedPdfFormats) { - t.Fatalf("expected %+v but got: %+v", tc.expectedPdfFormats, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - -func TestFormDataPdfMetadata(t *testing.T) { - for _, tc := range []struct { - scenario string - ctx *api.ContextMock - expectedMetadata map[string]interface{} - expectValidationError bool - }{ - { - scenario: "no metadata form field", - ctx: &api.ContextMock{Context: new(api.Context)}, - expectedMetadata: nil, - expectValidationError: false, - }, - { - scenario: "invalid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "metadata": { - "foo", - }, - }) - return ctx - }(), - expectedMetadata: nil, - expectValidationError: true, - }, - { - scenario: "valid metadata form field", - ctx: func() *api.ContextMock { - ctx := &api.ContextMock{Context: new(api.Context)} - ctx.SetValues(map[string][]string{ - "metadata": { - "{\"foo\":\"bar\"}", - }, - }) - return ctx - }(), - expectedMetadata: map[string]interface{}{ - "foo": "bar", - }, - expectValidationError: false, - }, - } { - t.Run(tc.scenario, func(t *testing.T) { - tc.ctx.SetLogger(zap.NewNop()) - form := tc.ctx.Context.FormData() - actual := FormDataPdfMetadata(form) - - if !reflect.DeepEqual(actual, tc.expectedMetadata) { - t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual) - } - - err := form.Validate() - - if tc.expectValidationError && err == nil { - t.Fatal("expected validation error but got none", err) - } - - if !tc.expectValidationError && err != nil { - t.Fatalf("expected no validation error but got: %v", err) - } - }) - } -} - func TestConvertUrlRoute(t *testing.T) { for _, tc := range []struct { scenario string diff --git a/pkg/modules/libreoffice/routes.go b/pkg/modules/libreoffice/routes.go index ba8340c6..b49677d6 100644 --- a/pkg/modules/libreoffice/routes.go +++ b/pkg/modules/libreoffice/routes.go @@ -13,6 +13,7 @@ import ( "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" + "github.com/gotenberg/gotenberg/v8/pkg/modules/pdfengines" ) // convertRoute returns an [api.Route] which can convert LibreOffice documents @@ -26,7 +27,10 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap ctx := c.Get("context").(*api.Context) defaultOptions := libreofficeapi.DefaultOptions() - // Let's get the data from the form and validate them. + form := ctx.FormData() + pdfFormats := pdfengines.FormDataPdfFormats(form) + metadata := pdfengines.FormDataPdfMetadata(form) + var ( inputPaths []string password string @@ -51,14 +55,11 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap quality int reduceImageResolution bool maxImageResolution int - pdfa string - pdfua bool nativePdfFormats bool merge bool - metadata map[string]interface{} ) - err := ctx.FormData(). + err := form. MandatoryPaths(libreOffice.Extensions(), &inputPaths). String("password", &password, defaultOptions.Password). Bool("landscape", &landscape, defaultOptions.Landscape). @@ -120,8 +121,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap maxImageResolution = intValue return nil }). - String("pdfa", &pdfa, ""). - Bool("pdfua", &pdfua, false). Bool("nativePdfFormats", &nativePdfFormats, true). Bool("merge", &merge, false). Custom("metadata", func(value string) error { @@ -138,12 +137,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap return fmt.Errorf("validate form data: %w", err) } - pdfFormats := gotenberg.PdfFormats{ - PdfA: pdfa, - PdfUa: pdfua, - } - - // Alright, let's convert each document to PDF. outputPaths := make([]string, len(inputPaths)) for i, inputPath := range inputPaths { outputPaths[i] = ctx.GeneratePath(".pdf") @@ -206,11 +199,8 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - // So far so good, let's check if we have to merge the PDFs. - if len(outputPaths) > 1 && merge { - outputPath := ctx.GeneratePath(".pdf") - - err = engine.Merge(ctx, ctx.Log(), outputPaths, outputPath) + if merge { + outputPath, err := pdfengines.MergeStub(ctx, engine, outputPaths) if err != nil { return fmt.Errorf("merge PDFs: %w", err) } @@ -219,34 +209,16 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap outputPaths = []string{outputPath} } - // Let's check if the client want to convert each PDF to a specific - // PDF format. - zeroValued := gotenberg.PdfFormats{} - if !nativePdfFormats && pdfFormats != zeroValued { - convertOutputPaths := make([]string, len(outputPaths)) - - for i, outputPath := range outputPaths { - convertInputPath := outputPath - convertOutputPaths[i] = ctx.GeneratePath(".pdf") - - err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPaths[i]) - if err != nil { - return fmt.Errorf("convert PDF: %w", err) - } + if !nativePdfFormats { + outputPaths, err = pdfengines.ConvertStub(ctx, engine, pdfFormats, outputPaths) + if err != nil { + return fmt.Errorf("convert PDFs: %w", err) } - - // Important: the output paths are now the converted files. - outputPaths = convertOutputPaths } - // Writes and potentially overrides metadata entries, if any. - if len(metadata) > 0 { - for _, outputPath := range outputPaths { - err = engine.WriteMetadata(ctx, ctx.Log(), metadata, outputPath) - if err != nil { - return fmt.Errorf("write metadata: %w", err) - } - } + err = pdfengines.WriteMetadataStub(ctx, engine, metadata, outputPaths) + if err != nil { + return fmt.Errorf("write metadata: %w", err) } if len(outputPaths) > 1 { @@ -263,8 +235,6 @@ func convertRoute(libreOffice libreofficeapi.Uno, engine gotenberg.PdfEngine) ap } } - // Last but not least, add the output paths to the context so that - // the API is able to send them as a response to the client. err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) diff --git a/pkg/modules/pdfengines/routes.go b/pkg/modules/pdfengines/routes.go index 7ee4a278..a0ddb756 100644 --- a/pkg/modules/pdfengines/routes.go +++ b/pkg/modules/pdfengines/routes.go @@ -13,6 +13,98 @@ import ( "github.com/gotenberg/gotenberg/v8/pkg/modules/api" ) +// FormDataPdfFormats creates [gotenberg.PdfFormats] from the form data. +// Fallback to default value if the considered key is not present. +func FormDataPdfFormats(form *api.FormData) gotenberg.PdfFormats { + var ( + pdfa string + pdfua bool + ) + + form. + String("pdfa", &pdfa, ""). + Bool("pdfua", &pdfua, false) + + return gotenberg.PdfFormats{ + PdfA: pdfa, + PdfUa: pdfua, + } +} + +// FormDataPdfMetadata creates metadata object from the form data. +func FormDataPdfMetadata(form *api.FormData) map[string]interface{} { + var metadata map[string]interface{} + form.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 + }) + return metadata +} + +// MergeStub merges given PDFs. If only one input PDF, it does nothing and +// returns the corresponding input path. +func MergeStub(ctx *api.Context, engine gotenberg.PdfEngine, inputPaths []string) (string, error) { + if len(inputPaths) == 0 { + return "", errors.New("no input paths") + } + + if len(inputPaths) == 1 { + return inputPaths[0], nil + } + + outputPath := ctx.GeneratePath(".pdf") + err := engine.Merge(ctx, ctx.Log(), inputPaths, outputPath) + if err != nil { + return "", fmt.Errorf("merge %d PDFs: %w", len(inputPaths), err) + } + + return outputPath, nil +} + +// ConvertStub transforms a given PDF to the specified formats defined in +// [gotenberg.PdfFormats]. If no format, it does nothing and returns the input +// paths. +func ConvertStub(ctx *api.Context, engine gotenberg.PdfEngine, formats gotenberg.PdfFormats, inputPaths []string) ([]string, error) { + zeroValued := gotenberg.PdfFormats{} + if formats == zeroValued { + return inputPaths, nil + } + + outputPaths := make([]string, len(inputPaths)) + for i, inputPath := range inputPaths { + outputPaths[i] = ctx.GeneratePath(".pdf") + + err := engine.Convert(ctx, ctx.Log(), formats, inputPath, outputPaths[i]) + if err != nil { + return nil, fmt.Errorf("convert '%s': %w", inputPath, err) + } + } + + return outputPaths, nil +} + +// WriteMetadataStub writes the metadata into PDF files. If no metadata, it +// does nothing. +func WriteMetadataStub(ctx *api.Context, engine gotenberg.PdfEngine, metadata map[string]interface{}, inputPaths []string) error { + if len(metadata) == 0 { + return nil + } + + for _, inputPath := range inputPaths { + err := engine.WriteMetadata(ctx, ctx.Log(), metadata, inputPath) + if err != nil { + return fmt.Errorf("write metadata into '%s': %w", inputPath, err) + } + } + + return nil +} + // mergeRoute returns an [api.Route] which can merge PDFs. func mergeRoute(engine gotenberg.PdfEngine) api.Route { return api.Route{ @@ -22,75 +114,37 @@ func mergeRoute(engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) - // Let's get the data from the form and validate them. - var ( - inputPaths []string - pdfa string - pdfua bool - metadata map[string]interface{} - ) + form := ctx.FormData() + pdfFormats := FormDataPdfFormats(form) + metadata := FormDataPdfMetadata(form) - err := ctx.FormData(). + var inputPaths []string + err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). - String("pdfa", &pdfa, ""). - Bool("pdfua", &pdfua, 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 - }). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) } - pdfFormats := gotenberg.PdfFormats{ - PdfA: pdfa, - PdfUa: pdfua, - } - - // Alright, let's merge the PDFs. outputPath := ctx.GeneratePath(".pdf") - err = engine.Merge(ctx, ctx.Log(), inputPaths, outputPath) if err != nil { return fmt.Errorf("merge PDFs: %w", err) } - // So far so good, the PDFs are merged into one unique PDF. - // Now, let's check if the client want to convert this result PDF - // to specific PDF formats. - zeroValued := gotenberg.PdfFormats{} - if pdfFormats != zeroValued { - convertInputPath := outputPath - convertOutputPath := ctx.GeneratePath(".pdf") - - err = engine.Convert(ctx, ctx.Log(), pdfFormats, convertInputPath, convertOutputPath) - if err != nil { - return fmt.Errorf("convert PDF: %w", err) - } - - // Important: the output path is now the converted file. - outputPath = convertOutputPath - } - - // Writes and potentially overrides metadata entries, if any. - if len(metadata) > 0 { - err = engine.WriteMetadata(ctx, ctx.Log(), metadata, outputPath) - if err != nil { - return fmt.Errorf("write metadata: %w", err) - } - } - - // Last but not least, add the output path to the context so that - // the API is able to send it as a response to the client. - err = ctx.AddOutputPaths(outputPath) + outputPaths, err := ConvertStub(ctx, engine, pdfFormats, []string{outputPath}) if err != nil { - return fmt.Errorf("add output path: %w", err) + return fmt.Errorf("convert PDF: %w", err) + } + + err = WriteMetadataStub(ctx, engine, metadata, outputPaths) + if err != nil { + return fmt.Errorf("write metadata: %w", err) + } + + err = ctx.AddOutputPaths(outputPaths...) + if err != nil { + return fmt.Errorf("add output paths: %w", err) } return nil @@ -108,27 +162,17 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) - // Let's get the data from the form and validate them. - var ( - inputPaths []string - pdfa string - pdfua bool - ) + form := ctx.FormData() + pdfFormats := FormDataPdfFormats(form) - err := ctx.FormData(). + var inputPaths []string + err := form. MandatoryPaths([]string{".pdf"}, &inputPaths). - String("pdfa", &pdfa, ""). - Bool("pdfua", &pdfua, false). Validate() if err != nil { return fmt.Errorf("validate form data: %w", err) } - pdfFormats := gotenberg.PdfFormats{ - PdfA: pdfa, - PdfUa: pdfua, - } - zeroValued := gotenberg.PdfFormats{} if pdfFormats == zeroValued { return api.WrapError( @@ -140,18 +184,14 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route { ) } - // Alright, let's convert the PDFs. - outputPaths := make([]string, len(inputPaths)) - for i, inputPath := range inputPaths { - outputPaths[i] = ctx.GeneratePath(".pdf") + outputPaths, err := ConvertStub(ctx, engine, pdfFormats, inputPaths) + if err != nil { + return fmt.Errorf("convert PDFs: %w", err) + } - err = engine.Convert(ctx, ctx.Log(), pdfFormats, inputPath, outputPaths[i]) - if err != nil { - return fmt.Errorf("convert PDF: %w", err) - } - - if len(outputPaths) > 1 { - // If .zip archive, keep the original filename. + if len(outputPaths) > 1 { + // If .zip archive, keep the original filename. + for i, inputPath := range inputPaths { err = ctx.Rename(outputPaths[i], inputPath) if err != nil { return fmt.Errorf("rename output path: %w", err) @@ -161,8 +201,6 @@ func convertRoute(engine gotenberg.PdfEngine) api.Route { } } - // Last but not least, add the output paths to the context so that - // the API is able to send them as a response to the client. err = ctx.AddOutputPaths(outputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) @@ -182,9 +220,7 @@ func readMetadataRoute(engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) - // Let's get the data from the form and validate them. var inputPaths []string - err := ctx.FormData(). MandatoryPaths([]string{".pdf"}, &inputPaths). Validate() @@ -192,7 +228,6 @@ func readMetadataRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("validate form data: %w", err) } - // Alright, let's read the metadata. res := make(map[string]map[string]interface{}, len(inputPaths)) for _, inputPath := range inputPaths { metadata, err := engine.ReadMetadata(ctx, ctx.Log(), inputPath) @@ -223,7 +258,6 @@ func writeMetadataRoute(engine gotenberg.PdfEngine) api.Route { Handler: func(c echo.Context) error { ctx := c.Get("context").(*api.Context) - // Let's get the data from the form and validate them. var ( inputPaths []string metadata map[string]interface{} @@ -248,16 +282,11 @@ func writeMetadataRoute(engine gotenberg.PdfEngine) api.Route { return fmt.Errorf("validate form data: %w", err) } - // Alright, let's convert the PDFs. - for _, inputPath := range inputPaths { - err = engine.WriteMetadata(ctx, ctx.Log(), metadata, inputPath) - if err != nil { - return fmt.Errorf("write metadata: %w", err) - } + err = WriteMetadataStub(ctx, engine, metadata, inputPaths) + if err != nil { + return fmt.Errorf("write metadata: %w", err) } - // Last but not least, add the output paths to the context so that - // the API is able to send them as a response to the client. err = ctx.AddOutputPaths(inputPaths...) if err != nil { return fmt.Errorf("add output paths: %w", err) diff --git a/pkg/modules/pdfengines/routes_test.go b/pkg/modules/pdfengines/routes_test.go index a3cdd940..94df1688 100644 --- a/pkg/modules/pdfengines/routes_test.go +++ b/pkg/modules/pdfengines/routes_test.go @@ -5,6 +5,7 @@ import ( "errors" "net/http" "net/http/httptest" + "reflect" "slices" "strings" "testing" @@ -16,6 +17,287 @@ import ( "github.com/gotenberg/gotenberg/v8/pkg/modules/api" ) +func TestFormDataPdfFormats(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + expectedPdfFormats gotenberg.PdfFormats + expectValidationError bool + }{ + { + scenario: "no custom form fields", + ctx: &api.ContextMock{Context: new(api.Context)}, + expectedPdfFormats: gotenberg.PdfFormats{}, + expectValidationError: false, + }, + { + scenario: "pdfa and pdfua form fields", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "pdfa": { + "foo", + }, + "pdfua": { + "true", + }, + }) + return ctx + }(), + expectedPdfFormats: gotenberg.PdfFormats{PdfA: "foo", PdfUa: true}, + expectValidationError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.SetLogger(zap.NewNop()) + form := tc.ctx.Context.FormData() + actual := FormDataPdfFormats(form) + + if !reflect.DeepEqual(actual, tc.expectedPdfFormats) { + t.Fatalf("expected %+v but got: %+v", tc.expectedPdfFormats, actual) + } + + err := form.Validate() + + if tc.expectValidationError && err == nil { + t.Fatal("expected validation error but got none", err) + } + + if !tc.expectValidationError && err != nil { + t.Fatalf("expected no validation error but got: %v", err) + } + }) + } +} + +func TestFormDataPdfMetadata(t *testing.T) { + for _, tc := range []struct { + scenario string + ctx *api.ContextMock + expectedMetadata map[string]interface{} + expectValidationError bool + }{ + { + scenario: "no metadata form field", + ctx: &api.ContextMock{Context: new(api.Context)}, + expectedMetadata: nil, + expectValidationError: false, + }, + { + scenario: "invalid metadata form field", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "metadata": { + "foo", + }, + }) + return ctx + }(), + expectedMetadata: nil, + expectValidationError: true, + }, + { + scenario: "valid metadata form field", + ctx: func() *api.ContextMock { + ctx := &api.ContextMock{Context: new(api.Context)} + ctx.SetValues(map[string][]string{ + "metadata": { + "{\"foo\":\"bar\"}", + }, + }) + return ctx + }(), + expectedMetadata: map[string]interface{}{ + "foo": "bar", + }, + expectValidationError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + tc.ctx.SetLogger(zap.NewNop()) + form := tc.ctx.Context.FormData() + actual := FormDataPdfMetadata(form) + + if !reflect.DeepEqual(actual, tc.expectedMetadata) { + t.Fatalf("expected %+v but got: %+v", tc.expectedMetadata, actual) + } + + err := form.Validate() + + if tc.expectValidationError && err == nil { + t.Fatal("expected validation error but got none", err) + } + + if !tc.expectValidationError && err != nil { + t.Fatalf("expected no validation error but got: %v", err) + } + }) + } +} + +func TestMergeStub(t *testing.T) { + for _, tc := range []struct { + scenario string + engine gotenberg.PdfEngine + inputPaths []string + expectError bool + }{ + { + scenario: "no input path (nil)", + inputPaths: nil, + expectError: true, + }, + { + scenario: "no input path (empty)", + inputPaths: make([]string, 0), + expectError: true, + }, + { + scenario: "only one input path", + inputPaths: []string{"my.pdf"}, + expectError: false, + }, + { + scenario: "merge error", + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return errors.New("foo") + }, + }, + inputPaths: []string{"my.pdf", "my2.pdf"}, + expectError: true, + }, + { + scenario: "merge success", + engine: &gotenberg.PdfEngineMock{ + MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error { + return nil + }, + }, + inputPaths: []string{"my.pdf", "my2.pdf"}, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + _, err := MergeStub(new(api.Context), tc.engine, tc.inputPaths) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + +func TestConvertStub(t *testing.T) { + for _, tc := range []struct { + scenario string + engine gotenberg.PdfEngine + pdfFormats gotenberg.PdfFormats + expectError bool + }{ + { + scenario: "no PDF formats", + pdfFormats: gotenberg.PdfFormats{}, + expectError: false, + }, + { + scenario: "convert error", + engine: &gotenberg.PdfEngineMock{ + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return errors.New("foo") + }, + }, + pdfFormats: gotenberg.PdfFormats{ + PdfA: gotenberg.PdfA3b, + PdfUa: true, + }, + expectError: true, + }, + { + scenario: "convert success", + engine: &gotenberg.PdfEngineMock{ + ConvertMock: func(ctx context.Context, logger *zap.Logger, formats gotenberg.PdfFormats, inputPath, outputPath string) error { + return nil + }, + }, + pdfFormats: gotenberg.PdfFormats{ + PdfA: gotenberg.PdfA3b, + PdfUa: true, + }, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + _, err := ConvertStub(new(api.Context), tc.engine, tc.pdfFormats, []string{"my.pdf", "my2.pdf"}) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + +func TestWriteMetadataStub(t *testing.T) { + for _, tc := range []struct { + scenario string + engine gotenberg.PdfEngine + metadata map[string]interface{} + expectError bool + }{ + { + scenario: "no metadata (nil)", + metadata: nil, + expectError: false, + }, + { + scenario: "no metadata (empty)", + metadata: make(map[string]interface{}, 0), + expectError: false, + }, + { + scenario: "write metadata error", + engine: &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return errors.New("foo") + }, + }, + metadata: map[string]interface{}{"foo": "bar"}, + expectError: true, + }, + { + scenario: "write metadata success", + engine: &gotenberg.PdfEngineMock{ + WriteMetadataMock: func(ctx context.Context, logger *zap.Logger, metadata map[string]interface{}, inputPath string) error { + return nil + }, + }, + metadata: map[string]interface{}{"foo": "bar"}, + expectError: false, + }, + } { + t.Run(tc.scenario, func(t *testing.T) { + err := WriteMetadataStub(new(api.Context), tc.engine, tc.metadata, []string{"my.pdf", "my2.pdf"}) + + if tc.expectError && err == nil { + t.Fatal("expected error but got none", err) + } + + if !tc.expectError && err != nil { + t.Fatalf("expected no error but got: %v", err) + } + }) + } +} + func TestMergeHandler(t *testing.T) { for _, tc := range []struct { scenario string