1
0
Fork 0
golang-github-nicholas-fedo.../pkg/services/slack/slack_test.go
Daniel Baumann c0c4addb85
Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-22 10:16:14 +02:00

332 lines
11 KiB
Go

package slack_test
import (
"errors"
"fmt"
"log"
"net/url"
"os"
"strings"
"testing"
"github.com/jarcoal/httpmock"
"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
"github.com/onsi/gomega/format"
"github.com/nicholas-fedor/shoutrrr/internal/testutils"
"github.com/nicholas-fedor/shoutrrr/pkg/services/slack"
)
const (
TestWebhookURL = "https://hooks.slack.com/services/AAAAAAAAA/BBBBBBBBB/123456789123456789123456"
)
func TestSlack(t *testing.T) {
format.CharactersAroundMismatchToInclude = 20
gomega.RegisterFailHandler(ginkgo.Fail)
ginkgo.RunSpecs(t, "Shoutrrr Slack Suite")
}
var (
service *slack.Service
envSlackURL *url.URL
logger *log.Logger
_ = ginkgo.BeforeSuite(func() {
service = &slack.Service{}
logger = log.New(ginkgo.GinkgoWriter, "Test", log.LstdFlags)
envSlackURL, _ = url.Parse(os.Getenv("SHOUTRRR_SLACK_URL"))
})
)
var _ = ginkgo.Describe("the slack service", func() {
ginkgo.When("running integration tests", func() {
ginkgo.It("should not error out", func() {
if envSlackURL.String() == "" {
return
}
serviceURL, _ := url.Parse(envSlackURL.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.It("returns the correct service identifier", func() {
gomega.Expect(service.GetID()).To(gomega.Equal("slack"))
})
})
// xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N
ginkgo.When("given a token with a malformed part", func() {
ginkgo.It("should return an error if part A is not 9 letters", func() {
expectErrorMessageGivenURL(
slack.ErrInvalidToken,
"slack://lol@12345678/123456789/123456789123456789123456",
)
})
ginkgo.It("should return an error if part B is not 9 letters", func() {
expectErrorMessageGivenURL(
slack.ErrInvalidToken,
"slack://lol@123456789/12345678/123456789123456789123456",
)
})
ginkgo.It("should return an error if part C is not 24 letters", func() {
expectErrorMessageGivenURL(
slack.ErrInvalidToken,
"slack://123456789/123456789/12345678912345678912345",
)
})
})
ginkgo.When("given a token missing a part", func() {
ginkgo.It("should return an error if the missing part is A", func() {
expectErrorMessageGivenURL(
slack.ErrInvalidToken,
"slack://lol@/123456789/123456789123456789123456",
)
})
ginkgo.It("should return an error if the missing part is B", func() {
expectErrorMessageGivenURL(slack.ErrInvalidToken, "slack://lol@123456789//123456789")
})
ginkgo.It("should return an error if the missing part is C", func() {
expectErrorMessageGivenURL(slack.ErrInvalidToken, "slack://lol@123456789/123456789/")
})
})
ginkgo.Describe("the slack config", func() {
ginkgo.When("parsing the configuration URL", func() {
ginkgo.When("given a config using the legacy format", func() {
ginkgo.It("should be converted to the new format after de-/serialization", func() {
oldURL := "slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456?color=3f00fe&title=Test+title"
newURL := "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?botname=testbot&color=3f00fe&title=Test+title"
config := &slack.Config{}
err := config.SetURL(testutils.URLMust(oldURL))
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "verifying")
gomega.Expect(config.GetURL().String()).To(gomega.Equal(newURL))
})
})
})
ginkgo.When("the URL contains an invalid property", func() {
testURL := testutils.URLMust(
"slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?bass=dirty",
)
err := (&slack.Config{}).SetURL(testURL)
gomega.Expect(err).To(gomega.HaveOccurred())
})
ginkgo.It("should be identical after de-/serialization", func() {
testURL := "slack://hook:AAAAAAAAA-BBBBBBBBB-123456789123456789123456@webhook?botname=testbot&color=3f00fe&title=Test+title"
config := &slack.Config{}
err := config.SetURL(testutils.URLMust(testURL))
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "verifying")
outputURL := config.GetURL()
gomega.Expect(outputURL.String()).To(gomega.Equal(testURL))
})
ginkgo.When("generating a config object", func() {
ginkgo.It(
"should use the default botname if the argument list contains three strings",
func() {
slackURL, _ := url.Parse("slack://AAAAAAAAA/BBBBBBBBB/123456789123456789123456")
config, configError := slack.CreateConfigFromURL(slackURL)
gomega.Expect(configError).NotTo(gomega.HaveOccurred())
gomega.Expect(config.BotName).To(gomega.BeEmpty())
},
)
ginkgo.It("should set the botname if the argument list is three", func() {
slackURL, _ := url.Parse(
"slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456",
)
config, configError := slack.CreateConfigFromURL(slackURL)
gomega.Expect(configError).NotTo(gomega.HaveOccurred())
gomega.Expect(config.BotName).To(gomega.Equal("testbot"))
})
ginkgo.It("should return an error if the argument list is shorter than three", func() {
slackURL, _ := url.Parse("slack://AAAAAAAA")
_, configError := slack.CreateConfigFromURL(slackURL)
gomega.Expect(configError).To(gomega.HaveOccurred())
})
})
ginkgo.When("getting credentials from token", func() {
ginkgo.It("should return a valid webhook URL for the given token", func() {
token := tokenMust("AAAAAAAAA/BBBBBBBBB/123456789123456789123456")
gomega.Expect(token.WebhookURL()).To(gomega.Equal(TestWebhookURL))
})
ginkgo.It(
"should return a valid authorization header value for the given token",
func() {
token := tokenMust("xoxb:AAAAAAAAA-BBBBBBBBB-123456789123456789123456")
expected := "Bearer xoxb-AAAAAAAAA-BBBBBBBBB-123456789123456789123456"
gomega.Expect(token.Authorization()).To(gomega.Equal(expected))
},
)
})
})
ginkgo.Describe("creating the payload", func() {
ginkgo.Describe("the icon fields", func() {
payload := slack.MessagePayload{}
ginkgo.It("should set IconURL when the configured icon looks like an URL", func() {
payload.SetIcon("https://example.com/logo.png")
gomega.Expect(payload.IconURL).To(gomega.Equal("https://example.com/logo.png"))
gomega.Expect(payload.IconEmoji).To(gomega.BeEmpty())
})
ginkgo.It(
"should set IconEmoji when the configured icon does not look like an URL",
func() {
payload.SetIcon("tanabata_tree")
gomega.Expect(payload.IconEmoji).To(gomega.Equal("tanabata_tree"))
gomega.Expect(payload.IconURL).To(gomega.BeEmpty())
},
)
ginkgo.It("should clear both fields when icon is empty", func() {
payload.SetIcon("")
gomega.Expect(payload.IconEmoji).To(gomega.BeEmpty())
gomega.Expect(payload.IconURL).To(gomega.BeEmpty())
})
})
ginkgo.When("when more than 99 lines are being sent", func() {
ginkgo.It("should append the exceeding lines to the last attachment", func() {
config := slack.Config{}
sb := strings.Builder{}
for i := 1; i <= 110; i++ {
sb.WriteString(fmt.Sprintf("Line %d\n", i))
}
payload := slack.CreateJSONPayload(&config, sb.String()).(slack.MessagePayload)
atts := payload.Attachments
fmt.Fprint(
ginkgo.GinkgoWriter,
"\nLines: ",
len(atts),
" Last: ",
atts[len(atts)-1],
"\n",
)
gomega.Expect(atts).To(gomega.HaveLen(100))
gomega.Expect(atts[len(atts)-1].Text).To(gomega.ContainSubstring("Line 110"))
})
})
ginkgo.When("when the last message line ends with a newline", func() {
ginkgo.It("should not send an empty attachment", func() {
payload := slack.CreateJSONPayload(&slack.Config{}, "One\nTwo\nThree\n").(slack.MessagePayload)
atts := payload.Attachments
gomega.Expect(atts[len(atts)-1].Text).NotTo(gomega.BeEmpty())
})
})
})
ginkgo.Describe("sending the payload", func() {
ginkgo.When("sending via webhook URL", func() {
var err error
ginkgo.BeforeEach(func() {
httpmock.Activate()
})
ginkgo.AfterEach(func() {
httpmock.DeactivateAndReset()
})
ginkgo.It("should not report an error if the server accepts the payload", func() {
serviceURL, _ := url.Parse(
"slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456",
)
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("should not panic if an error occurs when sending the payload", func() {
serviceURL, _ := url.Parse(
"slack://testbot@AAAAAAAAA/BBBBBBBBB/123456789123456789123456",
)
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.When("sending via bot API", func() {
var err error
ginkgo.BeforeEach(func() {
httpmock.Activate()
})
ginkgo.AfterEach(func() {
httpmock.DeactivateAndReset()
})
ginkgo.It("should not report an error if the server accepts the payload", func() {
serviceURL := testutils.URLMust(
"slack://xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N@C0123456789",
)
err = service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
targetURL := "https://slack.com/api/chat.postMessage"
httpmock.RegisterResponder(
"POST",
targetURL,
testutils.JSONRespondMust(200, slack.APIResponse{
Ok: true,
}),
)
err = service.Send("Message", nil)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
ginkgo.It("should not panic if an error occurs when sending the payload", func() {
serviceURL := testutils.URLMust(
"slack://xoxb:123456789012-1234567890123-4mt0t4l1YL3g1T5L4cK70k3N@C0123456789",
)
err = service.Initialize(serviceURL, logger)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
targetURL := "https://slack.com/api/chat.postMessage"
httpmock.RegisterResponder(
"POST",
targetURL,
testutils.JSONRespondMust(200, slack.APIResponse{
Error: "someone turned off the internet",
}),
)
err = service.Send("Message", nil)
gomega.Expect(err).To(gomega.HaveOccurred())
})
})
})
})
func tokenMust(rawToken string) *slack.Token {
token, err := slack.ParseToken(rawToken)
gomega.ExpectWithOffset(1, err).NotTo(gomega.HaveOccurred())
return token
}
func expectErrorMessageGivenURL(expected error, rawURL string) {
err := service.Initialize(testutils.URLMust(rawURL), testutils.TestLogger())
gomega.ExpectWithOffset(1, err).To(gomega.HaveOccurred())
gomega.ExpectWithOffset(1, err).To(gomega.Equal(expected))
}