Skip to content

Add deployment.GetFullyQualifiedHomeserverName(t, hsName) to support custom Deployment #780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/tidwall/gjson"
"golang.org/x/crypto/curve25519"

Expand Down Expand Up @@ -139,7 +140,11 @@ func (c *CSAPI) CreateRoom(t ct.TestLike, body map[string]interface{}) *http.Res
}

// MustJoinRoom joins the room ID or alias given, else fails the test. Returns the room ID.
func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []string) string {
//
// Args:
// - `serverNames`: The list of servers to attempt to join the room through.
// These should be a resolvable addresses within the deployment network.
func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []spec.ServerName) string {
Comment on lines 142 to +147
Copy link
Collaborator Author

@MadLittleMods MadLittleMods May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically, wherever we expect people to use deployment.GetFullyQualifiedHomeserverName(t, hsName), I've updated these function signatures to accept spec.ServerName instead of just plain strings.

I also think this is more semantically correct for the places because this needs to be a resolvable homeserver address in the federation.

t.Helper()
res := c.JoinRoom(t, roomIDOrAlias, serverNames)
mustRespond2xx(t, res)
Expand All @@ -153,12 +158,19 @@ func (c *CSAPI) MustJoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []
}

// JoinRoom joins the room ID or alias given. Returns the raw http response
func (c *CSAPI) JoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []string) *http.Response {
//
// Args:
// - `serverNames`: The list of servers to attempt to join the room through.
// These should be a resolvable addresses within the deployment network.
func (c *CSAPI) JoinRoom(t ct.TestLike, roomIDOrAlias string, serverNames []spec.ServerName) *http.Response {
t.Helper()
// construct URL query parameters
query := make(url.Values, len(serverNames))
for _, serverName := range serverNames {
query.Add("server_name", serverName)
serverNameStrings := make([]string, len(serverNames))
for i, serverName := range serverNames {
serverNameStrings[i] = string(serverName)
}
query := url.Values{
"server_name": serverNameStrings,
}
// join the room
return c.Do(
Expand Down
12 changes: 6 additions & 6 deletions federation/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func MakeRespMakeKnock(s *Server, room *ServerRoom, userID string) (resp fclient
// the current server is returned to the joining server.
func SendJoinRequestsHandler(s *Server, w http.ResponseWriter, req *http.Request, expectPartialState bool, omitServersInRoom bool) {
fedReq, errResp := fclient.VerifyHTTPRequest(
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
req, time.Now(), s.serverName, nil, s.keyRing,
)
if fedReq == nil {
w.WriteHeader(errResp.Code)
Expand Down Expand Up @@ -208,7 +208,7 @@ func HandleInviteRequests(inviteCallback func(gomatrixserverlib.PDU)) func(*Serv
// https://matrix.org/docs/spec/server_server/r0.1.4#put-matrix-federation-v2-invite-roomid-eventid
s.mux.Handle("/_matrix/federation/v2/invite/{roomID}/{eventID}", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
fedReq, errResp := fclient.VerifyHTTPRequest(
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
req, time.Now(), s.serverName, nil, s.keyRing,
)
if fedReq == nil {
w.WriteHeader(errResp.Code)
Expand Down Expand Up @@ -236,7 +236,7 @@ func HandleInviteRequests(inviteCallback func(gomatrixserverlib.PDU)) func(*Serv
}

// Sign the event before we send it back
signedEvent := inviteRequest.Event().Sign(s.serverName, s.KeyID, s.Priv)
signedEvent := inviteRequest.Event().Sign(string(s.serverName), s.KeyID, s.Priv)

// Send the response
res := map[string]interface{}{
Expand All @@ -263,7 +263,7 @@ func HandleDirectoryLookups() func(*Server) {
b, err := json.Marshal(fclient.RespDirectory{
RoomID: roomID,
Servers: []spec.ServerName{
spec.ServerName(s.serverName),
s.serverName,
},
})
if err != nil {
Expand Down Expand Up @@ -432,7 +432,7 @@ func HandleMediaRequests(mediaIds map[string]func(w http.ResponseWriter)) func(*
origin := vars["origin"]
mediaId := vars["mediaId"]

if origin != srv.serverName {
if origin != string(srv.serverName) {
w.WriteHeader(400)
w.Write([]byte("complement: Invalid Origin; Expected " + srv.serverName))
return
Expand Down Expand Up @@ -471,7 +471,7 @@ func HandleTransactionRequests(pduCallback func(gomatrixserverlib.PDU), eduCallb

// Check federation signature
fedReq, errResp := fclient.VerifyHTTPRequest(
req, time.Now(), spec.ServerName(srv.serverName), nil, srv.keyRing,
req, time.Now(), srv.serverName, nil, srv.keyRing,
)
if fedReq == nil {
log.Printf(
Expand Down
40 changes: 25 additions & 15 deletions federation/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ type Server struct {
// Default: true
UnexpectedRequestsAreErrors bool

Priv ed25519.PrivateKey
KeyID gomatrixserverlib.KeyID
serverName string
Priv ed25519.PrivateKey
KeyID gomatrixserverlib.KeyID
// The homeserver name. This should be a resolvable address in the deployment network
serverName spec.ServerName
listening bool

certPath string
Expand Down Expand Up @@ -80,7 +81,7 @@ func NewServer(t ct.TestLike, deployment FederationDeployment, opts ...func(*Ser
mux: mux.NewRouter(),
// The server name will be updated when the caller calls Listen() to include the port number
// of the HTTP server e.g "host.docker.internal:56353"
serverName: deployment.GetConfig().HostnameRunningComplement,
serverName: spec.ServerName(deployment.GetConfig().HostnameRunningComplement),
rooms: make(map[string]*ServerRoom),
aliases: make(map[string]string),
UnexpectedRequestsAreErrors: true,
Expand Down Expand Up @@ -142,7 +143,7 @@ func NewServer(t ct.TestLike, deployment FederationDeployment, opts ...func(*Ser
// It is not supported to call ServerName() before Listen() because Listen() modifies the server name.
// Listen() will select a random OS-provided high-numbered port to listen on, which then needs to be
// retrofitted into the server name so containers know how to route to it.
func (s *Server) ServerName() string {
func (s *Server) ServerName() spec.ServerName {
if !s.listening {
ct.Fatalf(s.t, "ServerName() called before Listen() - this is not supported because Listen() chooses a high-numbered port and thus changes the server name. Ensure you Listen() first!")
}
Expand Down Expand Up @@ -205,28 +206,31 @@ func (s *Server) FederationClient(deployment FederationDeployment) fclient.Feder
ct.Fatalf(s.t, "FederationClient() called before Listen() - this is not supported because Listen() chooses a high-numbered port and thus changes the server name and thus changes the way federation requests are signed. Ensure you Listen() first!")
}
identity := fclient.SigningIdentity{
ServerName: spec.ServerName(s.ServerName()),
ServerName: s.ServerName(),
KeyID: s.KeyID,
PrivateKey: s.Priv,
}
f := fclient.NewFederationClient(
fedClient := fclient.NewFederationClient(
[]*fclient.SigningIdentity{&identity},
fclient.WithTransport(deployment.RoundTripper()),
)
return f
return fedClient
}

// MustSendTransaction sends the given PDUs/EDUs to the target destination, returning an error if the /send fails or if the response contains an error
// for any sent PDUs. Times out after 10 seconds.
func (s *Server) MustSendTransaction(t ct.TestLike, deployment FederationDeployment, destination string, pdus []json.RawMessage, edus []gomatrixserverlib.EDU) {
//
// Args:
// - `destination`: This should be a resolvable addresses within the deployment network.
func (s *Server) MustSendTransaction(t ct.TestLike, deployment FederationDeployment, destination spec.ServerName, pdus []json.RawMessage, edus []gomatrixserverlib.EDU) {
t.Helper()
cli := s.FederationClient(deployment)
fedClient := s.FederationClient(deployment)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
resp, err := cli.SendTransaction(ctx, gomatrixserverlib.Transaction{
resp, err := fedClient.SendTransaction(ctx, gomatrixserverlib.Transaction{
TransactionID: gomatrixserverlib.TransactionID(fmt.Sprintf("complement-%d", time.Now().Nanosecond())),
Origin: spec.ServerName(s.ServerName()),
Destination: spec.ServerName(destination),
Destination: destination,
PDUs: pdus,
EDUs: edus,
})
Expand Down Expand Up @@ -319,6 +323,9 @@ func (s *Server) MustCreateEvent(t ct.TestLike, room *ServerRoom, ev Event) goma

// MustJoinRoom will make the server send a make_join and a send_join to join a room
// It returns the resultant room.
//
// Args:
// - `remoteServer`: This should be a resolvable addresses within the deployment network.
func (s *Server) MustJoinRoom(t ct.TestLike, deployment FederationDeployment, remoteServer spec.ServerName, roomID string, userID string, opts ...JoinRoomOpt) *ServerRoom {
t.Helper()
var jr joinRoom
Expand Down Expand Up @@ -401,6 +408,9 @@ func (s *Server) MustJoinRoom(t ct.TestLike, deployment FederationDeployment, re
}

// Leaves a room. If this is rejecting an invite then a make_leave request is made first, before send_leave.
//
// Args:
// - `remoteServer`: This should be a resolvable addresses within the deployment network.
func (s *Server) MustLeaveRoom(t ct.TestLike, deployment FederationDeployment, remoteServer spec.ServerName, roomID string, userID string) {
t.Helper()
origin := spec.ServerName(s.serverName)
Expand Down Expand Up @@ -449,7 +459,7 @@ func (s *Server) ValidFederationRequest(t ct.TestLike, handler func(fr *fclient.
return func(w http.ResponseWriter, req *http.Request) {
// Check federation signature
fedReq, errResp := fclient.VerifyHTTPRequest(
req, time.Now(), spec.ServerName(s.serverName), nil, s.keyRing,
req, time.Now(), s.serverName, nil, s.keyRing,
)
if fedReq == nil {
ct.Errorf(t,
Expand Down Expand Up @@ -495,7 +505,7 @@ func (s *Server) Listen() (cancel func()) {
ct.Fatalf(s.t, "ListenFederationServer: net.Listen failed: %s", err)
}
port := ln.Addr().(*net.TCPAddr).Port
s.serverName += fmt.Sprintf(":%d", port)
s.serverName = spec.ServerName(fmt.Sprintf("%s:%d", s.serverName, port))
s.listening = true

go func() {
Expand Down Expand Up @@ -647,7 +657,7 @@ func (f *basicKeyFetcher) FetchKeys(
) {
result := make(map[gomatrixserverlib.PublicKeyLookupRequest]gomatrixserverlib.PublicKeyLookupResult, len(requests))
for req := range requests {
if string(req.ServerName) == f.srv.serverName && req.KeyID == f.srv.KeyID {
if req.ServerName == f.srv.serverName && req.KeyID == f.srv.KeyID {
publicKey := f.srv.Priv.Public().(ed25519.PublicKey)
result[req] = gomatrixserverlib.PublicKeyLookupResult{
ValidUntilTS: spec.AsTimestamp(time.Now().Add(24 * time.Hour)),
Expand Down
16 changes: 11 additions & 5 deletions federation/server_room.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func (r *ServerRoom) MustHaveMembershipForUser(t ct.TestLike, userID, wantMember
}

// ServersInRoom gets all servers currently joined to the room
func (r *ServerRoom) ServersInRoom() (servers []string) {
func (r *ServerRoom) ServersInRoom() (servers []spec.ServerName) {
serverSet := make(map[string]struct{})

r.StateMutex.RLock()
Expand All @@ -282,7 +282,7 @@ func (r *ServerRoom) ServersInRoom() (servers []string) {
r.StateMutex.RUnlock()

for server := range serverSet {
servers = append(servers, server)
servers = append(servers, spec.ServerName(server))
}

return
Expand Down Expand Up @@ -498,21 +498,27 @@ func (i *ServerRoomImplDefault) GenerateSendJoinResponse(room *ServerRoom, s *Se
authEvents := room.AuthChainForEvents(stateEvents)

// get servers in room *before* the join event
serversInRoom := []string{s.serverName}
serversInRoom := []spec.ServerName{s.serverName}
if !omitServersInRoom {
serversInRoom = room.ServersInRoom()
}

serversInRoomStrings := make([]string, len(serversInRoom))
for i, serverName := range serversInRoom {
serversInRoomStrings[i] = string(serverName)
}

// insert the join event into the room state
room.AddEvent(joinEvent)
log.Printf("Received send-join of event %s", joinEvent.EventID())

// return state and auth chain
return fclient.RespSendJoin{
Origin: spec.ServerName(s.serverName),
Origin: s.serverName,
AuthEvents: gomatrixserverlib.NewEventJSONsFromEvents(authEvents),
StateEvents: gomatrixserverlib.NewEventJSONsFromEvents(stateEvents),
MembersOmitted: expectPartialState,
ServersInRoom: serversInRoom,
// TODO: It feels like `ServersInRoom` should be `[]spec.ServerName` instead of `[]string`
ServersInRoom: serversInRoomStrings,
Comment on lines +521 to +522
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a gomatrixserverlib problem.

(not going to fix in this PR)

}
}
2 changes: 1 addition & 1 deletion federation/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestComplementServerIsSigned(t *testing.T) {
transport := &http.Transport{TLSClientConfig: tc.config}
client := &http.Client{Transport: transport}

resp, err := client.Get("https://" + srv.ServerName())
resp, err := client.Get("https://" + string(srv.ServerName()))
if err != nil {
if tc.wantSuccess {
t.Fatalf("Failed to GET: %s", err)
Expand Down
11 changes: 11 additions & 0 deletions internal/docker/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/matrix-org/complement/ct"
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
)

// Deployment is the complete instantiation of a Blueprint, with running containers
Expand Down Expand Up @@ -57,6 +58,16 @@ func (hsDep *HomeserverDeployment) SetEndpoints(baseURL string, fedBaseURL strin
}
}

func (d *Deployment) GetFullyQualifiedHomeserverName(t ct.TestLike, hsName string) spec.ServerName {
_, ok := d.HS[hsName]
if !ok {
ct.Fatalf(t, "Deployment.GetFullyQualifiedHomeserverName - HS name '%s' not found", hsName)
}
// We have network aliases for each Docker container that will resolve the `hsName` to
// the container.
return spec.ServerName(hsName)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, I don't think the built-in Complement Deployment implementation supports multiple Deployments at the same time (hs1, hs2 would conflict between them). Since one of the goals of this PR is to unlock that functionality for custom Deployment's, it could make some sense to also refactor and update that here as well.

I'd rather leave it as-is until we need it or at-least do this in a follow-up PR.

See the PR description for more context on multiple Deployment.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description doesn't give more context on why multiple Deployment is desirable:

But imagine a case where we have multiple Deployment and we want the homeservers to communicate with each other.

Why would you do this? The Deployment is meant to encapsulate an entire deployment, all servers and network links between them. I don't understand the rationale.

Copy link
Collaborator Author

@MadLittleMods MadLittleMods May 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kegsay In the current setup of our custom Deployment, each Deployment is a "shard" application that can deploy multiple homeserver "tenants".

And we specifically want to test that homeservers between multiple shards can federate with each other as a sanity check (make sure our shards can deploy homeserver tenants correctly):

func TestCrossShardFederation(t *testing.T) {
	// Create two shards with their own homeserver tenants
	shardDeployment1 := complement.Deploy(t, 1)
	defer shardDeployment1.Destroy(t)
	shardDeployment2 := complement.Deploy(t, 1)
	defer shardDeployment2.Destroy(t)

	alice := shardDeployment1.Register(t, "hs1", helpers.RegistrationOpts{})
	bob := shardDeployment2.Register(t, "hs1", helpers.RegistrationOpts{})

	aliceRoomID := alice.MustCreateRoom(t, map[string]any{
		"preset": "public_chat",
	})
	bobRoomID := bob.MustCreateRoom(t, map[string]any{
		"preset": "public_chat",
	})

	t.Run("parallel", func(t *testing.T) {
		t.Run("shard1 -> shard2", func(t *testing.T) {
			// Since these tests use the same config, they can be run in parallel
			t.Parallel()

			alice.MustJoinRoom(t, bobRoomID, []string{
				shardDeployment2.GetFullyQualifiedHomeserverName(t, "hs1"),
			})

			bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, bobRoomID))
		})

		t.Run("shard2 -> shard1", func(t *testing.T) {
			// Since these tests use the same config, they can be run in parallel
			t.Parallel()

			bob.MustJoinRoom(t, aliceRoomID, []string{
				shardDeployment1.GetFullyQualifiedHomeserverName(t, "hs1"),
			})

			alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, aliceRoomID))
		})
	})
}

This does assume that each Deployment shares a network that can communicate with each other (which they do).

Better way to go about this?

One alternative I can think of is to bake this information into the hsName (hs1.shard1) and parse it out but then we run into existing test compatibility issues as our custom deployment Deploy no longer creates hs1 named things.

Another is to statically assign each homeserver to a shard like hs1 -> shard1 and hs2 -> shard2, etc. But that's not very flexible to different numbers of homeservers per shard and the magic value knowledge that gets built-in to the tests.

// DestroyAtCleanup destroys the entire deployment. It should be called at cleanup time for dirty
// deployments only. Handles configuration options for things which should run at container destroy
// time, like post-run scripts and printing logs.
Expand Down
9 changes: 9 additions & 0 deletions test_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ func WithCleanup(fn func(config *config.Complement)) opt {
}

// WithDeployment adds a custom mechanism to deploy homeservers.
//
// For test consistency and compatibility, deployers should be creating servers that can
// be referred to as `hs1`, `hs2`, etc as the `hsName` in the `Deployment` interface.
// The actual resolvable address of the homeserver in the network can be something
// different and just needs to be mapped by
// your implementation of `deployment.GetFullyQualifiedHomeserverName(hsName)`.
func WithDeployment(fn func(t ct.TestLike, numServers int, config *config.Complement) Deployment) opt {
return func(co *complementOpts) {
co.customDeployment = fn
Expand Down Expand Up @@ -93,6 +99,9 @@ func OldDeploy(t ct.TestLike, blueprint b.Blueprint) Deployment {
// Deploy will deploy the given number of servers or terminate the test.
// This function is the main setup function for all tests as it provides a deployment with
// which tests can interact with.
//
// For test consistency and compatibility, deployers should be creating servers that can
// be referred to as `hs1`, `hs2`, etc as the `hsName` in the `Deployment` interface.
func Deploy(t ct.TestLike, numServers int) Deployment {
t.Helper()
if testPackage == nil {
Expand Down
8 changes: 8 additions & 0 deletions test_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,19 @@ import (
"github.com/matrix-org/complement/ct"
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/complement/internal/docker"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/sirupsen/logrus"
)

// Deployment provides a way for tests to interact with a set of homeservers.
type Deployment interface {
// Returns the resolvable server name (host) of a homeserver given its short alias
// (e.g., "hs1", "hs2").
//
// In the case of the standard Docker deployment, this will be the same `hs1`, `hs2`
// but may be different for other custom deployments (ex.
// `shardDeployment1.GetFullyQualifiedHomeserverName(t, "hs1")` -> `hs1.shard1:8081`).
GetFullyQualifiedHomeserverName(t ct.TestLike, hsName string) spec.ServerName
// UnauthenticatedClient returns a blank CSAPI client.
UnauthenticatedClient(t ct.TestLike, serverName string) *client.CSAPI
// Register a new user on the given server.
Expand Down
3 changes: 2 additions & 1 deletion tests/csapi/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/complement/match"
"github.com/matrix-org/complement/must"
"github.com/matrix-org/gomatrixserverlib/spec"
)

// Check if this homeserver supports Synapse-style admin registration.
Expand Down Expand Up @@ -69,7 +70,7 @@ func TestServerNotices(t *testing.T) {
})
})
t.Run("Alice can join the alert room", func(t *testing.T) {
alice.MustJoinRoom(t, roomID, []string{})
alice.MustJoinRoom(t, roomID, []spec.ServerName{})
alice.MustSyncUntil(t, client.SyncReq{}, client.SyncTimelineHasEventID(roomID, eventID))
})
t.Run("Alice can leave the alert room, after joining it", func(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion tests/csapi/apidoc_presence_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/matrix-org/complement/helpers"
"github.com/matrix-org/complement/match"
"github.com/matrix-org/complement/must"
"github.com/matrix-org/gomatrixserverlib/spec"
)

func TestPresence(t *testing.T) {
Expand All @@ -27,7 +28,9 @@ func TestPresence(t *testing.T) {

// to share presence alice and bob must be in a shared room
roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"})
bob.MustJoinRoom(t, roomID, []string{"hs1"})
bob.MustJoinRoom(t, roomID, []spec.ServerName{
deployment.GetFullyQualifiedHomeserverName(t, "hs1"),
Copy link
Collaborator Author

@MadLittleMods MadLittleMods May 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reviewer: I've tried to be thorough in updating everything on this list (from the PR description). This would be the main thing to think about. Are there other spots that we need to use GetFullyQualifiedHomeserverName(...) instead of the hard-coded hs1 values?

  • Update tests to use GetFullyQualifiedHomeserverName(...)
    • Join room (MustJoinRoom, JoinRoom)
    • Knock room (mustKnockOnRoomSynced, knockOnRoomWithStatus)
    • srv.MustJoinRoom, srv.MustLeaveRoom, srv.MustSendTransaction
    • FederationClient -> fedClient.MakeJoin, fedClient.SendJoin, etc
    • fclient, fclient.NewFederationRequest
    • m.space.child via
    • m.space.parent via
    • m.room.join_rules restricted via
    • gomatrixserverlib.EDU Destination

I've also reviewed the diff itself to ensure that I didn't accidentally swap hs1 for hs2 somewhere.

})

// sytest: GET /presence/:user_id/status fetches initial status
t.Run("GET /presence/:user_id/status fetches initial status", func(t *testing.T) {
Expand Down
Loading
Loading