diff --git a/build/tests/docker-entrypoint.sh b/build/tests/docker-entrypoint.sh index 21a86c41..49aa729b 100755 --- a/build/tests/docker-entrypoint.sh +++ b/build/tests/docker-entrypoint.sh @@ -13,6 +13,7 @@ go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/int go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/internal/pkg/random go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/internal/pkg/standarderror go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/internal/pkg/timeout +go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/internal/pkg/printer go test -race -cover -covermode=atomic github.com/thecodingmachine/gotenberg/internal/app/api # Finally testing processes shutdown. diff --git a/internal/app/api/pkg/context/context.go b/internal/app/api/pkg/context/context.go index e3673c35..99e70532 100644 --- a/internal/app/api/pkg/context/context.go +++ b/internal/app/api/pkg/context/context.go @@ -1,6 +1,7 @@ package context import ( + "fmt" "net/http" "strconv" "time" @@ -35,9 +36,10 @@ func New(c echo.Context, logger *logger.Logger, config *config.Config) *Context // MustCastFromEchoContext cast an echo.Context to our custom // context. If something goes wrong, panic. func MustCastFromEchoContext(c echo.Context) *Context { + const op = "MustCastFromEchoContext" ctx, ok := c.(*Context) if !ok { - panic("unable to cast an echo.Context to a custom context") + panic(fmt.Sprintf("%s: unable to cast an echo.Context to a custom context", op)) } return ctx } diff --git a/internal/pkg/printer/chrome.go b/internal/pkg/printer/chrome.go index 5bf36a9c..fe1d181a 100644 --- a/internal/pkg/printer/chrome.go +++ b/internal/pkg/printer/chrome.go @@ -44,19 +44,19 @@ func (p *chrome) Print(destination string) error { defer cancel() devt, err := devtool.New("http://localhost:9222").Version(ctx) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } // connect to WebSocket URL (page) that speaks the Chrome DevTools Protocol. devtConn, err := rpcc.DialContext(ctx, devt.WebSocketDebuggerURL) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } defer devtConn.Close() // nolint: errcheck // create a new CDP Client that uses conn. devtClient := cdp.NewClient(devtConn) newContextTarget, err := devtClient.Target.CreateBrowserContext(ctx) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } // create a new blank target with the new browser context. createTargetArgs := target. @@ -64,13 +64,13 @@ func (p *chrome) Print(destination string) error { SetBrowserContextID(newContextTarget.BrowserContextID) newTarget, err := devtClient.Target.CreateTarget(ctx, createTargetArgs) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } // connect the client to the new target. newTargetWsURL := fmt.Sprintf("ws://127.0.0.1:9222/devtools/page/%s", newTarget.TargetID) newContextConn, err := rpcc.DialContext(ctx, newTargetWsURL) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } defer newContextConn.Close() // nolint: errcheck // create a new CDP Client that uses newContextConn. @@ -85,10 +85,10 @@ func (p *chrome) Print(destination string) error { func() error { return targetClient.Page.Enable(ctx) }, func() error { return targetClient.Runtime.Enable(ctx) }, ); err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } if err := p.navigate(ctx, targetClient); err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } print, err := targetClient.Page.PrintToPDF( ctx, @@ -106,7 +106,7 @@ func (p *chrome) Print(destination string) error { SetPrintBackground(true), ) if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } if err := ioutil.WriteFile(destination, print.Data, 0644); err != nil { return &standarderror.Error{Op: op, Err: err} diff --git a/internal/pkg/printer/merge.go b/internal/pkg/printer/merge.go index cc096801..746a5ee1 100644 --- a/internal/pkg/printer/merge.go +++ b/internal/pkg/printer/merge.go @@ -41,7 +41,7 @@ func (p *merge) Print(destination string) error { cmd := exec.CommandContext(p.ctx, "pdftk", cmdArgs...) _, err := cmd.Output() if err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(p.ctx, &standarderror.Error{Op: op, Err: err}) } return nil } diff --git a/internal/pkg/printer/office.go b/internal/pkg/printer/office.go index d3eaec61..26db09e9 100644 --- a/internal/pkg/printer/office.go +++ b/internal/pkg/printer/office.go @@ -43,7 +43,7 @@ func (p *office) Print(destination string) error { baseFilename := random.String(32) tmpDest := fmt.Sprintf("%s/%d%s.pdf", dirPath, i, baseFilename) if err := unoconv(ctx, fpath, tmpDest, p.opts); err != nil { - return &standarderror.Error{Op: op, Err: err} + return handleErrContext(ctx, &standarderror.Error{Op: op, Err: err}) } fpaths[i] = tmpDest } diff --git a/internal/pkg/printer/printer.go b/internal/pkg/printer/printer.go index e1e72497..5fff84c7 100644 --- a/internal/pkg/printer/printer.go +++ b/internal/pkg/printer/printer.go @@ -1,7 +1,39 @@ package printer +import ( + "context" + "fmt" + "strings" + + "github.com/thecodingmachine/gotenberg/internal/pkg/standarderror" +) + // Printer is a type that can create a PDF file from a source. // The source is defined in the underlying implementation. type Printer interface { Print(destination string) error } + +func handleErrContext(ctx context.Context, previousErr error) error { + const op = "printer.handleErrContext" + if previousErr == nil { + panic(fmt.Sprintf("%s: previous error should not be nil", op)) + } + err := ctx.Err() + if err == nil { + return previousErr + } + if strings.Contains(err.Error(), context.DeadlineExceeded.Error()) { + return &standarderror.Error{ + Code: standarderror.Timeout, + Message: "context has timed out", + Op: op, + Err: previousErr, + } + } + return &standarderror.Error{ + Message: "context finished with an error", + Op: op, + Err: previousErr, + } +} diff --git a/internal/pkg/printer/printer_test.go b/internal/pkg/printer/printer_test.go new file mode 100644 index 00000000..d844ac72 --- /dev/null +++ b/internal/pkg/printer/printer_test.go @@ -0,0 +1,35 @@ +package printer + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/thecodingmachine/gotenberg/internal/pkg/standarderror" + "github.com/thecodingmachine/gotenberg/internal/pkg/timeout" + "github.com/thecodingmachine/gotenberg/test" +) + +func TestHandlerErr(t *testing.T) { + previousErr := errors.New("previous error") + // should be OK. + ctx, cancel := timeout.Context(5) + defer cancel() + assert.NotNil(t, handleErrContext(ctx, previousErr)) + // should timeout. + ctx, cancel = timeout.Context(0.5) + defer cancel() + time.Sleep(timeout.Duration(1)) + err := handleErrContext(ctx, previousErr) + assert.NotNil(t, err) + standardized := test.RequireStandardError(t, err) + assert.Equal(t, standarderror.Timeout, standardized.Code) + // should failed. + ctx, cancel = timeout.Context(5) + cancel() + err = handleErrContext(ctx, previousErr) + assert.NotNil(t, err) + standardized = test.RequireStandardError(t, err) + assert.Equal(t, standarderror.Internal, standarderror.Code(err)) +} diff --git a/internal/pkg/timeout/timeout.go b/internal/pkg/timeout/timeout.go index 7228ece2..f29a03d0 100644 --- a/internal/pkg/timeout/timeout.go +++ b/internal/pkg/timeout/timeout.go @@ -2,10 +2,7 @@ package timeout import ( "context" - "strings" "time" - - "github.com/thecodingmachine/gotenberg/internal/pkg/standarderror" ) // Context creates a context with timeout for @@ -18,26 +15,3 @@ func Context(seconds float64) (context.Context, context.CancelFunc) { func Duration(seconds float64) time.Duration { return time.Duration(1000*seconds) * time.Millisecond } - -// Err returns a standarderror.Error -// if the context has an error. -func Err(ctx context.Context) error { - const op = "timeout.Err" - err := ctx.Err() - if err == nil { - return nil - } - if strings.Contains(err.Error(), context.DeadlineExceeded.Error()) { - return &standarderror.Error{ - Code: standarderror.Timeout, - Message: "context has timed out", - Op: op, - Err: err, - } - } - return &standarderror.Error{ - Message: "context finished with an error", - Op: op, - Err: err, - } -} diff --git a/internal/pkg/timeout/timeout_test.go b/internal/pkg/timeout/timeout_test.go index 9ab855e9..43f21e5c 100644 --- a/internal/pkg/timeout/timeout_test.go +++ b/internal/pkg/timeout/timeout_test.go @@ -5,8 +5,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/thecodingmachine/gotenberg/internal/pkg/standarderror" - "github.com/thecodingmachine/gotenberg/test" ) func TestDuration(t *testing.T) { @@ -14,25 +12,3 @@ func TestDuration(t *testing.T) { result := Duration(1.5) assert.Equal(t, expected.String(), result.String()) } - -func TestErr(t *testing.T) { - // should be OK. - ctx, cancel := Context(5) - defer cancel() - assert.Nil(t, Err(ctx)) - // should timeout. - ctx, cancel = Context(0.5) - defer cancel() - time.Sleep(Duration(1)) - err := Err(ctx) - assert.NotNil(t, err) - standardized := test.RequireStandardError(t, err) - assert.Equal(t, standarderror.Timeout, standardized.Code) - // should failed. - ctx, cancel = Context(5) - cancel() - err = Err(ctx) - assert.NotNil(t, err) - standardized = test.RequireStandardError(t, err) - assert.Equal(t, standarderror.Internal, standarderror.Code(err)) -}