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
48
internal/testutils/config.go
Normal file
48
internal/testutils/config.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/format"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// TestConfigGetInvalidQueryValue tests whether the config returns
|
||||
// an error when an invalid query value is requested.
|
||||
func TestConfigGetInvalidQueryValue(config types.ServiceConfig) {
|
||||
value, err := format.GetConfigQueryResolver(config).Get("invalid query var")
|
||||
gomega.ExpectWithOffset(1, value).To(gomega.BeEmpty())
|
||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
||||
// TestConfigSetInvalidQueryValue tests whether the config returns
|
||||
// an error when a URL with an invalid query value is parsed.
|
||||
func TestConfigSetInvalidQueryValue(config types.ServiceConfig, rawInvalidURL string) {
|
||||
invalidURL, err := url.Parse(rawInvalidURL)
|
||||
gomega.ExpectWithOffset(1, err).
|
||||
ToNot(gomega.HaveOccurred(), "the test URL did not parse correctly")
|
||||
|
||||
err = config.SetURL(invalidURL)
|
||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred())
|
||||
}
|
||||
|
||||
// TestConfigSetDefaultValues tests whether setting the default values
|
||||
// can be set for an empty config without any errors.
|
||||
func TestConfigSetDefaultValues(config types.ServiceConfig) {
|
||||
pkr := format.NewPropKeyResolver(config)
|
||||
gomega.ExpectWithOffset(1, pkr.SetDefaultProps(config)).To(gomega.Succeed())
|
||||
}
|
||||
|
||||
// TestConfigGetEnumsCount tests whether the config.Enums returns the expected amount of items.
|
||||
func TestConfigGetEnumsCount(config types.ServiceConfig, expectedCount int) {
|
||||
enums := config.Enums()
|
||||
gomega.ExpectWithOffset(1, enums).To(gomega.HaveLen(expectedCount))
|
||||
}
|
||||
|
||||
// TestConfigGetFieldsCount tests whether the config.QueryFields return the expected amount of fields.
|
||||
func TestConfigGetFieldsCount(config types.ServiceConfig, expectedCount int) {
|
||||
fields := format.GetConfigQueryResolver(config).QueryFields()
|
||||
gomega.ExpectWithOffset(1, fields).To(gomega.HaveLen(expectedCount))
|
||||
}
|
7
internal/testutils/eavesdropper.go
Normal file
7
internal/testutils/eavesdropper.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package testutils
|
||||
|
||||
// Eavesdropper is an interface that provides a way to get a summarized output of a connection RX and TX.
|
||||
type Eavesdropper interface {
|
||||
GetConversation(includeGreeting bool) string
|
||||
GetClientSentences() []string
|
||||
}
|
36
internal/testutils/failwriter.go
Normal file
36
internal/testutils/failwriter.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
var ErrWriteLimitReached = errors.New("reached write limit")
|
||||
|
||||
type failWriter struct {
|
||||
writeLimit int
|
||||
writeCount int
|
||||
}
|
||||
|
||||
// Close is just a dummy function to implement io.Closer.
|
||||
func (fw *failWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write returns an error if the write limit has been reached.
|
||||
func (fw *failWriter) Write(data []byte) (int, error) {
|
||||
fw.writeCount++
|
||||
if fw.writeCount > fw.writeLimit {
|
||||
return 0, fmt.Errorf("%w: %d", ErrWriteLimitReached, fw.writeLimit)
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// CreateFailWriter returns a io.WriteCloser that returns an error after the amount of writes indicated by writeLimit.
|
||||
func CreateFailWriter(writeLimit int) io.WriteCloser {
|
||||
return &failWriter{
|
||||
writeLimit: writeLimit,
|
||||
}
|
||||
}
|
14
internal/testutils/iofaker.go
Normal file
14
internal/testutils/iofaker.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
type ioFaker struct {
|
||||
io.ReadWriter
|
||||
}
|
||||
|
||||
// Close is just a dummy function to implement the io.Closer interface.
|
||||
func (iof ioFaker) Close() error {
|
||||
return nil
|
||||
}
|
12
internal/testutils/logging.go
Normal file
12
internal/testutils/logging.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
)
|
||||
|
||||
// TestLogger returns a log.Logger that writes to ginkgo.GinkgoWriter for use in tests.
|
||||
func TestLogger() *log.Logger {
|
||||
return log.New(ginkgo.GinkgoWriter, "[Test] ", 0)
|
||||
}
|
8
internal/testutils/mockclientservice.go
Normal file
8
internal/testutils/mockclientservice.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package testutils
|
||||
|
||||
import "net/http"
|
||||
|
||||
// MockClientService is used to allow mocking the HTTP client when testing.
|
||||
type MockClientService interface {
|
||||
GetHTTPClient() *http.Client
|
||||
}
|
25
internal/testutils/must.go
Normal file
25
internal/testutils/must.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
// URLMust creates a url.URL from the given rawURL and fails the test if it cannot be parsed.
|
||||
func URLMust(rawURL string) *url.URL {
|
||||
parsed, err := url.Parse(rawURL)
|
||||
gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred())
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
// JSONRespondMust creates a httpmock.Responder with the given response
|
||||
// as the body, and fails the test if it cannot be created.
|
||||
func JSONRespondMust(code int, response any) httpmock.Responder {
|
||||
responder, err := httpmock.NewJsonResponder(code, response)
|
||||
gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred(), "invalid test response struct")
|
||||
|
||||
return responder
|
||||
}
|
14
internal/testutils/service.go
Normal file
14
internal/testutils/service.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// TestServiceSetInvalidParamValue tests whether the service returns an error
|
||||
// when an invalid param key/value is passed through Send.
|
||||
func TestServiceSetInvalidParamValue(service types.Service, key string, value string) {
|
||||
err := service.Send("TestMessage", &types.Params{key: value})
|
||||
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred())
|
||||
}
|
135
internal/testutils/testutils_test.go
Normal file
135
internal/testutils/testutils_test.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package testutils_test
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/internal/testutils"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
func TestTestUtils(t *testing.T) {
|
||||
gomega.RegisterFailHandler(ginkgo.Fail)
|
||||
|
||||
ginkgo.RunSpecs(t, "Shoutrrr TestUtils Suite")
|
||||
}
|
||||
|
||||
var _ = ginkgo.Describe("the testutils package", func() {
|
||||
ginkgo.When("calling function TestLogger", func() {
|
||||
ginkgo.It("should not return nil", func() {
|
||||
gomega.Expect(testutils.TestLogger()).NotTo(gomega.BeNil())
|
||||
})
|
||||
ginkgo.It(`should have the prefix "[Test] "`, func() {
|
||||
gomega.Expect(testutils.TestLogger().Prefix()).To(gomega.Equal("[Test] "))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("Must helpers", func() {
|
||||
ginkgo.Describe("URLMust", func() {
|
||||
ginkgo.It("should panic when an invalid URL is passed", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() { testutils.URLMust(":") })
|
||||
gomega.Expect(failures).To(gomega.HaveLen(1))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("JSONRespondMust", func() {
|
||||
ginkgo.It("should panic when an invalid struct is passed", func() {
|
||||
notAValidJSONSource := func() {}
|
||||
failures := gomega.InterceptGomegaFailures(
|
||||
func() { testutils.JSONRespondMust(200, notAValidJSONSource) },
|
||||
)
|
||||
gomega.Expect(failures).To(gomega.HaveLen(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("Config test helpers", func() {
|
||||
var config dummyConfig
|
||||
ginkgo.BeforeEach(func() {
|
||||
config = dummyConfig{}
|
||||
})
|
||||
ginkgo.Describe("TestConfigSetInvalidQueryValue", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestConfigSetInvalidQueryValue(&config, "mock://host?invalid=value")
|
||||
})
|
||||
gomega.Expect(failures).To(gomega.HaveLen(1))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("TestConfigGetInvalidQueryValue", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestConfigGetInvalidQueryValue(&config)
|
||||
})
|
||||
gomega.Expect(failures).To(gomega.HaveLen(1))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("TestConfigSetDefaultValues", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestConfigSetDefaultValues(&config)
|
||||
})
|
||||
gomega.Expect(failures).NotTo(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("TestConfigGetEnumsCount", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestConfigGetEnumsCount(&config, 99)
|
||||
})
|
||||
gomega.Expect(failures).NotTo(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("TestConfigGetFieldsCount", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestConfigGetFieldsCount(&config, 99)
|
||||
})
|
||||
gomega.Expect(failures).NotTo(gomega.BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("Service test helpers", func() {
|
||||
var service dummyService
|
||||
ginkgo.BeforeEach(func() {
|
||||
service = dummyService{}
|
||||
})
|
||||
ginkgo.Describe("TestConfigSetInvalidQueryValue", func() {
|
||||
ginkgo.It("should fail when not correctly implemented", func() {
|
||||
failures := gomega.InterceptGomegaFailures(func() {
|
||||
testutils.TestServiceSetInvalidParamValue(&service, "invalid", "value")
|
||||
})
|
||||
gomega.Expect(failures).To(gomega.HaveLen(1))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
type dummyConfig struct {
|
||||
standard.EnumlessConfig
|
||||
Foo uint64 `default:"-1" key:"foo"`
|
||||
}
|
||||
|
||||
func (dc *dummyConfig) GetURL() *url.URL { return &url.URL{} }
|
||||
func (dc *dummyConfig) SetURL(_ *url.URL) error { return nil }
|
||||
func (dc *dummyConfig) Get(string) (string, error) { return "", nil }
|
||||
func (dc *dummyConfig) Set(string, string) error { return nil }
|
||||
func (dc *dummyConfig) QueryFields() []string { return []string{} }
|
||||
|
||||
type dummyService struct {
|
||||
standard.Standard
|
||||
Config dummyConfig
|
||||
}
|
||||
|
||||
func (s *dummyService) Initialize(_ *url.URL, _ types.StdLogger) error { return nil }
|
||||
func (s *dummyService) Send(_ string, _ *types.Params) error { return nil }
|
||||
func (s *dummyService) GetID() string { return "dummy" }
|
106
internal/testutils/textconfaker.go
Normal file
106
internal/testutils/textconfaker.go
Normal file
|
@ -0,0 +1,106 @@
|
|||
package testutils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type textConFaker struct {
|
||||
inputBuffer *bytes.Buffer
|
||||
inputWriter *bufio.Writer
|
||||
outputReader *bufio.Reader
|
||||
responses []string
|
||||
delim string
|
||||
}
|
||||
|
||||
func (tcf *textConFaker) GetInput() string {
|
||||
_ = tcf.inputWriter.Flush()
|
||||
|
||||
return tcf.inputBuffer.String()
|
||||
}
|
||||
|
||||
// GetConversation returns the input and output streams as a conversation.
|
||||
func (tcf *textConFaker) GetConversation(includeGreeting bool) string {
|
||||
conv := ""
|
||||
inSequence := false
|
||||
input := strings.Split(tcf.GetInput(), tcf.delim)
|
||||
responseIndex := 0
|
||||
|
||||
if includeGreeting {
|
||||
conv += fmt.Sprintf(" %-55s << %-50s\n", "(server greeting)", tcf.responses[0])
|
||||
responseIndex = 1
|
||||
}
|
||||
|
||||
for i, query := range input {
|
||||
if query == "." {
|
||||
inSequence = false
|
||||
}
|
||||
|
||||
resp := ""
|
||||
if len(tcf.responses) > responseIndex && !inSequence {
|
||||
resp = tcf.responses[responseIndex]
|
||||
}
|
||||
|
||||
if query == "" && resp == "" && i == len(input)-1 {
|
||||
break
|
||||
}
|
||||
|
||||
conv += fmt.Sprintf(" #%2d >> %50s << %-50s\n", i, query, resp)
|
||||
|
||||
for len(resp) > 3 && resp[3] == '-' {
|
||||
responseIndex++
|
||||
resp = tcf.responses[responseIndex]
|
||||
conv += fmt.Sprintf(" %50s << %-50s\n", " ", resp)
|
||||
}
|
||||
|
||||
if !inSequence {
|
||||
responseIndex++
|
||||
}
|
||||
|
||||
if len(resp) > 0 && resp[0] == '3' {
|
||||
inSequence = true
|
||||
}
|
||||
}
|
||||
|
||||
return conv
|
||||
}
|
||||
|
||||
// GetClientSentences returns all the input received from the client separated by the delimiter.
|
||||
func (tcf *textConFaker) GetClientSentences() []string {
|
||||
_ = tcf.inputWriter.Flush()
|
||||
|
||||
return strings.Split(tcf.inputBuffer.String(), tcf.delim)
|
||||
}
|
||||
|
||||
// CreateReadWriter returns a ReadWriter from the textConFakers internal reader and writer.
|
||||
func (tcf *textConFaker) CreateReadWriter() *bufio.ReadWriter {
|
||||
return bufio.NewReadWriter(tcf.outputReader, tcf.inputWriter)
|
||||
}
|
||||
|
||||
func (tcf *textConFaker) init() {
|
||||
tcf.inputBuffer = &bytes.Buffer{}
|
||||
stringReader := strings.NewReader(strings.Join(tcf.responses, tcf.delim))
|
||||
tcf.outputReader = bufio.NewReader(stringReader)
|
||||
tcf.inputWriter = bufio.NewWriter(tcf.inputBuffer)
|
||||
}
|
||||
|
||||
// CreateTextConFaker returns a textproto.Conn to fake textproto based connections.
|
||||
func CreateTextConFaker(responses []string, delim string) (*textproto.Conn, Eavesdropper) {
|
||||
tcfaker := textConFaker{
|
||||
responses: responses,
|
||||
delim: delim,
|
||||
}
|
||||
tcfaker.init()
|
||||
|
||||
// rx := iotest.NewReadLogger("TextConRx", tcfaker.outputReader)
|
||||
// tx := iotest.NewWriteLogger("TextConTx", tcfaker.inputWriter)
|
||||
// faker := CreateIOFaker(rx, tx)
|
||||
faker := ioFaker{
|
||||
ReadWriter: tcfaker.CreateReadWriter(),
|
||||
}
|
||||
|
||||
return textproto.NewConn(faker), &tcfaker
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue