Files
gotenberg/pkg/modules/libreoffice/api/api_test.go
2025-02-04 10:20:29 +01:00

523 lines
12 KiB
Go

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)
}
}