func TestRoomMapLoadsConfig(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) } matrixRoomID := "foo" slack := "bar" rooms.Link(matrix.NewRoom(matrixRoomID), slack) db.Close() db, err = sql.Open("sqlite3", file) if err != nil { t.Fatal(err) } rooms, err = NewRoomMap(db) if err != nil { t.Fatal(err) } if got := rooms.SlackForMatrix(matrixRoomID); got != slack { t.Errorf("want %q got %q", slack, got) } }
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 TestSlackMessageFilter(t *testing.T) { rooms, err := NewRoomMap(makeDB(t)) if err != nil { t.Fatal(err) } receive := func(ts string) bool { return rooms.ShouldNotify(&slack.Message{ Type: "message", Channel: "CANTINA", User: "******", Text: "Take more chances", TS: ts, }) } if receive("1") { t.Fatalf("not linked: should have skipped") } rooms.Link(matrix.NewRoom("!abc123:matrix.org"), "CANTINA") if !receive("1") { t.Fatalf("should have notified") } if receive("0.5") { t.Fatalf("seen newer, should have skipped") } if !receive("2") { t.Fatalf("should have notified") } }
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 NewRoomMap(db *sql.DB) (*RoomMap, error) { m := &RoomMap{ matrixToSlack: make(map[string]string), slackToMatrix: make(map[string]*matrix.Room), rows: make(map[string]*entry), /* CREATE TABLE IF NOT EXISTS rooms( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, slack_channel_id TEXT, matrix_room_id TEXT, last_slack_timestamp TEXT, last_matrix_stream_token TEXT) */ db: db, } rows, err := db.Query("SELECT id, slack_channel_id, matrix_room_id FROM rooms ORDER BY id ASC") if err != nil { return nil, err } for rows.Next() { var id int32 var slack string var matrixID string if err := rows.Scan(&id, &slack, &matrixID); err != nil { return nil, err } matrixRoom := matrix.NewRoom(matrixID) if err := m.Link(matrixRoom, slack); err != nil { return nil, err } } if rows.Err() != nil { return nil, rows.Err() } return m, nil }
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)) } }