402 lines
12 KiB
Go
402 lines
12 KiB
Go
package zulip
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
"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/types"
|
|
)
|
|
|
|
func TestZulip(t *testing.T) {
|
|
gomega.RegisterFailHandler(ginkgo.Fail)
|
|
ginkgo.RunSpecs(t, "Shoutrrr Zulip Suite")
|
|
}
|
|
|
|
var (
|
|
service *Service
|
|
envZulipURL *url.URL
|
|
)
|
|
|
|
var _ = ginkgo.BeforeSuite(func() {
|
|
service = &Service{}
|
|
envZulipURL, _ = url.Parse(os.Getenv("SHOUTRRR_ZULIP_URL"))
|
|
})
|
|
|
|
// Helper function to create Zulip URLs with optional overrides.
|
|
func createZulipURL(botMail, botKey, host, stream, topic string) *url.URL {
|
|
query := url.Values{}
|
|
if stream != "" {
|
|
query.Set("stream", stream)
|
|
}
|
|
|
|
if topic != "" {
|
|
query.Set("topic", topic)
|
|
}
|
|
|
|
u := &url.URL{
|
|
Scheme: "zulip",
|
|
User: url.UserPassword(botMail, botKey),
|
|
Host: host,
|
|
RawQuery: query.Encode(),
|
|
}
|
|
|
|
return u
|
|
}
|
|
|
|
var _ = ginkgo.Describe("the zulip service", func() {
|
|
ginkgo.When("running integration tests", func() {
|
|
ginkgo.It("should not error out", func() {
|
|
if envZulipURL.String() == "" {
|
|
return
|
|
}
|
|
serviceURL, _ := url.Parse(envZulipURL.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.When("given a service url with missing parts", func() {
|
|
ginkgo.It("should return an error if bot mail is missing", func() {
|
|
zulipURL := createZulipURL(
|
|
"",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
expectErrorMessageGivenURL("bot mail missing from config URL", zulipURL)
|
|
})
|
|
ginkgo.It("should return an error if api key is missing", func() {
|
|
zulipURL := &url.URL{
|
|
Scheme: "zulip",
|
|
User: url.User("bot-name@zulipchat.com"),
|
|
Host: "example.zulipchat.com",
|
|
RawQuery: url.Values{
|
|
"stream": []string{"foo"},
|
|
"topic": []string{"bar"},
|
|
}.Encode(),
|
|
}
|
|
expectErrorMessageGivenURL("API key missing from config URL", zulipURL)
|
|
})
|
|
ginkgo.It("should return an error if host is missing", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
expectErrorMessageGivenURL("host missing from config URL", zulipURL)
|
|
})
|
|
})
|
|
ginkgo.When("given a valid service url is provided", func() {
|
|
ginkgo.It("should not return an error", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
})
|
|
ginkgo.It("should not return an error with a different bot key", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"differentkey123456789",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
})
|
|
})
|
|
ginkgo.When("sending a message", func() {
|
|
ginkgo.It("should error if topic exceeds max length", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
longTopic := strings.Repeat("a", topicMaxLength+1) // 61 chars
|
|
params := &types.Params{"topic": longTopic}
|
|
err = service.Send("test message", params)
|
|
gomega.Expect(err).To(gomega.HaveOccurred())
|
|
gomega.Expect(err.Error()).To(gomega.Equal(
|
|
fmt.Sprintf(
|
|
"topic exceeds max length: %d characters, got %d",
|
|
topicMaxLength,
|
|
len([]rune(longTopic)),
|
|
),
|
|
))
|
|
})
|
|
ginkgo.It("should error if message exceeds max size", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
longMessage := strings.Repeat("a", contentMaxSize+1) // 10001 bytes
|
|
err = service.Send(longMessage, nil)
|
|
gomega.Expect(err).To(gomega.HaveOccurred())
|
|
gomega.Expect(err.Error()).To(gomega.Equal(
|
|
fmt.Sprintf(
|
|
"message exceeds max size: %d bytes, got %d bytes",
|
|
contentMaxSize,
|
|
len(longMessage),
|
|
),
|
|
))
|
|
})
|
|
ginkgo.It("should override stream from params", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"original",
|
|
"",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
params := &types.Params{"stream": "newstream"}
|
|
httpmock.Activate()
|
|
defer httpmock.DeactivateAndReset()
|
|
apiURL := service.getAPIURL(&Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "newstream",
|
|
})
|
|
httpmock.RegisterResponder(
|
|
"POST",
|
|
apiURL,
|
|
httpmock.NewStringResponder(http.StatusOK, ""),
|
|
)
|
|
err = service.Send("test message", params)
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
})
|
|
ginkgo.It("should override topic from params", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"original",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
params := &types.Params{"topic": "newtopic"}
|
|
httpmock.Activate()
|
|
defer httpmock.DeactivateAndReset()
|
|
config := &Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "newtopic",
|
|
}
|
|
apiURL := service.getAPIURL(config)
|
|
httpmock.RegisterResponder(
|
|
"POST",
|
|
apiURL,
|
|
func(req *http.Request) (*http.Response, error) {
|
|
gomega.Expect(req.FormValue("topic")).To(gomega.Equal("newtopic"))
|
|
|
|
return httpmock.NewStringResponse(http.StatusOK, ""), nil
|
|
},
|
|
)
|
|
err = service.Send("test message", params)
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
})
|
|
ginkgo.It("should handle HTTP errors", func() {
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
httpmock.Activate()
|
|
defer httpmock.DeactivateAndReset()
|
|
apiURL := service.getAPIURL(service.Config)
|
|
httpmock.RegisterResponder(
|
|
"POST",
|
|
apiURL,
|
|
httpmock.NewStringResponder(http.StatusBadRequest, "Bad Request"),
|
|
)
|
|
err = service.Send("test message", nil)
|
|
gomega.Expect(err).To(gomega.HaveOccurred())
|
|
gomega.Expect(err.Error()).To(gomega.ContainSubstring(
|
|
"failed to send zulip message: response status code unexpected: 400",
|
|
))
|
|
})
|
|
})
|
|
ginkgo.Describe("the zulip config", func() {
|
|
ginkgo.When("cloning a config object", func() {
|
|
ginkgo.It("the clone should have equal values", func() {
|
|
// Covers zulip_config.go:75-84 (Clone equality)
|
|
config1 := &Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
config2 := config1.Clone()
|
|
gomega.Expect(config1).To(gomega.Equal(config2))
|
|
})
|
|
ginkgo.It("the clone should not be the same struct", func() {
|
|
// Covers zulip_config.go:75-84 (Clone identity)
|
|
config1 := &Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
config2 := config1.Clone()
|
|
gomega.Expect(config1).NotTo(gomega.BeIdenticalTo(config2))
|
|
})
|
|
})
|
|
ginkgo.When("generating a config object", func() {
|
|
ginkgo.It("should generate a correct config object using CreateConfigFromURL", func() {
|
|
// Covers zulip_config.go:92-98 (CreateConfigFromURL), zulip_config.go:49-72 (setURL)
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
serviceConfig, err := CreateConfigFromURL(zulipURL)
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
config := &Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
gomega.Expect(serviceConfig).To(gomega.Equal(config))
|
|
})
|
|
ginkgo.It("should update config correctly using SetURL", func() {
|
|
// Covers zulip_config.go:27-29 (SetURL), zulip_config.go:49-72 (setURL)
|
|
config := &Config{} // Start with empty config
|
|
zulipURL := createZulipURL(
|
|
"bot-name@zulipchat.com",
|
|
"correcthorsebatterystable",
|
|
"example.zulipchat.com",
|
|
"foo",
|
|
"bar",
|
|
)
|
|
err := config.SetURL(zulipURL)
|
|
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
|
|
|
expected := &Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
gomega.Expect(config).To(gomega.Equal(expected))
|
|
})
|
|
})
|
|
ginkgo.When("given a config object with stream and topic", func() {
|
|
ginkgo.It("should build the correct service url", func() {
|
|
// Covers zulip_config.go:27-46 (GetURL with Topic)
|
|
config := Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
url := config.GetURL()
|
|
gomega.Expect(url.String()).
|
|
To(gomega.Equal("zulip://bot-name%40zulipchat.com:correcthorsebatterystable@example.zulipchat.com?stream=foo&topic=bar"))
|
|
})
|
|
})
|
|
ginkgo.When("given a config object with stream but without topic", func() {
|
|
ginkgo.It("should build the correct service url", func() {
|
|
// Covers zulip_config.go:27-46 (GetURL without Topic)
|
|
config := Config{
|
|
BotMail: "bot-name@zulipchat.com",
|
|
BotKey: "correcthorsebatterystable",
|
|
Host: "example.zulipchat.com",
|
|
Stream: "foo",
|
|
}
|
|
url := config.GetURL()
|
|
gomega.Expect(url.String()).
|
|
To(gomega.Equal("zulip://bot-name%40zulipchat.com:correcthorsebatterystable@example.zulipchat.com?stream=foo"))
|
|
})
|
|
})
|
|
})
|
|
ginkgo.Describe("the zulip payload", func() {
|
|
ginkgo.When("creating a payload with topic", func() {
|
|
ginkgo.It("should include all fields", func() {
|
|
// Covers zulip_payload.go:7-18 (CreatePayload with Topic)
|
|
config := &Config{
|
|
Stream: "foo",
|
|
Topic: "bar",
|
|
}
|
|
payload := CreatePayload(config, "test message")
|
|
gomega.Expect(payload.Get("type")).To(gomega.Equal("stream"))
|
|
gomega.Expect(payload.Get("to")).To(gomega.Equal("foo"))
|
|
gomega.Expect(payload.Get("content")).To(gomega.Equal("test message"))
|
|
gomega.Expect(payload.Get("topic")).To(gomega.Equal("bar"))
|
|
})
|
|
})
|
|
ginkgo.When("creating a payload without topic", func() {
|
|
ginkgo.It("should exclude topic field", func() {
|
|
// Covers zulip_payload.go:7-18 (CreatePayload without Topic)
|
|
config := &Config{
|
|
Stream: "foo",
|
|
}
|
|
payload := CreatePayload(config, "test message")
|
|
gomega.Expect(payload.Get("type")).To(gomega.Equal("stream"))
|
|
gomega.Expect(payload.Get("to")).To(gomega.Equal("foo"))
|
|
gomega.Expect(payload.Get("content")).To(gomega.Equal("test message"))
|
|
gomega.Expect(payload.Get("topic")).To(gomega.Equal(""))
|
|
})
|
|
})
|
|
})
|
|
ginkgo.It("should return the correct service ID", func() {
|
|
service := &Service{}
|
|
gomega.Expect(service.GetID()).To(gomega.Equal("zulip"))
|
|
})
|
|
})
|
|
|
|
func expectErrorMessageGivenURL(msg ErrorMessage, zulipURL *url.URL) {
|
|
err := service.Initialize(zulipURL, testutils.TestLogger())
|
|
gomega.Expect(err).To(gomega.HaveOccurred())
|
|
gomega.Expect(err.Error()).To(gomega.Equal(string(msg)))
|
|
}
|