func TestSlackMeMessage(t *testing.T) { mockMatrixClient := &MockMatrixClient{} mockSlackClient := &MockSlackClient{} db := makeDB(t) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } rooms.Link(matrix.NewRoom("!abc123:matrix.org"), "CANTINA") echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } matrixUser := matrix.NewUser("@nancy:st.andrews", mockMatrixClient) slackUser := &slack.User{"U34", mockSlackClient} users.Link(matrixUser, slackUser) bridge := Bridge{users, rooms, nil, nil, http.Client{}, echoSuppresser, Config{}} bridge.OnSlackMessage(slack.Message{ Type: "message", Channel: "CANTINA", User: "******", Subtype: "me_message", Text: "takes more chances", }) want := []call{call{"SendEmote", []interface{}{"!abc123:matrix.org", "takes more chances"}}} if !reflect.DeepEqual(mockMatrixClient.calls, want) { t.Fatalf("Wrong Matrix calls, want %v got %v", want, mockMatrixClient.calls) } }
func makeBridge(t *testing.T, db *sql.DB) *Bridge { mockMatrixClient := &MockMatrixClient{} mockSlackClient := &MockSlackClient{} rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } matrixUser := matrix.NewUser("@nancy:st.andrews", mockMatrixClient) slackUser := &slack.User{"U34", mockSlackClient} users.Link(matrixUser, slackUser) // Subsequent calls should load link from database, but don't yet if err := rooms.Link(matrix.NewRoom("!abc123:matrix.org"), "CANTINA"); err != nil { t.Fatalf("Error linking rooms: %v", err) } return &Bridge{users, rooms, nil, nil, http.Client{}, echoSuppresser, Config{}} }
func TestMatrixImageMessage(t *testing.T) { mockMatrixClient := &MockMatrixClient{} mockSlackClient := &MockSlackClient{} db := makeDB(t) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } rooms.Link(matrix.NewRoom("!abc123:matrix.org"), "BOWLINGALLEY") echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } matrixUser := matrix.NewUser("@sean:st.andrews", mockMatrixClient) slackUser := &slack.User{"U35", mockSlackClient} users.Link(matrixUser, slackUser) bridge := Bridge{users, rooms, nil, nil, http.Client{}, echoSuppresser, Config{ HomeserverBaseURL: "https://some.url:1234", }} bridge.OnMatrixRoomMessage(matrix.RoomMessage{ Type: "m.room.message", Content: []byte(`{"msgtype": "m.image", "body": "It's Nancy!", "url": "mxc://some.homeserver/abcDEF"}`), UserID: "@sean:st.andrews", RoomID: "!abc123:matrix.org", }) want := []call{call{"SendImage", []interface{}{"BOWLINGALLEY", "It's Nancy!", "https://some.url:1234/_matrix/media/v1/download/some.homeserver/abcDEF"}}} if !reflect.DeepEqual(mockSlackClient.calls, want) { t.Fatalf("Wrong Slack calls, want %v got %v", want, mockSlackClient.calls) } }
func TestSlackMessageWithImage(t *testing.T) { mockMatrixClient := &MockMatrixClient{} mockSlackClient := &MockSlackClient{} db := makeDB(t) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } rooms.Link(matrix.NewRoom("!abc123:matrix.org"), "CANTINA") echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } matrixUser := matrix.NewUser("@nancy:st.andrews", mockMatrixClient) slackUser := &slack.User{"U34", mockSlackClient} users.Link(matrixUser, slackUser) bridge := Bridge{users, rooms, nil, nil, http.Client{}, echoSuppresser, Config{}} imageURL := "https://slack-files.com/files-pub/T02TMLW97-F0D2M81QA-38528eaf47/otters.jpg" bridge.OnSlackMessage(slack.Message{ Type: "message", Channel: "CANTINA", User: "******", Text: "Cute otter", File: &slack.File{ MIMEType: "image/jpeg", URL: imageURL, OriginalHeight: 768, OriginalWidth: 1024, Size: 90, CommentsCount: 1, InitialComment: &slack.Comment{ Comment: "omg", User: "******", }, }, }) want := []call{ call{"SendImage", []interface{}{"!abc123:matrix.org", "otters.jpg", matrix.Image{ URL: imageURL, Info: &matrix.ImageInfo{ Width: 1024, Height: 768, MIMEType: "image/jpeg", Size: 90, }, }}}, call{"SendText", []interface{}{"!abc123:matrix.org", "omg"}}, } if !reflect.DeepEqual(mockMatrixClient.calls, want) { t.Fatalf("Wrong Matrix calls, want:\n%v\ngot:\n%v", want, mockMatrixClient.calls) } }
func NewClient(token string, c http.Client, messageFilter MessageFilter) *client { return &client{ token: token, client: c, messageFilter: messageFilter, asUser: "", echoSuppresser: common.NewEchoSuppresser(), } }
func TestListenOneRoomMessage(t *testing.T) { listenTest(t, common.NewEchoSuppresser(), func(called chan struct{}) { select { case _ = <-called: return case _ = <-time.After(50 * time.Millisecond): t.Fatalf("Timed out waiting for event") } }) }
func NewBotClient(token, asUser, displayName, avatarURL string, c http.Client, messageFilter MessageFilter) *client { return &client{ token: token, client: c, messageFilter: messageFilter, asUser: asUser, displayName: displayName, avatarURL: avatarURL, echoSuppresser: common.NewEchoSuppresser(), } }
func TestUserMapLoadsConfig(t *testing.T) { dir, err := ioutil.TempDir("", "testdb") if err != nil { t.Fatal(err) } file := path.Join(dir, "sqlite3.db") db := makeDBAt(t, file) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } users, err := NewUserMap(db, http.Client{}, rooms, common.NewEchoSuppresser()) if err != nil { t.Fatal(err) } matrixID := "@foo:somewhere.com" slackID := "bar" matrixUser := matrix.NewUser(matrixID, &MockMatrixClient{}) users.Link(matrixUser, &slack.User{slackID, &MockSlackClient{}}) db.Close() db, err = sql.Open("sqlite3", file) if err != nil { t.Fatal(err) } rooms, err = NewRoomMap(db) if err != nil { t.Fatal(err) } users, err = NewUserMap(db, http.Client{}, rooms, common.NewEchoSuppresser()) if err != nil { t.Fatal(err) } got := users.SlackForMatrix(matrixID) if got == nil { t.Fatalf("got nil user, want user ID %q", slackID) } if got.UserID != slackID { t.Errorf("want %q got %q", slackID, got.UserID) } }
func TestSuppressEcho(t *testing.T) { echoSuppresser := common.NewEchoSuppresser() echoSuppresser.Sent("abc123:some.server") listenTest(t, echoSuppresser, func(called chan struct{}) { select { case _ = <-called: t.Fatalf("Should not have been called") case _ = <-time.After(50 * time.Millisecond): return } }) }
func TestMemberEvent(t *testing.T) { s := httptest.NewServer(&stubHandler{`{ "chunk": [{ "content": { "membership": "join", "displayname": "ME!" }, "room_id": "!cantina:london", "type": "m.room.member", "state_key": "@nancy:london", "user_id": "@nancy:london", "event_id": "abc123:some.server" }], "start": "1", "end": "1" }`}) defer s.Close() called := make(chan struct{}, 1) c := NewClient("6000000000peopleandyou", http.Client{}, s.URL, common.NewEchoSuppresser()) c.OnRoomMember(func(m RoomMemberEvent) { if m.RoomID != "!cantina:london" { t.Errorf("RoomID: want %q got %q", "!cantina:london", m.RoomID) } if m.UserID != "@nancy:london" { t.Errorf("UserID: want %q got %q", "@nancy:london", m.UserID) } if m.StateKey != "@nancy:london" { t.Errorf("StateKey: want %q got %q", "@nancy:london", m.StateKey) } if m.Content.Membership != "join" { t.Errorf("Membership: want %q got %q", "join", m.Content.Membership) } if m.Content.DisplayName != "ME!" { t.Errorf("DisplayName: want %q got %q", "ME!", m.Content.DisplayName) } called <- struct{}{} }) ch := make(chan struct{}, 1) defer func() { ch <- struct{}{} }() go c.Listen(ch) select { case _ = <-called: return case _ = <-time.After(50 * time.Millisecond): t.Fatalf("Timed out waiting for event") } }
func TestSendTextMessage(t *testing.T) { var called int32 s := httptest.NewServer(&handler{t, &called, func(req *http.Request) bool { // I don't know why Go chooses to escape the ! but not the : even though url.QueryEscape escapes both of them if req.URL.String() != "/_matrix/client/api/v1/rooms/%21undertheclock:waterloo.station/send/m.room.message?access_token=6000000000peopleandyou" { return false } dec := json.NewDecoder(req.Body) var c TextMessageContent if err := dec.Decode(&c); err != nil { log.Printf("Error decoding json: %v", err) return false } return c.Body == "quid pro quo" && c.MsgType == "m.text" }}) defer s.Close() c := NewClient("6000000000peopleandyou", http.Client{}, s.URL, common.NewEchoSuppresser()) c.SendText("!undertheclock:waterloo.station", "quid pro quo") if got := atomic.LoadInt32(&called); got != 1 { t.Fatalf("Didn't get expected HTTP request, got: %d", got) } }
func TestSlackMessageFromUnlinkedUser(t *testing.T) { db := makeDB(t) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } slackChannel := "BOWLINGALLEY" matrixRoom := matrix.NewRoom("!abc123:matrix.org") message := "Shhhhh" slackUser := "******" asToken := "abc123" rooms.Link(matrixRoom, slackChannel) echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } slackRoomMembers := slack.NewRoomMembers() slackRoomMembers.Add(slackChannel, &slack.User{"someone", &MockSlackClient{}}) calledTwice := make(chan struct{}, 1) var invites int32 var botJoins int32 var joins int32 var calls int32 verify := func(req *http.Request) string { if req.URL.Path == "/api/users.info" { return `{"ok": true, "user": {"id": "` + slackUser + `", "name": "someoneonslack"}}` } if req.URL.Path == "/_matrix/client/api/v1/rooms/"+matrixRoom.ID+"/join" { if req.URL.Query().Get("user_id") == "" { atomic.AddInt32(&botJoins, 1) } else { atomic.AddInt32(&joins, 1) } return "" } if req.URL.Path == "/_matrix/client/api/v1/rooms/"+matrixRoom.ID+"/invite" { atomic.AddInt32(&invites, 1) return "" } if req.URL.Path != "/_matrix/client/api/v1/rooms/"+matrixRoom.ID+"/send/m.room.message" { t.Fatalf("Got request to unexpected path %q", req.URL.Path) return "" } if atomic.LoadInt32(&invites) == 0 { t.Errorf("Didn't get expected invite before message send") } if atomic.LoadInt32(&joins) == 0 { t.Errorf("Didn't get expected join before message send") } query := req.URL.Query() assertUrlValueEquals(t, query, "access_token", asToken) assertUrlValueEquals(t, query, "user_id", "@prefix_someoneonslack:my.server") b, err := ioutil.ReadAll(req.Body) if err != nil { t.Fatalf("Error reading request body: %v", err) } var content matrix.TextMessageContent if err := json.Unmarshal(b, &content); err != nil { t.Fatalf("Error unmarshaling json: %v", err) } if content.MsgType != "m.text" { t.Errorf("Msgtype: want %q got %q", "m.text", content.MsgType) } if content.Body != message { t.Errorf("Message: want %q got %q", message, content.Body) } callsAfter := atomic.AddInt32(&calls, 1) if callsAfter == 2 { calledTwice <- struct{}{} } return "" } client := http.Client{ Transport: &spyRoundTripper{verify}, } matrixUsers := matrix.NewUsers() bridge := Bridge{users, rooms, slackRoomMembers, matrixUsers, client, echoSuppresser, Config{ MatrixASAccessToken: asToken, UserPrefix: "@prefix_", HomeserverBaseURL: "https://my.server", HomeserverName: "my.server", }} bridge.OnSlackMessage(slack.Message{ Type: "message", Channel: "BOWLINGALLEY", TS: "10", User: slackUser, Text: message, }) bridge.OnSlackMessage(slack.Message{ Type: "message", Channel: "BOWLINGALLEY", TS: "11", User: slackUser, Text: message, }) select { case _ = <-calledTwice: if got := atomic.LoadInt32(&joins); got != 1 { t.Errorf("join count: want: %d, got: %d", 1, got) } if got := atomic.LoadInt32(&botJoins); got != 1 { t.Errorf("bot join count: want: %d, got: %d", 1, got) } return case _ = <-time.After(50 * time.Millisecond): t.Fatalf("Didn't get expected calls") } }
func TestMatrixMessageFromUnlinkedUser(t *testing.T) { db := makeDB(t) rooms, err := NewRoomMap(db) if err != nil { t.Fatal(err) } slackChannel := "BOWLINGALLEY" message := "It's Nancy!" matrixUser := "******" matrixRoom := matrix.NewRoom("!abc123:matrix.org") rooms.Link(matrixRoom, slackChannel) echoSuppresser := common.NewEchoSuppresser() users, err := NewUserMap(db, http.Client{}, rooms, echoSuppresser) if err != nil { t.Fatal(err) } slackRoomMembers := slack.NewRoomMembers() slackRoomMembers.Add(slackChannel, &slack.User{"someone", &MockSlackClient{}}) var calls int32 called := make(chan struct{}, 1) verify := func(req *http.Request) string { b, err := ioutil.ReadAll(req.Body) if err != nil { t.Fatalf("Error reading request body: %v", err) } v, err := url.ParseQuery(string(b)) if err != nil { t.Fatalf("Error parsing request body: %v", err) } assertUrlValueEquals(t, v, "token", "slack_access_token") assertUrlValueEquals(t, v, "channel", slackChannel) assertUrlValueEquals(t, v, "text", message) assertUrlValueEquals(t, v, "as_user", "false") assertUrlValueEquals(t, v, "username", matrixUser) if c := atomic.LoadInt32(&calls); c == 0 { if got, ok := v["icon_url"]; ok { t.Errorf("Want icon_url absent, got: %v", got) } } else if c == 1 { assertUrlValueEquals(t, v, "icon_url", "https://hs.url/_matrix/media/v1/download/st.andrews/sean.jpg") } atomic.AddInt32(&calls, 1) if calls == 2 { called <- struct{}{} } return "" } client := http.Client{ Transport: &spyRoundTripper{verify}, } bridge := Bridge{users, rooms, slackRoomMembers, nil, client, echoSuppresser, Config{ HomeserverBaseURL: "https://hs.url", }} bridge.OnMatrixRoomMessage(matrix.RoomMessage{ Type: "m.room.message", Content: []byte(`{"msgtype": "m.text", "body": "` + message + `"}`), UserID: matrixUser, RoomID: matrixRoom.ID, }) bridge.OnMatrixRoomMember(matrix.RoomMemberEvent{ Type: "m.room.member", Content: matrix.UserInfo{ Membership: "join", AvatarURL: "mxc://st.andrews/sean.jpg", }, StateKey: matrixUser, UserID: matrixUser, RoomID: matrixRoom.ID, }) bridge.OnMatrixRoomMessage(matrix.RoomMessage{ Type: "m.room.message", Content: []byte(`{"msgtype": "m.text", "body": "` + message + `"}`), UserID: matrixUser, RoomID: matrixRoom.ID, }) select { case _ = <-called: return case _ = <-time.After(50 * time.Millisecond): t.Fatalf("Didn't get expected calls (2), got: %d", atomic.LoadInt32(&calls)) } }