332 lines
11 KiB
Go
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))
|
|
}
|