359 lines
14 KiB
Go
359 lines
14 KiB
Go
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", ¶ms)
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
gomega.Expect(params).To(gomega.Equal(types.Params{"title": "TITLE"}))
|
|
})
|
|
})
|
|
})
|
|
})
|