1
0
Fork 0

Adding upstream version 0.8.9.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-22 10:16:14 +02:00
parent 3b2c48b5e4
commit c0c4addb85
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
285 changed files with 25880 additions and 0 deletions

View file

@ -0,0 +1,359 @@
package generic_test
import (
"errors"
"io"
"log"
"net/http"
"net/url"
"os"
"testing"
"github.com/jarcoal/httpmock"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/nicholas-fedor/shoutrrr/internal/testutils"
"github.com/nicholas-fedor/shoutrrr/pkg/services/generic"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
// Test constants.
const (
TestWebhookURL = "https://host.tld/webhook" // Default test webhook URL
)
// TestGeneric runs the Ginkgo test suite for the generic package.
func TestGeneric(t *testing.T) {
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Shoutrrr Generic Webhook Suite")
}
var (
service *generic.Service
logger *log.Logger
envGenericURL *url.URL
_ = ginkgo.BeforeSuite(func() {
service = &generic.Service{}
logger = log.New(ginkgo.GinkgoWriter, "Test", log.LstdFlags)
var err error
envGenericURL, err = url.Parse(os.Getenv("SHOUTRRR_GENERIC_URL"))
if err != nil {
envGenericURL = &url.URL{} // Default to empty URL if parsing fails
}
})
)
var _ = ginkgo.Describe("the generic service", func() {
ginkgo.When("running integration tests", func() {
ginkgo.It("sends a message successfully with a valid ENV URL", func() {
if envGenericURL.String() == "" {
ginkgo.Skip("No integration test ENV URL was set")
return
}
serviceURL := testutils.URLMust(envGenericURL.String())
err := service.Initialize(serviceURL, testutils.TestLogger())
gomega.Expect(err).NotTo(gomega.HaveOccurred())
err = service.Send("This is an integration test message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
})
ginkgo.Describe("the service", func() {
ginkgo.BeforeEach(func() {
service = &generic.Service{}
service.SetLogger(logger)
})
ginkgo.It("returns the correct service identifier", func() {
gomega.Expect(service.GetID()).To(gomega.Equal("generic"))
})
})
ginkgo.When("parsing a custom URL", func() {
ginkgo.BeforeEach(func() {
service = &generic.Service{}
service.SetLogger(logger)
})
ginkgo.It("correctly sets webhook URL from custom URL", func() {
customURL := testutils.URLMust("generic+https://test.tld")
serviceURL, err := service.GetConfigURLFromCustom(customURL)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
err = service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.WebhookURL().String()).To(gomega.Equal("https://test.tld"))
})
ginkgo.When("a HTTP URL is provided via query parameter", func() {
ginkgo.It("disables TLS", func() {
config := &generic.Config{}
err := config.SetURL(testutils.URLMust("generic://example.com?disabletls=yes"))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(config.DisableTLS).To(gomega.BeTrue())
})
})
ginkgo.When("a HTTPS URL is provided", func() {
ginkgo.It("enables TLS", func() {
config := &generic.Config{}
err := config.SetURL(testutils.URLMust("generic://example.com"))
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(config.DisableTLS).To(gomega.BeFalse())
})
})
ginkgo.It("escapes conflicting custom query keys", func() {
serviceURL := testutils.URLMust("generic://example.com/?__template=passed")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.Template).NotTo(gomega.Equal("passed"))
whURL := service.Config.WebhookURL().String()
gomega.Expect(whURL).To(gomega.Equal("https://example.com/?template=passed"))
gomega.Expect(service.Config.GetURL().String()).To(gomega.Equal(serviceURL.String()))
})
ginkgo.It("handles both escaped and service prop versions of keys", func() {
serviceURL := testutils.URLMust(
"generic://example.com/?__template=passed&template=captured",
)
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.Template).To(gomega.Equal("captured"))
whURL := service.Config.WebhookURL().String()
gomega.Expect(whURL).To(gomega.Equal("https://example.com/?template=passed"))
})
})
ginkgo.When("retrieving the webhook URL", func() {
ginkgo.BeforeEach(func() {
service = &generic.Service{}
service.SetLogger(logger)
})
ginkgo.It("builds a valid webhook URL", func() {
serviceURL := testutils.URLMust("generic://example.com/path?foo=bar")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.WebhookURL().String()).
To(gomega.Equal("https://example.com/path?foo=bar"))
})
ginkgo.When("TLS is disabled", func() {
ginkgo.It("uses http scheme", func() {
serviceURL := testutils.URLMust("generic://test.tld?disabletls=yes")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.WebhookURL().Scheme).To(gomega.Equal("http"))
})
})
ginkgo.When("TLS is not disabled", func() {
ginkgo.It("uses https scheme", func() {
serviceURL := testutils.URLMust("generic://test.tld")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.WebhookURL().Scheme).To(gomega.Equal("https"))
})
})
})
ginkgo.Describe("the generic config", func() {
ginkgo.When("parsing the configuration URL", func() {
ginkgo.It("is identical after de-/serialization", func() {
testURL := "generic://user:pass@host.tld/api/v1/webhook?$context=inside-joke&@Authorization=frend&__title=w&contenttype=a%2Fb&template=f&title=t"
expectedURL := "generic://user:pass@host.tld/api/v1/webhook?%24context=inside-joke&%40Authorization=frend&__title=w&contenttype=a%2Fb&template=f&title=t"
serviceURL := testutils.URLMust(testURL)
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(service.Config.GetURL().String()).To(gomega.Equal(expectedURL))
})
})
})
ginkgo.Describe("building the payload", func() {
ginkgo.BeforeEach(func() {
service = &generic.Service{}
service.SetLogger(logger)
})
ginkgo.When("no template is specified", func() {
ginkgo.It("uses the message as payload", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
payload, err := service.GetPayload(
service.Config,
types.Params{"message": "test message"},
)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
contents, err := io.ReadAll(payload)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(contents)).To(gomega.Equal("test message"))
})
})
ginkgo.When("template is specified as `JSON`", func() {
ginkgo.It("creates a JSON object as the payload", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?template=JSON")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
params := types.Params{"title": "test title", "message": "test message"}
payload, err := service.GetPayload(service.Config, params)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
contents, err := io.ReadAll(payload)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(contents)).To(gomega.MatchJSON(`{
"title": "test title",
"message": "test message"
}`))
})
ginkgo.When("alternate keys are specified", func() {
ginkgo.It("creates a JSON object using the specified keys", func() {
serviceURL := testutils.URLMust(
"generic://host.tld/webhook?template=JSON&messagekey=body&titlekey=header",
)
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
params := types.Params{"header": "test title", "body": "test message"}
payload, err := service.GetPayload(service.Config, params)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
contents, err := io.ReadAll(payload)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(contents)).To(gomega.MatchJSON(`{
"header": "test title",
"body": "test message"
}`))
})
})
})
ginkgo.When("a valid template is specified", func() {
ginkgo.It("applies the template to the message payload", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?template=news")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
err = service.SetTemplateString("news", `{{.title}} ==> {{.message}}`)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
params := types.Params{"title": "BREAKING NEWS", "message": "it's today!"}
payload, err := service.GetPayload(service.Config, params)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
contents, err := io.ReadAll(payload)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(contents)).To(gomega.Equal("BREAKING NEWS ==> it's today!"))
})
ginkgo.When("given nil params", func() {
ginkgo.It("applies template with message data", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?template=arrows")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
err = service.SetTemplateString("arrows", `==> {{.message}} <==`)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
payload, err := service.GetPayload(
service.Config,
types.Params{"message": "LOOK AT ME"},
)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
contents, err := io.ReadAll(payload)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(contents)).To(gomega.Equal("==> LOOK AT ME <=="))
})
})
})
ginkgo.When("an unknown template is specified", func() {
ginkgo.It("returns an error", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?template=missing")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
_, err = service.GetPayload(service.Config, nil)
gomega.Expect(err).To(gomega.HaveOccurred())
})
})
})
ginkgo.Describe("sending the payload", func() {
ginkgo.BeforeEach(func() {
httpmock.Activate()
service = &generic.Service{}
service.SetLogger(logger)
})
ginkgo.AfterEach(func() {
httpmock.DeactivateAndReset()
})
ginkgo.When("sending via webhook URL", func() {
ginkgo.It("succeeds if the server accepts the payload", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder(
"POST",
TestWebhookURL,
httpmock.NewStringResponder(200, ""),
)
err = service.Send("Message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("reports an error if sending fails", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder(
"POST",
TestWebhookURL,
httpmock.NewErrorResponder(errors.New("dummy error")),
)
err = service.Send("Message", nil)
gomega.Expect(err).To(gomega.HaveOccurred())
})
ginkgo.It("includes custom headers in the request", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?@authorization=frend")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder("POST", TestWebhookURL,
func(req *http.Request) (*http.Response, error) {
gomega.Expect(req.Header.Get("Authorization")).To(gomega.Equal("frend"))
return httpmock.NewStringResponse(200, ""), nil
})
err = service.Send("Message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("includes extra data in JSON payload", func() {
serviceURL := testutils.URLMust(
"generic://host.tld/webhook?template=json&$context=inside+joke",
)
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder("POST", TestWebhookURL,
func(req *http.Request) (*http.Response, error) {
body, err := io.ReadAll(req.Body)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(string(body)).
To(gomega.MatchJSON(`{"message":"Message","context":"inside joke"}`))
return httpmock.NewStringResponse(200, ""), nil
})
err = service.Send("Message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("uses the configured HTTP method", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?method=GET")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder(
"GET",
TestWebhookURL,
httpmock.NewStringResponder(200, ""),
)
err = service.Send("Message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("does not mutate the given params", func() {
serviceURL := testutils.URLMust("generic://host.tld/webhook?method=GET")
err := service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
httpmock.RegisterResponder(
"GET",
TestWebhookURL,
httpmock.NewStringResponder(200, ""),
)
params := types.Params{"title": "TITLE"}
err = service.Send("Message", &params)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(params).To(gomega.Equal(types.Params{"title": "TITLE"}))
})
})
})
})