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
79
pkg/services/matrix/matrix.go
Normal file
79
pkg/services/matrix/matrix.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"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 identifies this service in configuration URLs.
|
||||
const Scheme = "matrix"
|
||||
|
||||
// ErrClientNotInitialized indicates that the client is not initialized for sending messages.
|
||||
var ErrClientNotInitialized = errors.New("client not initialized; cannot send message")
|
||||
|
||||
// Service sends notifications via the Matrix protocol.
|
||||
type Service struct {
|
||||
standard.Standard
|
||||
Config *Config
|
||||
client *client
|
||||
pkr format.PropKeyResolver
|
||||
}
|
||||
|
||||
// Initialize configures the service with a URL and logger.
|
||||
func (s *Service) Initialize(configURL *url.URL, logger types.StdLogger) error {
|
||||
s.SetLogger(logger)
|
||||
s.Config = &Config{}
|
||||
s.pkr = format.NewPropKeyResolver(s.Config)
|
||||
|
||||
if err := s.Config.setURL(&s.pkr, configURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if configURL.String() != "matrix://dummy@dummy.com" {
|
||||
s.client = newClient(s.Config.Host, s.Config.DisableTLS, logger)
|
||||
if s.Config.User != "" {
|
||||
return s.client.login(s.Config.User, s.Config.Password)
|
||||
}
|
||||
|
||||
s.client.useToken(s.Config.Password)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID returns the identifier for this service.
|
||||
func (s *Service) GetID() string {
|
||||
return Scheme
|
||||
}
|
||||
|
||||
// Send delivers a notification message to Matrix rooms.
|
||||
func (s *Service) Send(message string, params *types.Params) error {
|
||||
config := *s.Config
|
||||
if err := s.pkr.UpdateConfigFromParams(&config, params); err != nil {
|
||||
return fmt.Errorf("updating config from params: %w", err)
|
||||
}
|
||||
|
||||
if s.client == nil {
|
||||
return ErrClientNotInitialized
|
||||
}
|
||||
|
||||
errors := s.client.sendMessage(message, s.Config.Rooms)
|
||||
if len(errors) > 0 {
|
||||
for _, err := range errors {
|
||||
s.Logf("error sending message: %w", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf(
|
||||
"%v error(s) sending message, with initial error: %w",
|
||||
len(errors),
|
||||
errors[0],
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
82
pkg/services/matrix/matrix_api.go
Normal file
82
pkg/services/matrix/matrix_api.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package matrix
|
||||
|
||||
type (
|
||||
messageType string
|
||||
flowType string
|
||||
identifierType string
|
||||
)
|
||||
|
||||
const (
|
||||
apiLogin = "/_matrix/client/r0/login"
|
||||
apiRoomJoin = "/_matrix/client/r0/join/%s"
|
||||
apiSendMessage = "/_matrix/client/r0/rooms/%s/send/m.room.message"
|
||||
apiJoinedRooms = "/_matrix/client/r0/joined_rooms"
|
||||
|
||||
contentType = "application/json"
|
||||
|
||||
accessTokenKey = "access_token"
|
||||
|
||||
msgTypeText messageType = "m.text"
|
||||
flowLoginPassword flowType = "m.login.password"
|
||||
idTypeUser identifierType = "m.id.user"
|
||||
)
|
||||
|
||||
type apiResLoginFlows struct {
|
||||
Flows []flow `json:"flows"`
|
||||
}
|
||||
|
||||
type apiReqLogin struct {
|
||||
Type flowType `json:"type"`
|
||||
Identifier *identifier `json:"identifier"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
type apiResLogin struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
HomeServer string `json:"home_server"`
|
||||
UserID string `json:"user_id"`
|
||||
DeviceID string `json:"device_id"`
|
||||
}
|
||||
|
||||
type apiReqSend struct {
|
||||
MsgType messageType `json:"msgtype"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type apiResRoom struct {
|
||||
RoomID string `json:"room_id"`
|
||||
}
|
||||
|
||||
type apiResJoinedRooms struct {
|
||||
Rooms []string `json:"joined_rooms"`
|
||||
}
|
||||
|
||||
type apiResEvent struct {
|
||||
EventID string `json:"event_id"`
|
||||
}
|
||||
|
||||
type apiResError struct {
|
||||
Message string `json:"error"`
|
||||
Code string `json:"errcode"`
|
||||
}
|
||||
|
||||
func (e *apiResError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
type flow struct {
|
||||
Type flowType `json:"type"`
|
||||
}
|
||||
|
||||
type identifier struct {
|
||||
Type identifierType `json:"type"`
|
||||
User string `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
func newUserIdentifier(user string) *identifier {
|
||||
return &identifier{
|
||||
Type: idTypeUser,
|
||||
User: user,
|
||||
}
|
||||
}
|
316
pkg/services/matrix/matrix_client.go
Normal file
316
pkg/services/matrix/matrix_client.go
Normal file
|
@ -0,0 +1,316 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/util"
|
||||
)
|
||||
|
||||
// schemeHTTPPrefixLength is the length of "http" in "https", used to strip TLS suffix.
|
||||
const (
|
||||
schemeHTTPPrefixLength = 4
|
||||
tokenHintLength = 3
|
||||
minSliceLength = 1
|
||||
httpClientErrorStatus = 400
|
||||
defaultHTTPTimeout = 10 * time.Second // defaultHTTPTimeout is the timeout for HTTP requests.
|
||||
)
|
||||
|
||||
// ErrUnsupportedLoginFlows indicates that none of the server login flows are supported.
|
||||
var (
|
||||
ErrUnsupportedLoginFlows = errors.New("none of the server login flows are supported")
|
||||
ErrUnexpectedStatus = errors.New("unexpected HTTP status")
|
||||
)
|
||||
|
||||
// client manages interactions with the Matrix API.
|
||||
type client struct {
|
||||
apiURL url.URL
|
||||
accessToken string
|
||||
logger types.StdLogger
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// newClient creates a new Matrix client with the specified host and TLS settings.
|
||||
func newClient(host string, disableTLS bool, logger types.StdLogger) *client {
|
||||
client := &client{
|
||||
logger: logger,
|
||||
apiURL: url.URL{
|
||||
Host: host,
|
||||
Scheme: "https",
|
||||
},
|
||||
httpClient: &http.Client{
|
||||
Timeout: defaultHTTPTimeout,
|
||||
},
|
||||
}
|
||||
|
||||
if client.logger == nil {
|
||||
client.logger = util.DiscardLogger
|
||||
}
|
||||
|
||||
if disableTLS {
|
||||
client.apiURL.Scheme = client.apiURL.Scheme[:schemeHTTPPrefixLength] // "https" -> "http"
|
||||
}
|
||||
|
||||
client.logger.Printf("Using server: %v\n", client.apiURL.String())
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// useToken sets the access token for the client.
|
||||
func (c *client) useToken(token string) {
|
||||
c.accessToken = token
|
||||
c.updateAccessToken()
|
||||
}
|
||||
|
||||
// login authenticates the client using a username and password.
|
||||
func (c *client) login(user string, password string) error {
|
||||
c.apiURL.RawQuery = ""
|
||||
defer c.updateAccessToken()
|
||||
|
||||
resLogin := apiResLoginFlows{}
|
||||
if err := c.apiGet(apiLogin, &resLogin); err != nil {
|
||||
return fmt.Errorf("failed to get login flows: %w", err)
|
||||
}
|
||||
|
||||
flows := make([]string, 0, len(resLogin.Flows))
|
||||
for _, flow := range resLogin.Flows {
|
||||
flows = append(flows, string(flow.Type))
|
||||
|
||||
if flow.Type == flowLoginPassword {
|
||||
c.logf("Using login flow '%v'", flow.Type)
|
||||
|
||||
return c.loginPassword(user, password)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %v", ErrUnsupportedLoginFlows, strings.Join(flows, ", "))
|
||||
}
|
||||
|
||||
// loginPassword performs a password-based login to the Matrix server.
|
||||
func (c *client) loginPassword(user string, password string) error {
|
||||
response := apiResLogin{}
|
||||
if err := c.apiPost(apiLogin, apiReqLogin{
|
||||
Type: flowLoginPassword,
|
||||
Password: password,
|
||||
Identifier: newUserIdentifier(user),
|
||||
}, &response); err != nil {
|
||||
return fmt.Errorf("failed to log in: %w", err)
|
||||
}
|
||||
|
||||
c.accessToken = response.AccessToken
|
||||
|
||||
tokenHint := ""
|
||||
if len(response.AccessToken) > tokenHintLength {
|
||||
tokenHint = response.AccessToken[:tokenHintLength]
|
||||
}
|
||||
|
||||
c.logf("AccessToken: %v...\n", tokenHint)
|
||||
c.logf("HomeServer: %v\n", response.HomeServer)
|
||||
c.logf("User: %v\n", response.UserID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendMessage sends a message to the specified rooms or all joined rooms if none are specified.
|
||||
func (c *client) sendMessage(message string, rooms []string) []error {
|
||||
if len(rooms) >= minSliceLength {
|
||||
return c.sendToExplicitRooms(rooms, message)
|
||||
}
|
||||
|
||||
return c.sendToJoinedRooms(message)
|
||||
}
|
||||
|
||||
// sendToExplicitRooms sends a message to explicitly specified rooms and collects any errors.
|
||||
func (c *client) sendToExplicitRooms(rooms []string, message string) []error {
|
||||
var errors []error
|
||||
|
||||
for _, room := range rooms {
|
||||
c.logf("Sending message to '%v'...\n", room)
|
||||
|
||||
roomID, err := c.joinRoom(room)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf("error joining room %v: %w", roomID, err))
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if room != roomID {
|
||||
c.logf("Resolved room alias '%v' to ID '%v'", room, roomID)
|
||||
}
|
||||
|
||||
if err := c.sendMessageToRoom(message, roomID); err != nil {
|
||||
errors = append(
|
||||
errors,
|
||||
fmt.Errorf("failed to send message to room '%v': %w", roomID, err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// sendToJoinedRooms sends a message to all joined rooms and collects any errors.
|
||||
func (c *client) sendToJoinedRooms(message string) []error {
|
||||
var errors []error
|
||||
|
||||
joinedRooms, err := c.getJoinedRooms()
|
||||
if err != nil {
|
||||
return append(errors, fmt.Errorf("failed to get joined rooms: %w", err))
|
||||
}
|
||||
|
||||
for _, roomID := range joinedRooms {
|
||||
c.logf("Sending message to '%v'...\n", roomID)
|
||||
|
||||
if err := c.sendMessageToRoom(message, roomID); err != nil {
|
||||
errors = append(
|
||||
errors,
|
||||
fmt.Errorf("failed to send message to room '%v': %w", roomID, err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// joinRoom joins a specified room and returns its ID.
|
||||
func (c *client) joinRoom(room string) (string, error) {
|
||||
resRoom := apiResRoom{}
|
||||
if err := c.apiPost(fmt.Sprintf(apiRoomJoin, room), nil, &resRoom); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return resRoom.RoomID, nil
|
||||
}
|
||||
|
||||
// sendMessageToRoom sends a message to a specific room.
|
||||
func (c *client) sendMessageToRoom(message string, roomID string) error {
|
||||
resEvent := apiResEvent{}
|
||||
|
||||
return c.apiPost(fmt.Sprintf(apiSendMessage, roomID), apiReqSend{
|
||||
MsgType: msgTypeText,
|
||||
Body: message,
|
||||
}, &resEvent)
|
||||
}
|
||||
|
||||
// apiGet performs a GET request to the Matrix API.
|
||||
func (c *client) apiGet(path string, response any) error {
|
||||
c.apiURL.Path = path
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultHTTPTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.apiURL.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating GET request: %w", err)
|
||||
}
|
||||
|
||||
res, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("executing GET request: %w", err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading GET response body: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode >= httpClientErrorStatus {
|
||||
resError := &apiResError{}
|
||||
if err = json.Unmarshal(body, resError); err == nil {
|
||||
return resError
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %v (unmarshal error: %w)", ErrUnexpectedStatus, res.Status, err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, response); err != nil {
|
||||
return fmt.Errorf("unmarshaling GET response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// apiPost performs a POST request to the Matrix API.
|
||||
func (c *client) apiPost(path string, request any, response any) error {
|
||||
c.apiURL.Path = path
|
||||
|
||||
body, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshaling POST request: %w", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultHTTPTimeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(
|
||||
ctx,
|
||||
http.MethodPost,
|
||||
c.apiURL.String(),
|
||||
bytes.NewReader(body),
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating POST request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", contentType)
|
||||
|
||||
res, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("executing POST request: %w", err)
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err = io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading POST response body: %w", err)
|
||||
}
|
||||
|
||||
if res.StatusCode >= httpClientErrorStatus {
|
||||
resError := &apiResError{}
|
||||
if err = json.Unmarshal(body, resError); err == nil {
|
||||
return resError
|
||||
}
|
||||
|
||||
return fmt.Errorf("%w: %v (unmarshal error: %w)", ErrUnexpectedStatus, res.Status, err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, response); err != nil {
|
||||
return fmt.Errorf("unmarshaling POST response: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateAccessToken updates the API URL query with the current access token.
|
||||
func (c *client) updateAccessToken() {
|
||||
query := c.apiURL.Query()
|
||||
query.Set(accessTokenKey, c.accessToken)
|
||||
c.apiURL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
// logf logs a formatted message using the client's logger.
|
||||
func (c *client) logf(format string, v ...any) {
|
||||
c.logger.Printf(format, v...)
|
||||
}
|
||||
|
||||
// getJoinedRooms retrieves the list of rooms the client has joined.
|
||||
func (c *client) getJoinedRooms() ([]string, error) {
|
||||
response := apiResJoinedRooms{}
|
||||
if err := c.apiGet(apiJoinedRooms, &response); err != nil {
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
return response.Rooms, nil
|
||||
}
|
68
pkg/services/matrix/matrix_config.go
Normal file
68
pkg/services/matrix/matrix_config.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/format"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/services/standard"
|
||||
"github.com/nicholas-fedor/shoutrrr/pkg/types"
|
||||
)
|
||||
|
||||
// Config is the configuration for the matrix service.
|
||||
type Config struct {
|
||||
standard.EnumlessConfig
|
||||
|
||||
User string `desc:"Username or empty when using access token" optional:"" url:"user"`
|
||||
Password string `desc:"Password or access token" url:"password"`
|
||||
DisableTLS bool ` default:"No" key:"disableTLS"`
|
||||
Host string ` url:"host"`
|
||||
Rooms []string `desc:"Room aliases, or with ! prefix, room IDs" optional:"" key:"rooms,room"`
|
||||
Title string ` default:"" key:"title"`
|
||||
}
|
||||
|
||||
// GetURL returns a URL representation of it's current field values.
|
||||
func (c *Config) GetURL() *url.URL {
|
||||
resolver := format.NewPropKeyResolver(c)
|
||||
|
||||
return c.getURL(&resolver)
|
||||
}
|
||||
|
||||
// SetURL updates a ServiceConfig from a URL representation of it's field values.
|
||||
func (c *Config) SetURL(url *url.URL) error {
|
||||
resolver := format.NewPropKeyResolver(c)
|
||||
|
||||
return c.setURL(&resolver, url)
|
||||
}
|
||||
|
||||
func (c *Config) getURL(resolver types.ConfigQueryResolver) *url.URL {
|
||||
return &url.URL{
|
||||
User: url.UserPassword(c.User, c.Password),
|
||||
Host: c.Host,
|
||||
Scheme: Scheme,
|
||||
ForceQuery: true,
|
||||
RawQuery: format.BuildQuery(resolver),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) setURL(resolver types.ConfigQueryResolver, configURL *url.URL) error {
|
||||
c.User = configURL.User.Username()
|
||||
password, _ := configURL.User.Password()
|
||||
c.Password = password
|
||||
c.Host = configURL.Host
|
||||
|
||||
for key, vals := range configURL.Query() {
|
||||
if err := resolver.Set(key, vals[0]); err != nil {
|
||||
return fmt.Errorf("setting query parameter %q to %q: %w", key, vals[0], err)
|
||||
}
|
||||
}
|
||||
|
||||
for r, room := range c.Rooms {
|
||||
// If room does not begin with a '#' let's prepend it
|
||||
if room[0] != '#' && room[0] != '!' {
|
||||
c.Rooms[r] = "#" + room
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
676
pkg/services/matrix/matrix_test.go
Normal file
676
pkg/services/matrix/matrix_test.go
Normal file
|
@ -0,0 +1,676 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/jarcoal/httpmock"
|
||||
"github.com/onsi/ginkgo/v2"
|
||||
"github.com/onsi/gomega"
|
||||
|
||||
"github.com/nicholas-fedor/shoutrrr/internal/testutils"
|
||||
)
|
||||
|
||||
func TestMatrix(t *testing.T) {
|
||||
gomega.RegisterFailHandler(ginkgo.Fail)
|
||||
ginkgo.RunSpecs(t, "Shoutrrr Matrix Suite")
|
||||
}
|
||||
|
||||
var _ = ginkgo.Describe("the matrix service", func() {
|
||||
var service *Service
|
||||
logger := log.New(ginkgo.GinkgoWriter, "Test", log.LstdFlags)
|
||||
envMatrixURL := os.Getenv("SHOUTRRR_MATRIX_URL")
|
||||
|
||||
ginkgo.BeforeEach(func() {
|
||||
service = &Service{}
|
||||
})
|
||||
|
||||
ginkgo.When("running integration tests", func() {
|
||||
ginkgo.It("should not error out", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (full initialization with logger and scheme)
|
||||
// - 63-65: login (via Initialize when User is set)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (via Send with real server)
|
||||
// - 156-173: sendMessageToRoom (sending to joined rooms)
|
||||
if envMatrixURL == "" {
|
||||
return
|
||||
}
|
||||
serviceURL, err := url.Parse(envMatrixURL)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("This is an integration test message", nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("creating configurations", func() {
|
||||
ginkgo.When("given an url with title prop", func() {
|
||||
ginkgo.It("should not throw an error", func() {
|
||||
// Tests matrix_config.go, not matrix_client.go directly
|
||||
// Related to Config.SetURL, which feeds into client setup later
|
||||
serviceURL := testutils.URLMust(
|
||||
`matrix://user:pass@mockserver?rooms=room1&title=Better%20Off%20Alone`,
|
||||
)
|
||||
gomega.Expect((&Config{}).SetURL(serviceURL)).To(gomega.Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("given an url with the prop `room`", func() {
|
||||
ginkgo.It("should treat is as an alias for `rooms`", func() {
|
||||
// Tests matrix_config.go, not matrix_client.go directly
|
||||
// Configures Rooms for client.sendToExplicitRooms later
|
||||
serviceURL := testutils.URLMust(`matrix://user:pass@mockserver?room=room1`)
|
||||
config := Config{}
|
||||
gomega.Expect(config.SetURL(serviceURL)).To(gomega.Succeed())
|
||||
gomega.Expect(config.Rooms).To(gomega.ContainElement("#room1"))
|
||||
})
|
||||
})
|
||||
ginkgo.When("given an url with invalid props", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
// Tests matrix_config.go, not matrix_client.go directly
|
||||
// Ensures invalid params fail before reaching client
|
||||
serviceURL := testutils.URLMust(
|
||||
`matrix://user:pass@mockserver?channels=room1,room2`,
|
||||
)
|
||||
gomega.Expect((&Config{}).SetURL(serviceURL)).To(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
ginkgo.When("parsing the configuration URL", func() {
|
||||
ginkgo.It("should be identical after de-/serialization", func() {
|
||||
// Tests matrix_config.go, not matrix_client.go directly
|
||||
// Verifies Config.GetURL/SetURL round-trip for client init
|
||||
testURL := "matrix://user:pass@mockserver?rooms=%23room1%2C%23room2"
|
||||
url, err := url.Parse(testURL)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "parsing")
|
||||
config := &Config{}
|
||||
err = config.SetURL(url)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred(), "verifying")
|
||||
outputURL := config.GetURL()
|
||||
gomega.Expect(outputURL.String()).To(gomega.Equal(testURL))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.Describe("the matrix client", func() {
|
||||
ginkgo.BeforeEach(func() {
|
||||
httpmock.Activate()
|
||||
})
|
||||
|
||||
ginkgo.When("not providing a logger", func() {
|
||||
ginkgo.It("should not crash", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (sets DiscardLogger when logger is nil)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
gomega.Expect(service.Initialize(serviceURL, nil)).To(gomega.Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("sending a message", func() {
|
||||
ginkgo.It("should not report any errors", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToJoinedRooms)
|
||||
// - 134-153: sendToJoinedRooms (sends to joined rooms)
|
||||
// - 156-173: sendMessageToRoom (successful send)
|
||||
// - 225-242: getJoinedRooms (fetches room list)
|
||||
setupMockResponders()
|
||||
serviceURL, _ := url.Parse("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("sending a message to explicit rooms", func() {
|
||||
ginkgo.It("should not report any errors", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToExplicitRooms)
|
||||
// - 112-133: sendToExplicitRooms (sends to explicit rooms)
|
||||
// - 177-192: joinRoom (joins rooms successfully)
|
||||
// - 156-173: sendMessageToRoom (successful send)
|
||||
setupMockResponders()
|
||||
serviceURL, _ := url.Parse("matrix://user:pass@mockserver?rooms=room1,room2")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
ginkgo.When("sending to one room fails", func() {
|
||||
ginkgo.It("should report one error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToExplicitRooms)
|
||||
// - 112-133: sendToExplicitRooms (handles join failure)
|
||||
// - 177-192: joinRoom (fails for "secret" room)
|
||||
// - 156-173: sendMessageToRoom (succeeds for "room2")
|
||||
setupMockResponders()
|
||||
serviceURL, _ := url.Parse("matrix://user:pass@mockserver?rooms=secret,room2")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("disabling TLS", func() {
|
||||
ginkgo.It("should use HTTP instead of HTTPS", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (specifically line 50: c.apiURL.Scheme = c.apiURL.Scheme[:schemeHTTPPrefixLength])
|
||||
// - 63-65: login (successful initialization over HTTP)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
setupMockRespondersHTTP()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver?disableTLS=yes")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(service.client.apiURL.Scheme).To(gomega.Equal("http"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to get login flows", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-69: login (specifically line 69: return fmt.Errorf("failed to get login flows: %w", err))
|
||||
// - 175-223: apiGet (returns error due to 500 response)
|
||||
setupMockRespondersLoginFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("failed to get login flows"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("no supported login flows are available", func() {
|
||||
ginkgo.It("should return an error with unsupported flows", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-87: login (specifically line 84: return fmt.Errorf("none of the server login flows are supported: %v", strings.Join(flows, ", ")))
|
||||
// - 175-223: apiGet (successful GET with unsupported flows)
|
||||
setupMockRespondersUnsupportedFlows()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).
|
||||
To(gomega.Equal("none of the server login flows are supported: m.login.dummy"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("using a token instead of login", func() {
|
||||
ginkgo.It("should initialize without errors", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 59-60: useToken (sets token and calls updateAccessToken)
|
||||
// - 244-248: updateAccessToken (updates URL query with token)
|
||||
setupMockResponders() // Minimal mocks for initialization
|
||||
serviceURL := testutils.URLMust("matrix://:token@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(service.client.accessToken).To(gomega.Equal("token"))
|
||||
gomega.Expect(service.client.apiURL.RawQuery).To(gomega.Equal("access_token=token"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to get joined rooms", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToJoinedRooms)
|
||||
// - 134-154: sendToJoinedRooms (specifically lines 137 and 154: error handling for getJoinedRooms failure)
|
||||
// - 225-267: getJoinedRooms (specifically line 267: return []string{}, err)
|
||||
setupMockRespondersJoinedRoomsFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("failed to get joined rooms"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to join a room", func() {
|
||||
ginkgo.It("should skip to the next room and continue", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToExplicitRooms)
|
||||
// - 112-133: sendToExplicitRooms (specifically line 147: continue on join failure)
|
||||
// - 177-192: joinRoom (specifically line 188: return "", err on failure)
|
||||
// - 156-173: sendMessageToRoom (succeeds for second room)
|
||||
setupMockRespondersJoinFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver?rooms=secret,room2")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("error joining room"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to marshal request in apiPost", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 195-252: apiPost (specifically line 208: body, err = json.Marshal(request) fails)
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.client.apiPost("/test/path", make(chan int), nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).
|
||||
To(gomega.ContainSubstring("json: unsupported type: chan int"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to read response body in apiPost", func() {
|
||||
ginkgo.It("should return an error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToJoinedRooms)
|
||||
// - 134-153: sendToJoinedRooms (calls sendMessageToRoom)
|
||||
// - 156-173: sendMessageToRoom (calls apiPost)
|
||||
// - 195-252: apiPost (specifically lines 204, 223, 230: res handling and body read failure)
|
||||
setupMockRespondersBodyFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).
|
||||
To(gomega.ContainSubstring("failed to read response body"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("routing to explicit rooms at line 94", func() {
|
||||
ginkgo.It("should use sendToExplicitRooms", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (specifically line 94: if len(rooms) >= minSliceLength { true branch)
|
||||
// - 112-133: sendToExplicitRooms (sends to explicit rooms)
|
||||
// - 177-192: joinRoom (joins rooms successfully)
|
||||
// - 156-173: sendMessageToRoom (successful send)
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver?rooms=room1")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("routing to joined rooms at line 94", func() {
|
||||
ginkgo.It("should use sendToJoinedRooms", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (specifically line 94: if len(rooms) >= minSliceLength { false branch)
|
||||
// - 134-153: sendToJoinedRooms (sends to joined rooms)
|
||||
// - 156-173: sendMessageToRoom (successful send)
|
||||
// - 225-242: getJoinedRooms (fetches room list)
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("appending joined rooms error at line 137", func() {
|
||||
ginkgo.It("should append the error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToJoinedRooms)
|
||||
// - 134-154: sendToJoinedRooms (specifically line 137: errors = append(errors, fmt.Errorf("failed to get joined rooms: %w", err)))
|
||||
// - 225-267: getJoinedRooms (returns error)
|
||||
setupMockRespondersJoinedRoomsFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("failed to get joined rooms"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("failing to join room at line 188", func() {
|
||||
ginkgo.It("should return join error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToExplicitRooms)
|
||||
// - 112-133: sendToExplicitRooms (calls joinRoom)
|
||||
// - 177-192: joinRoom (specifically line 188: return "", err)
|
||||
setupMockRespondersJoinFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver?rooms=secret")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("error joining room"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("declaring response variable at line 204", func() {
|
||||
ginkgo.It("should handle HTTP failure", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 195-252: apiPost (specifically line 204: var res *http.Response and error handling)
|
||||
setupMockRespondersPostFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.client.apiPost(
|
||||
"/test/path",
|
||||
apiReqSend{MsgType: msgTypeText, Body: "test"},
|
||||
nil,
|
||||
)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).To(gomega.ContainSubstring("simulated HTTP failure"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("marshaling request fails at line 208", func() {
|
||||
ginkgo.It("should return marshal error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 195-252: apiPost (specifically line 208: body, err = json.Marshal(request))
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.client.apiPost("/test/path", make(chan int), nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).
|
||||
To(gomega.ContainSubstring("json: unsupported type: chan int"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("getting query at line 244", func() {
|
||||
ginkgo.It("should update token in URL", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 59-60: useToken (calls updateAccessToken)
|
||||
// - 244-248: updateAccessToken (specifically line 244: query := c.apiURL.Query())
|
||||
setupMockResponders()
|
||||
serviceURL := testutils.URLMust("matrix://:token@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
gomega.Expect(service.client.apiURL.RawQuery).To(gomega.Equal("access_token=token"))
|
||||
service.client.useToken("newtoken")
|
||||
gomega.Expect(service.client.apiURL.RawQuery).
|
||||
To(gomega.Equal("access_token=newtoken"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.When("checking body read error at line 251", func() {
|
||||
ginkgo.It("should return read error", func() {
|
||||
// Tests matrix_client.go lines:
|
||||
// - 36-52: newClient (successful setup)
|
||||
// - 63-65: login (successful initialization)
|
||||
// - 76-87: loginPassword (successful login flow)
|
||||
// - 91-108: sendMessage (routes to sendToJoinedRooms)
|
||||
// - 134-153: sendToJoinedRooms (calls sendMessageToRoom)
|
||||
// - 156-173: sendMessageToRoom (calls apiPost)
|
||||
// - 195-252: apiPost (specifically line 251: if err != nil { after io.ReadAll)
|
||||
setupMockRespondersBodyFail()
|
||||
serviceURL := testutils.URLMust("matrix://user:pass@mockserver")
|
||||
err := service.Initialize(serviceURL, logger)
|
||||
gomega.Expect(err).NotTo(gomega.HaveOccurred())
|
||||
err = service.Send("Test message", nil)
|
||||
gomega.Expect(err).To(gomega.HaveOccurred())
|
||||
gomega.Expect(err.Error()).
|
||||
To(gomega.ContainSubstring("failed to read response body"))
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.AfterEach(func() {
|
||||
httpmock.DeactivateAndReset()
|
||||
})
|
||||
})
|
||||
|
||||
ginkgo.It("should implement basic service API methods correctly", func() {
|
||||
// Tests matrix_config.go, not matrix_client.go directly
|
||||
// Exercises Config methods used indirectly by client initialization
|
||||
testutils.TestConfigGetInvalidQueryValue(&Config{})
|
||||
testutils.TestConfigSetInvalidQueryValue(&Config{}, "matrix://user:pass@host/?foo=bar")
|
||||
testutils.TestConfigGetEnumsCount(&Config{}, 0)
|
||||
testutils.TestConfigGetFieldsCount(&Config{}, 4)
|
||||
})
|
||||
|
||||
ginkgo.It("should return the correct service ID", func() {
|
||||
service := &Service{}
|
||||
gomega.Expect(service.GetID()).To(gomega.Equal("matrix"))
|
||||
})
|
||||
})
|
||||
|
||||
// setupMockResponders for HTTPS.
|
||||
func setupMockResponders() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiJoinedRooms,
|
||||
httpmock.NewStringResponder(200, `{ "joined_rooms": [ "!room:mockserver" ] }`))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "%21room:mockserver"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResEvent{EventID: "7"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "1"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResEvent{EventID: "8"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "2"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResEvent{EventID: "9"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiRoomJoin, "%23room1"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResRoom{RoomID: "1"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiRoomJoin, "%23room2"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResRoom{RoomID: "2"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiRoomJoin, "%23secret"),
|
||||
httpmock.NewJsonResponderOrPanic(403, apiResError{
|
||||
Code: "M_FORBIDDEN",
|
||||
Message: "You are not invited to this room.",
|
||||
}))
|
||||
}
|
||||
|
||||
// setupMockRespondersHTTP for HTTP.
|
||||
func setupMockRespondersHTTP() {
|
||||
const mockServer = "http://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiJoinedRooms,
|
||||
httpmock.NewStringResponder(200, `{ "joined_rooms": [ "!room:mockserver" ] }`))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "%21room:mockserver"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResEvent{EventID: "7"}))
|
||||
}
|
||||
|
||||
// setupMockRespondersLoginFail for testing line 69.
|
||||
func setupMockRespondersLoginFail() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(500, `{"error": "Internal Server Error"}`))
|
||||
}
|
||||
|
||||
// setupMockRespondersUnsupportedFlows for testing line 84.
|
||||
func setupMockRespondersUnsupportedFlows() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.dummy" } ] }`))
|
||||
}
|
||||
|
||||
// setupMockRespondersJoinedRoomsFail for testing lines 137, 154, and 267.
|
||||
func setupMockRespondersJoinedRoomsFail() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiJoinedRooms,
|
||||
httpmock.NewStringResponder(500, `{"error": "Internal Server Error"}`))
|
||||
}
|
||||
|
||||
// setupMockRespondersJoinFail for testing lines 147 and 188.
|
||||
func setupMockRespondersJoinFail() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiRoomJoin, "%23secret"),
|
||||
httpmock.NewJsonResponderOrPanic(403, apiResError{
|
||||
Code: "M_FORBIDDEN",
|
||||
Message: "You are not invited to this room.",
|
||||
}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiRoomJoin, "%23room2"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResRoom{RoomID: "2"}))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "2"),
|
||||
httpmock.NewJsonResponderOrPanic(200, apiResEvent{EventID: "9"}))
|
||||
}
|
||||
|
||||
// setupMockRespondersBodyFail for testing lines 204, 223, and 230.
|
||||
func setupMockRespondersBodyFail() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiJoinedRooms,
|
||||
httpmock.NewStringResponder(200, `{ "joined_rooms": [ "!room:mockserver" ] }`))
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+fmt.Sprintf(apiSendMessage, "%21room:mockserver"),
|
||||
httpmock.NewErrorResponder(errors.New("failed to read response body")))
|
||||
}
|
||||
|
||||
// setupMockRespondersPostFail for testing line 204 and HTTP failure.
|
||||
func setupMockRespondersPostFail() {
|
||||
const mockServer = "https://mockserver"
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"GET",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(200, `{"flows": [ { "type": "m.login.password" } ] }`))
|
||||
|
||||
httpmock.RegisterResponder(
|
||||
"POST",
|
||||
mockServer+apiLogin,
|
||||
httpmock.NewStringResponder(
|
||||
200,
|
||||
`{ "access_token": "TOKEN", "home_server": "mockserver", "user_id": "test:mockerserver" }`,
|
||||
),
|
||||
)
|
||||
|
||||
httpmock.RegisterResponder("POST", mockServer+"/test/path",
|
||||
httpmock.NewErrorResponder(errors.New("simulated HTTP failure")))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue