Adding upstream version 0.8.9.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
3b2c48b5e4
commit
c0c4addb85
285 changed files with 25880 additions and 0 deletions
129
pkg/services/zulip/zulip.go
Normal file
129
pkg/services/zulip/zulip.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
package zulip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// contentMaxSize defines the maximum allowed message size in bytes.
|
||||
const (
|
||||
contentMaxSize = 10000 // bytes
|
||||
topicMaxLength = 60 // characters
|
||||
)
|
||||
|
||||
// ErrTopicTooLong indicates the topic exceeds the maximum allowed length.
|
||||
var (
|
||||
ErrTopicTooLong = errors.New("topic exceeds max length")
|
||||
ErrMessageTooLong = errors.New("message exceeds max size")
|
||||
ErrResponseStatusFailure = errors.New("response status code unexpected")
|
||||
ErrInvalidHost = errors.New("invalid host format")
|
||||
)
|
||||
|
||||
// hostValidator ensures the host is a valid hostname or domain.
|
||||
var hostValidator = regexp.MustCompile(
|
||||
`^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`,
|
||||
)
|
||||
|
||||
// Service sends notifications to a pre-configured Zulip channel or user.
|
||||
type Service struct {
|
||||
standard.Standard
|
||||
Config *Config
|
||||
}
|
||||
|
||||
// Send delivers a notification message to Zulip.
|
||||
func (service *Service) Send(message string, params *types.Params) error {
|
||||
// Clone the config to avoid modifying the original for this send operation.
|
||||
config := service.Config.Clone()
|
||||
|
||||
if params != nil {
|
||||
if stream, found := (*params)["stream"]; found {
|
||||
config.Stream = stream
|
||||
}
|
||||
|
||||
if topic, found := (*params)["topic"]; found {
|
||||
config.Topic = topic
|
||||
}
|
||||
}
|
||||
|
||||
topicLength := len([]rune(config.Topic))
|
||||
if topicLength > topicMaxLength {
|
||||
return fmt.Errorf("%w: %d characters, got %d", ErrTopicTooLong, topicMaxLength, topicLength)
|
||||
}
|
||||
|
||||
messageSize := len(message)
|
||||
if messageSize > contentMaxSize {
|
||||
return fmt.Errorf(
|
||||
"%w: %d bytes, got %d bytes",
|
||||
ErrMessageTooLong,
|
||||
contentMaxSize,
|
||||
messageSize,
|
||||
)
|
||||
}
|
||||
|
||||
return service.doSend(config, message)
|
||||
}
|
||||
|
||||
// Initialize configures the service with a URL and logger.
|
||||
func (service *Service) Initialize(configURL *url.URL, logger types.StdLogger) error {
|
||||
service.SetLogger(logger)
|
||||
service.Config = &Config{}
|
||||
|
||||
if err := service.Config.setURL(nil, configURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the identifier for this service.
|
||||
func (service *Service) GetID() string {
|
||||
return Scheme
|
||||
}
|
||||
|
||||
// doSend sends the notification to Zulip using the configured API URL.
|
||||
//
|
||||
//nolint:gosec,noctx // Ignoring G107: Potential HTTP request made with variable url
|
||||
func (service *Service) doSend(config *Config, message string) error {
|
||||
apiURL := service.getAPIURL(config)
|
||||
|
||||
// Validate the host to mitigate SSRF risks
|
||||
if !hostValidator.MatchString(config.Host) {
|
||||
return fmt.Errorf("%w: %q", ErrInvalidHost, config.Host)
|
||||
}
|
||||
|
||||
payload := CreatePayload(config, message)
|
||||
|
||||
res, err := http.Post(
|
||||
apiURL,
|
||||
"application/x-www-form-urlencoded",
|
||||
strings.NewReader(payload.Encode()),
|
||||
)
|
||||
if err == nil && res.StatusCode != http.StatusOK {
|
||||
err = fmt.Errorf("%w: %s", ErrResponseStatusFailure, res.Status)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send zulip message: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAPIURL constructs the API URL for Zulip based on the Config.
|
||||
func (service *Service) getAPIURL(config *Config) string {
|
||||
return (&url.URL{
|
||||
User: url.UserPassword(config.BotMail, config.BotKey),
|
||||
Host: config.Host,
|
||||
Path: "api/v1/messages",
|
||||
Scheme: "https",
|
||||
}).String()
|
||||
}
|
110
pkg/services/zulip/zulip_config.go
Normal file
110
pkg/services/zulip/zulip_config.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
package zulip
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/format"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// Scheme is the identifying part of this service's configuration URL.
|
||||
const Scheme = "zulip"
|
||||
|
||||
// Static errors for configuration validation.
|
||||
var (
|
||||
ErrMissingBotMail = errors.New("bot mail missing from config URL")
|
||||
ErrMissingAPIKey = errors.New("API key missing from config URL")
|
||||
ErrMissingHost = errors.New("host missing from config URL")
|
||||
)
|
||||
|
||||
// Config for the zulip service.
|
||||
type Config struct {
|
||||
standard.EnumlessConfig
|
||||
BotMail string `desc:"Bot e-mail address" url:"user"`
|
||||
BotKey string `desc:"API Key" url:"pass"`
|
||||
Host string `desc:"API server hostname" url:"host,port"`
|
||||
Stream string ` description:"Target stream name" key:"stream" optional:""`
|
||||
Topic string ` key:"topic,title" default:""`
|
||||
}
|
||||
|
||||
// GetURL returns a URL representation of its current field values.
|
||||
func (config *Config) GetURL() *url.URL {
|
||||
resolver := format.NewPropKeyResolver(config)
|
||||
|
||||
return config.getURL(&resolver)
|
||||
}
|
||||
|
||||
// SetURL updates a ServiceConfig from a URL representation of its field values.
|
||||
func (config *Config) SetURL(url *url.URL) error {
|
||||
resolver := format.NewPropKeyResolver(config)
|
||||
|
||||
return config.setURL(&resolver, url)
|
||||
}
|
||||
|
||||
// getURL constructs a URL from the Config's fields using the provided resolver.
|
||||
func (config *Config) getURL(_ types.ConfigQueryResolver) *url.URL {
|
||||
query := &url.Values{}
|
||||
if config.Stream != "" {
|
||||
query.Set("stream", config.Stream)
|
||||
}
|
||||
|
||||
if config.Topic != "" {
|
||||
query.Set("topic", config.Topic)
|
||||
}
|
||||
|
||||
return &url.URL{
|
||||
User: url.UserPassword(config.BotMail, config.BotKey),
|
||||
Host: config.Host,
|
||||
RawQuery: query.Encode(),
|
||||
Scheme: Scheme,
|
||||
}
|
||||
}
|
||||
|
||||
// setURL updates the Config from a URL using the provided resolver.
|
||||
func (config *Config) setURL(_ types.ConfigQueryResolver, serviceURL *url.URL) error {
|
||||
var isSet bool
|
||||
|
||||
config.BotMail = serviceURL.User.Username()
|
||||
config.BotKey, isSet = serviceURL.User.Password()
|
||||
config.Host = serviceURL.Hostname()
|
||||
|
||||
if serviceURL.String() != "zulip://dummy@dummy.com" {
|
||||
if config.BotMail == "" {
|
||||
return ErrMissingBotMail
|
||||
}
|
||||
|
||||
if !isSet {
|
||||
return ErrMissingAPIKey
|
||||
}
|
||||
|
||||
if config.Host == "" {
|
||||
return ErrMissingHost
|
||||
}
|
||||
}
|
||||
|
||||
config.Stream = serviceURL.Query().Get("stream")
|
||||
config.Topic = serviceURL.Query().Get("topic")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clone creates a copy of the Config.
|
||||
func (config *Config) Clone() *Config {
|
||||
return &Config{
|
||||
BotMail: config.BotMail,
|
||||
BotKey: config.BotKey,
|
||||
Host: config.Host,
|
||||
Stream: config.Stream,
|
||||
Topic: config.Topic,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateConfigFromURL creates a new Config from a URL for use within the zulip service.
|
||||
func CreateConfigFromURL(serviceURL *url.URL) (*Config, error) {
|
||||
config := Config{}
|
||||
err := config.setURL(nil, serviceURL)
|
||||
|
||||
return &config, err
|
||||
}
|
15
pkg/services/zulip/zulip_errors.go
Normal file
15
pkg/services/zulip/zulip_errors.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package zulip
|
||||
|
||||
// ErrorMessage for error events within the zulip service.
|
||||
type ErrorMessage string
|
||||
|
||||
const (
|
||||
// MissingAPIKey from the service URL.
|
||||
MissingAPIKey ErrorMessage = "missing API key"
|
||||
// MissingHost from the service URL.
|
||||
MissingHost ErrorMessage = "missing Zulip host"
|
||||
// MissingBotMail from the service URL.
|
||||
MissingBotMail ErrorMessage = "missing Bot mail address"
|
||||
// TopicTooLong if topic is more than 60 characters.
|
||||
TopicTooLong ErrorMessage = "topic exceeds max length (%d characters): was %d characters"
|
||||
)
|
19
pkg/services/zulip/zulip_payload.go
Normal file
19
pkg/services/zulip/zulip_payload.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package zulip
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// CreatePayload compatible with the zulip api.
|
||||
func CreatePayload(config *Config, message string) url.Values {
|
||||
form := url.Values{}
|
||||
form.Set("type", "stream")
|
||||
form.Set("to", config.Stream)
|
||||
form.Set("content", message)
|
||||
|
||||
if config.Topic != "" {
|
||||
form.Set("topic", config.Topic)
|
||||
}
|
||||
|
||||
return form
|
||||
}
|
402
pkg/services/zulip/zulip_test.go
Normal file
402
pkg/services/zulip/zulip_test.go
Normal file
|
@ -0,0 +1,402 @@
|
|||
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)))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue