diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a516a1657..e097c2af8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,15 +39,9 @@ jobs: name: Complement (${{ matrix.homeserver }}) runs-on: ubuntu-latest strategy: - fail-fast: false # ensure if synapse fails we keep running dendrite and vice-versa + fail-fast: false matrix: include: - - homeserver: Synapse - tags: synapse_blacklist - packages: ./tests/msc3874 ./tests/msc3902 - env: "COMPLEMENT_ENABLE_DIRTY_RUNS=1 COMPLEMENT_SHARE_ENV_PREFIX=PASS_ PASS_SYNAPSE_COMPLEMENT_DATABASE=sqlite" - timeout: 20m - - homeserver: Dendrite tags: dendrite_blacklist packages: "" @@ -100,16 +94,6 @@ jobs: # Build homeserver image - # Build the base Synapse dockerfile and then build a Complement-specific image from that base. - - run: | - docker build -t matrixdotorg/synapse:latest -f docker/Dockerfile . - docker build -t matrixdotorg/synapse-workers:latest -f docker/Dockerfile-workers . - docker build -t homeserver -f docker/complement/Dockerfile docker/complement - if: ${{ matrix.homeserver == 'Synapse' }} - working-directory: homeserver - env: - DOCKER_BUILDKIT: 1 - # Build the Complement-specific dendrite image from the dockerfile in the Dendrite repo. # We don't use the dockerfiles in the Complement repo as they tend to get stale quickly. - run: docker build -t homeserver -f build/scripts/Complement.Dockerfile . diff --git a/b/blueprints.go b/b/blueprints.go index 9b2ce150c..d6390e1ac 100644 --- a/b/blueprints.go +++ b/b/blueprints.go @@ -33,6 +33,7 @@ var KnownBlueprints = map[string]*Blueprint{ BlueprintPerfManyMessages.Name: &BlueprintPerfManyMessages, BlueprintPerfManyRooms.Name: &BlueprintPerfManyRooms, BlueprintPerfE2EERoom.Name: &BlueprintPerfE2EERoom, + BlueprintMultiRoom.Name: &BlueprintMultiRoom, } // Blueprint represents an entire deployment to make. diff --git a/b/multiroom.go b/b/multiroom.go new file mode 100644 index 000000000..562af1ea7 --- /dev/null +++ b/b/multiroom.go @@ -0,0 +1,21 @@ +package b + +// BlueprintOneToOneRoom contains a homeserver for multiroom feature tests. +var BlueprintMultiRoom = MustValidate(Blueprint{ + Name: "multiroom", + Homeservers: []Homeserver{ + { + Name: "hs1", + Users: []User{ + { + Localpart: "@alice", + DisplayName: "Alice", + }, + { + Localpart: "@bob", + DisplayName: "Bob", + }, + }, + }, + }, +}) diff --git a/client/multiroom.go b/client/multiroom.go new file mode 100644 index 000000000..1ec5867c7 --- /dev/null +++ b/client/multiroom.go @@ -0,0 +1,88 @@ +package client + +import ( + "fmt" + "testing" + "time" + + "github.com/matrix-org/complement/b" + "github.com/tidwall/gjson" +) + +type FakeMrd struct { + Foo string +} + +func (c *CSAPI) SendMultiRoom(t *testing.T, dataType string, data interface{}) { + t.Helper() + paths := []string{"_matrix", "client", "v3", "multiroom", dataType} + c.MustDo(t, "POST", paths, WithJSONBody(t, data)) +} + +func (c *CSAPI) SendMultiRoomVisibility(t *testing.T, dataType string, roomId string, expire time.Time) { + t.Helper() + c.SendEventSynced(t, roomId, b.Event{ + StateKey: &c.UserID, + Type: dataType, + Content: map[string]interface{}{ + "expire_ts": expire.Unix(), + }, + }) +} + +func (c *CSAPI) SendMultiRoomVisibilityOff(t *testing.T, dataType string, roomId string) { + t.Helper() + c.SendEventSynced(t, roomId, b.Event{ + StateKey: &c.UserID, + Type: dataType, + Content: map[string]interface{}{ + "hidden": true, + }, + }) +} + +func SyncMultiRoom(userID, dataType string, data *FakeMrd) SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + key := "multiroom." + GjsonEscape(userID) + "." + GjsonEscape(dataType) + keyContent := key + ".content" + mrContent := topLevelSyncJSON.Get(keyContent) + if !mrContent.Exists() { + return fmt.Errorf("key %s does not exist, sync body: %s", keyContent, topLevelSyncJSON.Raw) + } + keyTimestamp := key + ".origin_server_ts" + mrTimestamp := topLevelSyncJSON.Get(keyTimestamp) + if !mrTimestamp.Exists() { + return fmt.Errorf("key %s does not exist, sync body: %s", keyTimestamp, topLevelSyncJSON.Raw) + } + if mrTimestamp.Num == 0 { + return fmt.Errorf("got timestamp equal 0") + } + str := mrContent.Get("Foo").String() + if str != data.Foo { + return fmt.Errorf("SyncMultiRoom: got %s, wanted %s, sync body: %s", str, data.Foo, topLevelSyncJSON.Raw) + } + return nil + } +} + +func SyncNoMultiRoom(userID, dataType string) SyncCheckOpt { + return func(clientUserID string, topLevelSyncJSON gjson.Result) error { + key := "multiroom." + GjsonEscape(userID) + "." + GjsonEscape(dataType) + mrd := topLevelSyncJSON.Get(key) + if mrd.Exists() { + return fmt.Errorf("key %s exist, expected to not exist, sync body: %s", key, topLevelSyncJSON.Raw) + } + return nil + } +} + +func (c *CSAPI) MustSyncCheck(t *testing.T, syncReq SyncReq, checks ...SyncCheckOpt) string { + res, nextBatch := c.MustSync(t, syncReq) + for _, check := range checks { + err := check(c.UserID, res) + if err != nil { + t.Fatalf("MustSyncCheck failed: %s", err) + } + } + return nextBatch +} diff --git a/tests/csapi/apidoc_room_history_visibility_test.go b/tests/csapi/apidoc_room_history_visibility_test.go index 9ab62f792..79909ba11 100644 --- a/tests/csapi/apidoc_room_history_visibility_test.go +++ b/tests/csapi/apidoc_room_history_visibility_test.go @@ -7,11 +7,13 @@ import ( "github.com/tidwall/gjson" "github.com/matrix-org/complement" + "github.com/matrix-org/complement/b" "github.com/matrix-org/complement/client" "github.com/matrix-org/complement/helpers" "github.com/matrix-org/complement/match" "github.com/matrix-org/complement/must" + "github.com/matrix-org/complement/runtime" ) // TODO most of this can be refactored into data-driven tests @@ -84,6 +86,7 @@ func TestFetchEvent(t *testing.T) { // history_visibility: joined // sytest: /event/ does not allow access to events before the user joined func TestFetchHistoricalJoinedEventDenied(t *testing.T) { + runtime.SkipIf(t, runtime.Dendrite) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) @@ -204,6 +207,7 @@ func TestFetchHistoricalInvitedEventFromBetweenInvite(t *testing.T) { // Tries to fetch an event before being invited, and fails. // history_visibility: invited func TestFetchHistoricalInvitedEventFromBeforeInvite(t *testing.T) { + runtime.SkipIf(t, runtime.Dendrite) deployment := complement.Deploy(t, 1) defer deployment.Destroy(t) diff --git a/tests/csapi/multiroom_test.go b/tests/csapi/multiroom_test.go new file mode 100644 index 000000000..a8eed3074 --- /dev/null +++ b/tests/csapi/multiroom_test.go @@ -0,0 +1,57 @@ +package csapi_tests + +import ( + "testing" + "time" + + "github.com/matrix-org/complement" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" + "github.com/matrix-org/complement/helpers" +) + +var ConnectMultiroomVisibility = "connect.multiroom.location.visibility" +var ConnectMultiroomLocation = "connect.multiroom.location" +var dataMrd = &client.FakeMrd{Foo: "bar"} + +func TestMultiRoom(t *testing.T) { + deployment := complement.OldDeploy(t, b.BlueprintMultiRoom) + defer deployment.Destroy(t) + alice := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + bob := deployment.Register(t, "hs1", helpers.RegistrationOpts{}) + t.Run("multiroom data does not pass to sender when visibility is off", func(t *testing.T) { + alice.SendMultiRoom(t, ConnectMultiroomLocation, dataMrd) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncNoMultiRoom(alice.UserID, ConnectMultiroomLocation)) + + }) + t.Run("multiroom data do not pass to others when visibility is off", func(t *testing.T) { + alice.SendMultiRoom(t, ConnectMultiroomLocation, dataMrd) + bob.MustSyncCheck(t, client.SyncReq{}, client.SyncNoMultiRoom(alice.UserID, ConnectMultiroomLocation)) + }) + t.Run("multiroom data pass to sender when visibility is on", func(t *testing.T) { + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + alice.SendMultiRoomVisibility(t, ConnectMultiroomVisibility, roomID, time.Now().Add(time.Hour)) + alice.SendMultiRoom(t, ConnectMultiroomLocation, dataMrd) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncMultiRoom(alice.UserID, ConnectMultiroomLocation, dataMrd)) + }) + t.Run("multiroom data does not pass to users not joined to room", func(t *testing.T) { + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + alice.SendMultiRoomVisibility(t, ConnectMultiroomVisibility, roomID, time.Now().Add(time.Hour)) + alice.SendMultiRoom(t, ConnectMultiroomLocation, dataMrd) + bob.MustSyncCheck(t, client.SyncReq{}, client.SyncNoMultiRoom(alice.UserID, ConnectMultiroomLocation)) + }) + t.Run("multiroom data pass to other users when visibility is on and does not when visibility is off", func(t *testing.T) { + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) + bob.MustDo(t, "POST", []string{"_matrix", "client", "v3", "join", roomID}) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(bob.UserID, roomID)) + alice.SendMultiRoomVisibility(t, ConnectMultiroomVisibility, roomID, time.Now().Add(time.Hour)) + alice.SendMultiRoom(t, ConnectMultiroomLocation, dataMrd) + alice.MustSyncUntil(t, client.SyncReq{}, client.SyncMultiRoom(alice.UserID, ConnectMultiroomLocation, dataMrd)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncMultiRoom(alice.UserID, ConnectMultiroomLocation, dataMrd)) + alice.SendMultiRoomVisibilityOff(t, ConnectMultiroomVisibility, roomID) + bob.MustSyncCheck(t, client.SyncReq{}, client.SyncNoMultiRoom(alice.UserID, ConnectMultiroomLocation)) + }) +}