Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
3b2c48b5e4
commit
c0c4addb85
285 changed files with 25880 additions and 0 deletions
13
pkg/services/standard/enumless_config.go
Normal file
13
pkg/services/standard/enumless_config.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// EnumlessConfig implements the ServiceConfig interface for services that does not use Enum fields.
|
||||
type EnumlessConfig struct{}
|
||||
|
||||
// Enums returns an empty map.
|
||||
func (ec *EnumlessConfig) Enums() map[string]types.EnumFormatter {
|
||||
return map[string]types.EnumFormatter{}
|
||||
}
|
7
pkg/services/standard/standard.go
Normal file
7
pkg/services/standard/standard.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package standard
|
||||
|
||||
// Standard implements the Logger and Templater parts of the Service interface.
|
||||
type Standard struct {
|
||||
Logger
|
||||
Templater
|
||||
}
|
59
pkg/services/standard/standard_failures.go
Normal file
59
pkg/services/standard/standard_failures.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/internal/failures"
|
||||
)
|
||||
|
||||
const (
|
||||
// FailTestSetup is the FailureID used to represent an error that is part of the setup for tests.
|
||||
FailTestSetup failures.FailureID = -1
|
||||
// FailParseURL is the FailureID used to represent failing to parse the service URL.
|
||||
FailParseURL failures.FailureID = -2
|
||||
// FailServiceInit is the FailureID used to represent failure of a service.Initialize method.
|
||||
FailServiceInit failures.FailureID = -3
|
||||
// FailUnknown is the default FailureID.
|
||||
FailUnknown failures.FailureID = iota
|
||||
)
|
||||
|
||||
type failureLike interface {
|
||||
failures.Failure
|
||||
}
|
||||
|
||||
// Failure creates a Failure instance corresponding to the provided failureID, wrapping the provided error.
|
||||
func Failure(failureID failures.FailureID, err error, args ...any) failures.Failure {
|
||||
messages := map[int]string{
|
||||
int(FailTestSetup): "test setup failed",
|
||||
int(FailParseURL): "error parsing Service URL",
|
||||
int(FailUnknown): "an unknown error occurred",
|
||||
}
|
||||
|
||||
msg := messages[int(failureID)]
|
||||
if msg == "" {
|
||||
msg = messages[int(FailUnknown)]
|
||||
}
|
||||
|
||||
// If variadic arguments are provided, format them correctly
|
||||
if len(args) > 0 {
|
||||
if format, ok := args[0].(string); ok && len(args) > 1 {
|
||||
// Treat the first argument as a format string and the rest as its arguments
|
||||
extraMsg := fmt.Sprintf(format, args[1:]...)
|
||||
msg = fmt.Sprintf("%s %s", msg, extraMsg)
|
||||
} else {
|
||||
// If no format string is provided, just append the arguments as-is
|
||||
msg = fmt.Sprintf("%s %v", msg, args)
|
||||
}
|
||||
}
|
||||
|
||||
return failures.Wrap(msg, failureID, err)
|
||||
}
|
||||
|
||||
// IsTestSetupFailure checks whether the given failure is due to the test setup being broken.
|
||||
func IsTestSetupFailure(failure failureLike) (string, bool) {
|
||||
if failure != nil && failure.ID() == FailTestSetup {
|
||||
return "test setup failed: " + failure.Error(), true
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
30
pkg/services/standard/standard_logger.go
Normal file
30
pkg/services/standard/standard_logger.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/util"
|
||||
)
|
||||
|
||||
// Logger provides the utility methods Log* that maps to Logger.Print*.
|
||||
type Logger struct {
|
||||
logger types.StdLogger
|
||||
}
|
||||
|
||||
// Logf maps to the service loggers Logger.Printf function.
|
||||
func (sl *Logger) Logf(format string, v ...any) {
|
||||
sl.logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
// Log maps to the service loggers Logger.Print function.
|
||||
func (sl *Logger) Log(v ...any) {
|
||||
sl.logger.Print(v...)
|
||||
}
|
||||
|
||||
// SetLogger maps the specified logger to the Log* helper methods.
|
||||
func (sl *Logger) SetLogger(logger types.StdLogger) {
|
||||
if logger == nil {
|
||||
sl.logger = util.DiscardLogger
|
||||
} else {
|
||||
sl.logger = logger
|
||||
}
|
||||
}
|
45
pkg/services/standard/standard_templater.go
Normal file
45
pkg/services/standard/standard_templater.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
// Templater is the standard implementation of ApplyTemplate using the "text/template" library.
|
||||
type Templater struct {
|
||||
templates map[string]*template.Template
|
||||
}
|
||||
|
||||
// GetTemplate attempts to retrieve the template identified with id.
|
||||
func (templater *Templater) GetTemplate(id string) (*template.Template, bool) {
|
||||
tpl, found := templater.templates[id]
|
||||
|
||||
return tpl, found
|
||||
}
|
||||
|
||||
// SetTemplateString creates a new template from the body and assigns it the id.
|
||||
func (templater *Templater) SetTemplateString(templateID string, body string) error {
|
||||
tpl, err := template.New("").Parse(body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing template string for ID %q: %w", templateID, err)
|
||||
}
|
||||
|
||||
if templater.templates == nil {
|
||||
templater.templates = make(map[string]*template.Template, 1)
|
||||
}
|
||||
|
||||
templater.templates[templateID] = tpl
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTemplateFile creates a new template from the file and assigns it the id.
|
||||
func (templater *Templater) SetTemplateFile(templateID string, file string) error {
|
||||
bytes, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading template file %q for ID %q: %w", file, templateID, err)
|
||||
}
|
||||
|
||||
return templater.SetTemplateString(templateID, string(bytes))
|
||||
}
|
205
pkg/services/standard/standard_test.go
Normal file
205
pkg/services/standard/standard_test.go
Normal file
|
@ -0,0 +1,205 @@
|
|||
package standard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/internal/failures"
|
||||
)
|
||||
|
||||
func TestStandard(t *testing.T) {
|
||||
gomega.RegisterFailHandler(ginkgo.Fail)
|
||||
ginkgo.RunSpecs(t, "Shoutrrr Standard Suite")
|
||||
}
|
||||
|
||||
var (
|
||||
logger *Logger
|
||||
builder *strings.Builder
|
||||
stringLogger *log.Logger
|
||||
)
|
||||
|
||||
var _ = ginkgo.Describe("the standard logging implementation", func() {
|
||||
ginkgo.When("setlogger is called with nil", func() {
|
||||
ginkgo.It("should provide the logging API without any errors", func() {
|
||||
logger = &Logger{}
|
||||
logger.SetLogger(nil)
|
||||
logger.Log("discarded log message")
|
||||
|
||||
gomega.Expect(logger.logger).ToNot(gomega.BeNil())
|
||||
})
|
||||
})
|
||||
ginkgo.When("setlogger is called with a proper logger", func() {
|
||||
ginkgo.BeforeEach(func() {
|
||||
logger = &Logger{}
|
||||
builder = &strings.Builder{}
|
||||
stringLogger = log.New(builder, "", 0)
|
||||
})
|
||||
ginkgo.When("when logger.Log is called", func() {
|
||||
ginkgo.It("should log messages", func() {
|
||||
logger.SetLogger(stringLogger)
|
||||
logger.Log("foo")
|
||||
logger.Log("bar")
|
||||
|
||||
gomega.Expect(builder.String()).To(gomega.Equal("foo\nbar\n"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("when logger.Logf is called", func() {
|
||||
ginkgo.It("should log messages", func() {
|
||||
logger.SetLogger(stringLogger)
|
||||
logger.Logf("foo %d", 7)
|
||||
|
||||
gomega.Expect(builder.String()).To(gomega.Equal("foo 7\n"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = ginkgo.Describe("the standard template implementation", func() {
|
||||
ginkgo.When("a template is being set from a file", func() {
|
||||
ginkgo.It("should load the template without any errors", func() {
|
||||
file, err := os.CreateTemp("", "")
|
||||
if err != nil {
|
||||
ginkgo.Skip(fmt.Sprintf("Could not create temp file: %s", err))
|
||||
|
||||
return
|
||||
}
|
||||
fileName := file.Name()
|
||||
defer os.Remove(fileName)
|
||||
|
||||
_, err = io.WriteString(file, "template content")
|
||||
if err != nil {
|
||||
ginkgo.Skip(fmt.Sprintf("Could not write to temp file: %s", err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
templater := &Templater{}
|
||||
err = templater.SetTemplateFile("foo", fileName)
|
||||
gomega.Expect(err).ToNot(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
ginkgo.When("a template is being set from a file that does not exist", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
templater := &Templater{}
|
||||
err := templater.SetTemplateFile("foo", "filename_that_should_not_exist")
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
ginkgo.When("a template is being set with a badly formatted string", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
templater := &Templater{}
|
||||
err := templater.SetTemplateString("foo", "template {{ missing end tag")
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
ginkgo.When("a template is being retrieved with a present ID", func() {
|
||||
ginkgo.It("should return the corresponding template", func() {
|
||||
templater := &Templater{}
|
||||
err := templater.SetTemplateString("bar", "template body")
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
|
||||
tpl, found := templater.GetTemplate("bar")
|
||||
gomega.Expect(tpl).ToNot(gomega.BeNil())
|
||||
gomega.Expect(found).To(gomega.BeTrue())
|
||||
})
|
||||
})
|
||||
ginkgo.When("a template is being retrieved with an invalid ID", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
templater := &Templater{}
|
||||
err := templater.SetTemplateString("bar", "template body")
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
|
||||
tpl, found := templater.GetTemplate("bad ID")
|
||||
gomega.Expect(tpl).To(gomega.BeNil())
|
||||
gomega.Expect(found).ToNot(gomega.BeTrue())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = ginkgo.Describe("the standard enumless config implementation", func() {
|
||||
ginkgo.When("it's enum method is called", func() {
|
||||
ginkgo.It("should return an empty map", func() {
|
||||
gomega.Expect((&EnumlessConfig{}).Enums()).To(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = ginkgo.Describe("the standard failure implementation", func() {
|
||||
ginkgo.Describe("Failure function", func() {
|
||||
ginkgo.When("called with FailParseURL", func() {
|
||||
ginkgo.It("should return a failure with the correct message", func() {
|
||||
err := errors.New("invalid URL")
|
||||
failure := Failure(FailParseURL, err)
|
||||
gomega.Expect(failure.ID()).To(gomega.Equal(FailParseURL))
|
||||
gomega.Expect(failure.Error()).
|
||||
To(gomega.ContainSubstring("error parsing Service URL"))
|
||||
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("invalid URL"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("called with FailUnknown", func() {
|
||||
ginkgo.It("should return a failure with the unknown error message", func() {
|
||||
err := errors.New("something went wrong")
|
||||
failure := Failure(FailUnknown, err)
|
||||
gomega.Expect(failure.ID()).To(gomega.Equal(FailUnknown))
|
||||
gomega.Expect(failure.Error()).
|
||||
To(gomega.ContainSubstring("an unknown error occurred"))
|
||||
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("something went wrong"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("called with an unrecognized FailureID", func() {
|
||||
ginkgo.It("should fallback to the unknown error message", func() {
|
||||
err := errors.New("unrecognized error")
|
||||
failure := Failure(failures.FailureID(999), err) // Arbitrary unknown ID
|
||||
gomega.Expect(failure.ID()).To(gomega.Equal(failures.FailureID(999)))
|
||||
gomega.Expect(failure.Error()).
|
||||
To(gomega.ContainSubstring("an unknown error occurred"))
|
||||
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("unrecognized error"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("called with additional arguments", func() {
|
||||
ginkgo.It("should include formatted arguments in the error", func() {
|
||||
err := errors.New("base error")
|
||||
failure := Failure(FailParseURL, err, "extra info: %s", "details")
|
||||
gomega.Expect(failure.Error()).
|
||||
To(gomega.ContainSubstring("error parsing Service URL extra info: details"))
|
||||
gomega.Expect(failure.Error()).To(gomega.ContainSubstring("base error"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("IsTestSetupFailure function", func() {
|
||||
ginkgo.When("called with a FailTestSetup failure", func() {
|
||||
ginkgo.It("should return true and the correct message", func() {
|
||||
err := errors.New("setup issue")
|
||||
failure := Failure(FailTestSetup, err)
|
||||
msg, isSetupFailure := IsTestSetupFailure(failure)
|
||||
gomega.Expect(isSetupFailure).To(gomega.BeTrue())
|
||||
gomega.Expect(msg).To(gomega.ContainSubstring("test setup failed: setup issue"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("called with a different failure", func() {
|
||||
ginkgo.It("should return false and an empty message", func() {
|
||||
err := errors.New("parse issue")
|
||||
failure := Failure(FailParseURL, err)
|
||||
msg, isSetupFailure := IsTestSetupFailure(failure)
|
||||
gomega.Expect(isSetupFailure).To(gomega.BeFalse())
|
||||
gomega.Expect(msg).To(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
ginkgo.When("called with nil", func() {
|
||||
ginkgo.It("should return false and an empty message", func() {
|
||||
msg, isSetupFailure := IsTestSetupFailure(nil)
|
||||
gomega.Expect(isSetupFailure).To(gomega.BeFalse())
|
||||
gomega.Expect(msg).To(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
Loading…
Add table
Add a link
Reference in a new issue