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)
	}
}
Beispiel #5
0
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")
		}
	})
}
Beispiel #7
0
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
		}
	})
}
Beispiel #10
0
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")
	}
}
Beispiel #11
0
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)
	}
}
Beispiel #12
0
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")
	}
}
Beispiel #13
0
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))
	}
}