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))
	}
}