From 37aa1469a53afecf4226bff7ea11fd7859a79af0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 11 Oct 2023 12:23:46 +0100 Subject: [PATCH] WIP: use complement libraries --- go.mod | 16 +- go.sum | 39 +- tests-e2e/account_data_test.go | 8 +- tests-e2e/client_test.go | 994 ++--------------------- tests-e2e/conns_test.go | 18 +- tests-e2e/gappy_state_test.go | 18 +- tests-e2e/ignored_user_test.go | 16 +- tests-e2e/lazy_loading_test.go | 13 +- tests-e2e/lists_test.go | 176 ++-- tests-e2e/main_test.go | 19 +- tests-e2e/membership_transitions_test.go | 66 +- tests-e2e/num_live_test.go | 21 +- tests-e2e/prev_batch_test.go | 28 +- tests-e2e/receipts_test.go | 33 +- tests-e2e/redaction_test.go | 46 +- tests-e2e/security_test.go | 30 +- tests-e2e/spaces_test.go | 44 +- tests-e2e/timestamp_test.go | 11 +- tests-e2e/to_device_test.go | 72 +- tests-e2e/tombstone_test.go | 20 +- tests-e2e/transaction_id_test.go | 35 +- tests-e2e/typing_test.go | 28 +- tests-e2e/unread_count_test.go | 8 +- 23 files changed, 531 insertions(+), 1228 deletions(-) diff --git a/go.mod b/go.mod index e09acda..45a5af3 100644 --- a/go.mod +++ b/go.mod @@ -9,12 +9,13 @@ require ( github.com/gorilla/mux v1.8.0 github.com/jmoiron/sqlx v1.3.3 github.com/lib/pq v1.10.9 - github.com/matrix-org/gomatrixserverlib v0.0.0-20230822143230-740f742d6993 - github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 + github.com/matrix-org/complement v0.0.0-20231010122615-008239d3b6e7 + github.com/matrix-org/gomatrixserverlib v0.0.0-20230921171121-0466775328c7 + github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 github.com/pressly/goose/v3 v3.14.0 github.com/prometheus/client_golang v1.13.0 github.com/rs/zerolog v1.29.0 - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.16.0 github.com/tidwall/sjson v1.2.5 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0 go.opentelemetry.io/contrib/propagators/jaeger v1.18.0 @@ -34,6 +35,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0 // indirect + github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect @@ -43,17 +45,19 @@ require ( github.com/rs/xid v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/pretty v1.2.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel/metric v1.18.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.14.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.12.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + maunium.net/go/mautrix v0.11.0 // indirect ) diff --git a/go.sum b/go.sum index 8d17e51..e7cdfa3 100644 --- a/go.sum +++ b/go.sum @@ -153,6 +153,7 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0 h1:Rme6CE1aUTyV9WmrEPyGf1V+7W3iQzZ1DZkKnT6z9B0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.0/go.mod h1:Hbb13e3/WtqQ8U5hLGkek9gJvBLasHuPFI0UEGfnQ10= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -178,11 +179,14 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5dLDCud4r0r55eP4j9FuUNpl60Gmntcop4= -github.com/matrix-org/gomatrixserverlib v0.0.0-20230822143230-740f742d6993 h1:88wDfSsqSFyeCnTha8vfK9XJUbNjNXdEZAieOjbBzos= -github.com/matrix-org/gomatrixserverlib v0.0.0-20230822143230-740f742d6993/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU= -github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4 h1:eCEHXWDv9Rm335MSuB49mFUK44bwZPFSDde3ORE3syk= -github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4/go.mod h1:vVQlW/emklohkZnOPwD3LrZUBqdfsbiyO3p1lNV8F6U= +github.com/matrix-org/complement v0.0.0-20231010122615-008239d3b6e7 h1:lamc87j5ig2mNEi5Qqd+ZeIpmLdKtvYgZ3HDSPG0DrI= +github.com/matrix-org/complement v0.0.0-20231010122615-008239d3b6e7/go.mod h1:GCJVyag4p1Qj/WJUmBLlRtRHd04ALFNJkhzOcM/BvgA= +github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530 h1:kHKxCOLcHH8r4Fzarl4+Y3K5hjothkVW5z7T1dUM11U= +github.com/matrix-org/gomatrix v0.0.0-20220926102614-ceba4d9f7530/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s= +github.com/matrix-org/gomatrixserverlib v0.0.0-20230921171121-0466775328c7 h1:NhPNNFLHwdDb/upeicBh1GkxX/sFinEp5HF1WBqPtiY= +github.com/matrix-org/gomatrixserverlib v0.0.0-20230921171121-0466775328c7/go.mod h1:H9V9N3Uqn1bBJqYJNGK1noqtgJTaCEhtTdcH/mp50uU= +github.com/matrix-org/util v0.0.0-20221111132719-399730281e66 h1:6z4KxomXSIGWqhHcfzExgkH3Z3UkIXry4ibJS4Aqz2Y= +github.com/matrix-org/util v0.0.0-20221111132719-399730281e66/go.mod h1:iBI1foelCqA09JJgPV0FYz4qA5dUXYOxMi57FxKBdd4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= @@ -190,10 +194,11 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.13 h1:1tj15ngiFfcZzii7yd82foL+ks+ouQcj8j/TPq3fk1I= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -254,12 +259,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.16.0 h1:SyXa+dsSPpUlcwEDuKuEBJEz5vzTvOea+9rjyYodQFg= +github.com/tidwall/gjson v1.16.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -299,8 +305,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -311,6 +317,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -443,8 +451,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -490,7 +498,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -584,6 +592,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -601,6 +610,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +maunium.net/go/mautrix v0.11.0 h1:B1FBHcvE4Mud+AC+zgNQQOw0JxSVrt40watCejhVA7w= +maunium.net/go/mautrix v0.11.0/go.mod h1:K29EcHwsNg6r7fMfwvi0GHQ9o5wSjqB9+Q8RjCIQEjA= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/ccgo/v3 v3.16.14 h1:af6KNtFgsVmnDYrWk3PQCS9XT6BXe7o3ZFJKkIKvXNQ= modernc.org/libc v1.24.1 h1:uvJSeCKL/AgzBo2yYIPPTy82v21KgGnizcGYfBHaNuM= diff --git a/tests-e2e/account_data_test.go b/tests-e2e/account_data_test.go index 248a915..917f95c 100644 --- a/tests-e2e/account_data_test.go +++ b/tests-e2e/account_data_test.go @@ -19,8 +19,8 @@ func TestAccountDataRespectsExtensionScope(t *testing.T) { // Want at least one test of the initial sync behaviour (which hits `ProcessInitial`) // separate to the incremental sync behaviour (hits `AppendLive`) t.Log("Alice creates rooms 1 and 2.") - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) t.Logf("room1=%s room2=%s", room1, room2) t.Log("Alice uploads account data for both rooms, plus global account data.") @@ -216,7 +216,7 @@ func TestAccountDataDoesntDupe(t *testing.T) { // sync response with bytes.Equal. func putGlobalAccountData(t *testing.T, client *CSAPI, eventType string, content map[string]interface{}) json.RawMessage { t.Helper() - client.SetGlobalAccountData(t, eventType, content) + client.MustSetGlobalAccountData(t, eventType, content) serialised := testutils.NewAccountData(t, eventType, content) return serialised } @@ -224,7 +224,7 @@ func putGlobalAccountData(t *testing.T, client *CSAPI, eventType string, content // putRoomAccountData is like putGlobalAccountData, but for room-specific account data. func putRoomAccountData(t *testing.T, client *CSAPI, roomID, eventType string, content map[string]interface{}) json.RawMessage { t.Helper() - client.SetRoomAccountData(t, roomID, eventType, content) + client.MustSetRoomAccountData(t, roomID, eventType, content) serialised := testutils.NewAccountData(t, eventType, content) return serialised } diff --git a/tests-e2e/client_test.go b/tests-e2e/client_test.go index 5c912e6..a082632 100644 --- a/tests-e2e/client_test.go +++ b/tests-e2e/client_test.go @@ -1,32 +1,20 @@ package syncv3_test import ( - "bytes" - "context" - "crypto/hmac" - "crypto/sha1" // nolint:gosec - "encoding/hex" "encoding/json" "fmt" "io" - "io/ioutil" "net/http" - "net/http/httputil" "net/url" - "strconv" - "strings" "testing" "time" - "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/tidwall/gjson" ) -const ( - SharedSecret = "complement" -) - var ( boolTrue = true boolFalse = false @@ -38,631 +26,37 @@ type Event struct { Sender string `json:"sender"` StateKey *string `json:"state_key"` Content map[string]interface{} `json:"content"` - - /* The following fields are ignored in blueprints as clients are unable to set them. - * They are used with federation.Server. - */ - Unsigned map[string]interface{} `json:"unsigned"` - - // The events needed to authenticate this event. - // This can be either []EventReference for room v1/v2, or []string for room v3 onwards. - // If it is left at nil, MustCreateEvent will populate it automatically based on the room state. - AuthEvents interface{} `json:"auth_events"` - - // The prev events of the event if we want to override or falsify them. - // If it is left at nil, MustCreateEvent will populate it automatically based on the forward extremities. - PrevEvents interface{} `json:"prev_events"` - - // If this is a redaction, the event that it redacts - Redacts string `json:"redacts"` } -func NewEncryptionEvent() Event { - return Event{ - Type: "m.room.encryption", - StateKey: ptr(""), - Content: map[string]interface{}{ - "algorithm": "m.megolm.v1.aes-sha2", - "rotation_period_ms": 604800000, - "rotation_period_msgs": 100, - }, - } -} - -type MessagesBatch struct { - Chunk []json.RawMessage `json:"chunk"` - Start string `json:"start"` - End string `json:"end"` -} - -type CtxKey string - -const ( - CtxKeyWithRetryUntil CtxKey = "complement_retry_until" // contains *retryUntilParams -) - -type retryUntilParams struct { - timeout time.Duration - untilFn func(*http.Response) bool -} - -// RequestOpt is a functional option which will modify an outgoing HTTP request. -// See functions starting with `With...` in this package for more info. -type RequestOpt func(req *http.Request) - -// SyncCheckOpt is a functional option for use with MustSyncUntil which should return if -// the response satisfies the check, else return a human friendly error. -// The result object is the entire /sync response from this request. -type SyncCheckOpt func(clientUserID string, topLevelSyncJSON gjson.Result) error - -// SyncReq contains all the /sync request configuration options. The empty struct `SyncReq{}` is valid -// which will do a full /sync due to lack of a since token. -type SyncReq struct { - // A point in time to continue a sync from. This should be the next_batch token returned by an - // earlier call to this endpoint. - Since string - // The ID of a filter created using the filter API or a filter JSON object encoded as a string. - // The server will detect whether it is an ID or a JSON object by whether the first character is - // a "{" open brace. Passing the JSON inline is best suited to one off requests. Creating a - // filter using the filter API is recommended for clients that reuse the same filter multiple - // times, for example in long poll requests. - Filter string - // Controls whether to include the full state for all rooms the user is a member of. - // If this is set to true, then all state events will be returned, even if since is non-empty. - // The timeline will still be limited by the since parameter. In this case, the timeout parameter - // will be ignored and the query will return immediately, possibly with an empty timeline. - // If false, and since is non-empty, only state which has changed since the point indicated by - // since will be returned. - // By default, this is false. - FullState bool - // Controls whether the client is automatically marked as online by polling this API. If this - // parameter is omitted then the client is automatically marked as online when it uses this API. - // Otherwise if the parameter is set to “offline” then the client is not marked as being online - // when it uses this API. When set to “unavailable”, the client is marked as being idle. - // One of: [offline online unavailable]. - SetPresence string - // The maximum time to wait, in milliseconds, before returning this request. If no events - // (or other data) become available before this time elapses, the server will return a response - // with empty fields. - // By default, this is 1000 for Complement testing. - TimeoutMillis string // string for easier conversion to query params +func WithPos(pos string) client.RequestOpt { + return client.WithQueries(url.Values{ + "pos": []string{pos}, + "timeout": []string{"500"}, // 0.5s + }) } type CSAPI struct { - UserID string - Localpart string - Domain string - AccessToken string - DeviceID string - AvatarURL string - BaseURL string - Client *http.Client - // how long are we willing to wait for MustSyncUntil.... calls - SyncUntilTimeout time.Duration - // True to enable verbose logging - Debug bool - - txnID int -} - -// UploadContent uploads the provided content with an optional file name. Fails the test on error. Returns the MXC URI. -func (c *CSAPI) UploadContent(t *testing.T, fileBody []byte, fileName string, contentType string) string { - t.Helper() - query := url.Values{} - if fileName != "" { - query.Set("filename", fileName) - } - res := c.MustDoFunc( - t, "POST", []string{"_matrix", "media", "v3", "upload"}, - WithRawBody(fileBody), WithContentType(contentType), WithQueries(query), - ) - body := ParseJSON(t, res) - return GetJSONFieldStr(t, body, "content_uri") -} - -// Use an empty string to remove a custom displayname. -func (c *CSAPI) SetDisplayname(t *testing.T, name string) { - t.Helper() - reqBody := map[string]any{} - if name != "" { - reqBody["displayname"] = name - } - c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "profile", c.UserID, "displayname"}, WithJSONBody(t, reqBody)) -} - -// Use an empty string to remove your avatar. -func (c *CSAPI) SetAvatar(t *testing.T, avatarURL string) { - t.Helper() - reqBody := map[string]interface{}{ - "avatar_url": avatarURL, - } - c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "profile", c.UserID, "avatar_url"}, WithJSONBody(t, reqBody)) - c.AvatarURL = avatarURL -} - -// DownloadContent downloads media from the server, returning the raw bytes and the Content-Type. Fails the test on error. -func (c *CSAPI) DownloadContent(t *testing.T, mxcUri string) ([]byte, string) { - t.Helper() - origin, mediaId := SplitMxc(mxcUri) - res := c.MustDo(t, "GET", []string{"_matrix", "media", "v3", "download", origin, mediaId}, struct{}{}) - contentType := res.Header.Get("Content-Type") - b, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Error(err) - } - return b, contentType -} - -// CreateRoom creates a room with an optional HTTP request body. Fails the test on error. Returns the room ID. -func (c *CSAPI) CreateRoom(t *testing.T, reqBody map[string]any) string { - t.Helper() - res := c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "createRoom"}, reqBody) - body := ParseJSON(t, res) - return GetJSONFieldStr(t, body, "room_id") -} - -// JoinRoom joins the room ID or alias given, else fails the test. Returns the room ID, -// NOT the join event ID! (c.f. https://github.com/matrix-org/matrix-spec/issues/1545) -func (c *CSAPI) JoinRoom(t *testing.T, roomIDOrAlias string, serverNames []string) string { - t.Helper() - // construct URL query parameters - query := make(url.Values, len(serverNames)) - for _, serverName := range serverNames { - query.Add("server_name", serverName) - } - // join the room - res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "join", roomIDOrAlias}, WithQueries(query)) - // return the room ID if we joined with it - if roomIDOrAlias[0] == '!' { - return roomIDOrAlias - } - // otherwise we should be told the room ID if we joined via an alias - body := ParseJSON(t, res) - return GetJSONFieldStr(t, body, "room_id") -} - -func (c *CSAPI) SendTyping(t *testing.T, roomID string, isTyping bool, durMs int) { - t.Helper() - c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "typing", c.UserID}, WithJSONBody(t, map[string]interface{}{ - "timeout": durMs, - "typing": isTyping, - })) -} - -// LeaveRoom joins the room ID, else fails the test. -func (c *CSAPI) LeaveRoom(t *testing.T, roomID string) { - t.Helper() - // leave the room - c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "leave"}) -} - -// InviteRoom invites userID to the room ID, else fails the test. -func (c *CSAPI) InviteRoom(t *testing.T, roomID string, userID string) { - t.Helper() - // Invite the user to the room - body := map[string]interface{}{ - "user_id": userID, - } - c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "invite"}, body) -} - -func (c *CSAPI) GetGlobalAccountData(t *testing.T, eventType string) *http.Response { - return c.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType}) -} - -func (c *CSAPI) SetGlobalAccountData(t *testing.T, eventType string, content map[string]interface{}) *http.Response { - return c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "account_data", eventType}, WithJSONBody(t, content)) -} - -func (c *CSAPI) SetRoomAccountData(t *testing.T, roomID, eventType string, content map[string]interface{}) *http.Response { - return c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "user", c.UserID, "rooms", roomID, "account_data", eventType}, WithJSONBody(t, content)) -} - -// SendEventUnsynced sends `e` into the room and returns the event ID of the sent event. -func (c *CSAPI) SendEventUnsynced(t *testing.T, roomID string, e Event) string { - t.Helper() - c.txnID++ - paths := []string{"_matrix", "client", "v3", "rooms", roomID, "send", e.Type, strconv.Itoa(c.txnID)} - if e.StateKey != nil { - paths = []string{"_matrix", "client", "v3", "rooms", roomID, "state", e.Type, *e.StateKey} - } - res := c.MustDo(t, "PUT", paths, e.Content) - body := ParseJSON(t, res) - eventID := GetJSONFieldStr(t, body, "event_id") - return eventID -} - -// SendEventSynced sends `e` into the room and waits for its event ID to come down /sync. -// NB: This is specifically v2 sync, not v3 sliding sync!! -// Returns the event ID of the sent event. -func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e Event) string { - t.Helper() - eventID := c.SendEventUnsynced(t, roomID, e) - t.Logf("SendEventSynced waiting for event ID %s", eventID) - c.MustSyncUntil(t, SyncReq{}, SyncTimelineHas(roomID, func(r gjson.Result) bool { - return r.Get("event_id").Str == eventID - })) - return eventID -} - -func (c *CSAPI) RedactEvent(t *testing.T, roomID, eventID string) string { - c.txnID++ - res := c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "redact", eventID, strconv.Itoa(c.txnID)}, WithJSONBody(t, map[string]interface{}{ - "reason": "who knows", - })) - body := ParseJSON(t, res) - return GetJSONFieldStr(t, body, "event_id") -} - -func (c *CSAPI) SendReceipt(t *testing.T, roomID, eventID, receiptType string) *http.Response { - return c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"}, WithJSONBody(t, map[string]interface{}{ - receiptType: eventID, - })) -} - -// Perform a single /sync request with the given request options. To sync until something happens, -// see `MustSyncUntil`. -// -// Fails the test if the /sync request does not return 200 OK. -// Returns the top-level parsed /sync response JSON as well as the next_batch token from the response. -func (c *CSAPI) MustSync(t *testing.T, syncReq SyncReq) (gjson.Result, string) { - t.Helper() - query := url.Values{ - "timeout": []string{"1000"}, - } - // configure the HTTP request based on SyncReq - if syncReq.TimeoutMillis != "" { - query["timeout"] = []string{syncReq.TimeoutMillis} - } - if syncReq.Since != "" { - query["since"] = []string{syncReq.Since} - } - if syncReq.Filter != "" { - query["filter"] = []string{syncReq.Filter} - } - if syncReq.FullState { - query["full_state"] = []string{"true"} - } - if syncReq.SetPresence != "" { - query["set_presence"] = []string{syncReq.SetPresence} - } - res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "sync"}, WithQueries(query)) - body := ParseJSON(t, res) - result := gjson.ParseBytes(body) - nextBatch := GetJSONFieldStr(t, body, "next_batch") - return result, nextBatch -} - -// MustSyncUntil blocks and continually calls /sync (advancing the since token) until all the -// check functions return no error. Returns the final/latest since token. -// -// Initial /sync example: (no since token) -// -// bob.InviteRoom(t, roomID, alice.UserID) -// alice.JoinRoom(t, roomID, nil) -// alice.MustSyncUntil(t, client.SyncReq{}, client.SyncJoinedTo(alice.UserID, roomID)) -// -// Incremental /sync example: (test controls since token) -// -// since := alice.MustSyncUntil(t, client.SyncReq{TimeoutMillis: "0"}) // get a since token -// bob.InviteRoom(t, roomID, alice.UserID) -// since = alice.MustSyncUntil(t, client.SyncReq{Since: since}, client.SyncInvitedTo(alice.UserID, roomID)) -// alice.JoinRoom(t, roomID, nil) -// alice.MustSyncUntil(t, client.SyncReq{Since: since}, client.SyncJoinedTo(alice.UserID, roomID)) -// -// Checking multiple parts of /sync: -// -// alice.MustSyncUntil( -// t, client.SyncReq{}, -// client.SyncJoinedTo(alice.UserID, roomID), -// client.SyncJoinedTo(alice.UserID, roomID2), -// client.SyncJoinedTo(alice.UserID, roomID3), -// ) -// -// Check functions are unordered and independent. Once a check function returns true it is removed -// from the list of checks and won't be called again. -// -// In the unlikely event that you want all the checkers to pass *explicitly* in a single /sync -// response (e.g to assert some form of atomic update which updates multiple parts of the /sync -// response at once) then make your own checker function which does this. -// -// In the unlikely event that you need ordering on your checks, call MustSyncUntil multiple times -// with a single checker, and reuse the returned since token, as in the "Incremental sync" example. -// -// Will time out after CSAPI.SyncUntilTimeout. Returns the `next_batch` token from the final -// response. -func (c *CSAPI) MustSyncUntil(t *testing.T, syncReq SyncReq, checks ...SyncCheckOpt) string { - t.Helper() - start := time.Now() - numResponsesReturned := 0 - checkers := make([]struct { - check SyncCheckOpt - errs []string - }, len(checks)) - for i := range checks { - c := checkers[i] - c.check = checks[i] - checkers[i] = c - } - printErrors := func() string { - err := "Checkers:\n" - for _, c := range checkers { - err += strings.Join(c.errs, "\n") - err += ", \n" - } - return err - } - for { - if time.Since(start) > c.SyncUntilTimeout { - t.Fatalf("%s MustSyncUntil: timed out after %v. Seen %d /sync responses. %s", c.UserID, time.Since(start), numResponsesReturned, printErrors()) - } - response, nextBatch := c.MustSync(t, syncReq) - syncReq.Since = nextBatch - numResponsesReturned += 1 - - for i := 0; i < len(checkers); i++ { - err := checkers[i].check(c.UserID, response) - if err == nil { - // check passed, removed from checkers - checkers = append(checkers[:i], checkers[i+1:]...) - i-- - } else { - c := checkers[i] - c.errs = append(c.errs, fmt.Sprintf("[t=%v] Response #%d: %s", time.Since(start), numResponsesReturned, err)) - checkers[i] = c - } - } - if len(checkers) == 0 { - // every checker has passed! - time.Sleep(10 * time.Millisecond) // sleep a very small amount to give the proxy time to process data - return syncReq.Since - } - } -} - -// RegisterUser will register the user with given parameters and -// return user ID & access token, and fail the test on network error -func (c *CSAPI) RegisterUser(t *testing.T, localpart, password string) (userID, accessToken, deviceID string) { - t.Helper() - reqBody := map[string]interface{}{ - "auth": map[string]string{ - "type": "m.login.dummy", - }, - "username": localpart, - "password": password, - } - res := c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "register"}, reqBody) - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatalf("unable to read response body: %v", err) - } - - userID = gjson.GetBytes(body, "user_id").Str - accessToken = gjson.GetBytes(body, "access_token").Str - deviceID = gjson.GetBytes(body, "device_id").Str - return userID, accessToken, deviceID -} - -// LoginUser will create a new device for the given user. -// The new access token and device ID are overwrite those of the current CSAPI instance. -func (c *CSAPI) Login(t *testing.T, password, deviceID string) { - t.Helper() - reqBody := map[string]interface{}{ - "type": "m.login.password", - "password": password, - "identifier": map[string]interface{}{ - "type": "m.id.user", - "user": c.UserID, - }, - "device_id": deviceID, - } - res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "login"}, WithJSONBody(t, reqBody)) - - body, err := io.ReadAll(res.Body) - if err != nil { - t.Fatalf("unable to read response body: %v", err) - } - - userID := gjson.GetBytes(body, "user_id").Str - if c.UserID != userID { - t.Fatalf("Logged in as %s but response included user_id=%s", c.UserID, userID) - } - gotDeviceID := gjson.GetBytes(body, "device_id").Str - if gotDeviceID != deviceID { - t.Fatalf("Asked for device ID %s but got %s", deviceID, gotDeviceID) - } - accessToken := gjson.GetBytes(body, "access_token").Str - if c.AccessToken == accessToken { - t.Fatalf("Logged in as %s but access token did not change (still %s)", c.UserID, c.AccessToken) - } - - c.AccessToken = accessToken - c.DeviceID = deviceID -} - -// SetState PUTs a piece of state in a room and returns the event ID of the created state event. -func (c *CSAPI) SetState(t *testing.T, roomID, eventType, stateKey string, content map[string]interface{}) string { - t.Helper() - res := c.MustDoFunc( - t, - "PUT", - []string{"_matrix", "client", "v3", "rooms", roomID, "state", eventType, stateKey}, - WithJSONBody(t, content), - ) - - body, err := io.ReadAll(res.Body) - if err != nil { - t.Fatalf("unable to read response body: %v", err) - } - - return gjson.ParseBytes(body).Get("event_id").Str -} - -// RegisterSharedSecret registers a new account with a shared secret via HMAC -// See https://github.com/matrix-org/synapse/blob/e550ab17adc8dd3c48daf7fedcd09418a73f524b/synapse/_scripts/register_new_matrix_user.py#L40 -func (c *CSAPI) RegisterSharedSecret(t *testing.T, user, pass string, isAdmin bool) (userID, accessToken, deviceID string) { - resp := c.DoFunc(t, "GET", []string{"_synapse", "admin", "v1", "register"}) - if resp.StatusCode != 200 { - t.Skipf("Homeserver image does not support shared secret registration, /_synapse/admin/v1/register returned HTTP %d", resp.StatusCode) - return - } - body := ParseJSON(t, resp) - nonce := gjson.GetBytes(body, "nonce") - if !nonce.Exists() { - t.Fatalf("Malformed shared secret GET response: %s", string(body)) - } - mac := hmac.New(sha1.New, []byte(SharedSecret)) - mac.Write([]byte(nonce.Str)) - mac.Write([]byte("\x00")) - mac.Write([]byte(user)) - mac.Write([]byte("\x00")) - mac.Write([]byte(pass)) - mac.Write([]byte("\x00")) - if isAdmin { - mac.Write([]byte("admin")) - } else { - mac.Write([]byte("notadmin")) - } - sig := mac.Sum(nil) - reqBody := map[string]interface{}{ - "nonce": nonce.Str, - "username": user, - "password": pass, - "mac": hex.EncodeToString(sig), - "admin": isAdmin, - } - resp = c.MustDoFunc(t, "POST", []string{"_synapse", "admin", "v1", "register"}, WithJSONBody(t, reqBody)) - body = ParseJSON(t, resp) - userID = gjson.GetBytes(body, "user_id").Str - accessToken = gjson.GetBytes(body, "access_token").Str - deviceID = gjson.GetBytes(body, "device_id").Str - return userID, accessToken, deviceID -} - -// GetCapbabilities queries the server's capabilities -func (c *CSAPI) GetCapabilities(t *testing.T) []byte { - t.Helper() - res := c.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "capabilities"}) - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatalf("unable to read response body: %v", err) - } - return body -} - -// GetDefaultRoomVersion returns the server's default room version -func (c *CSAPI) GetDefaultRoomVersion(t *testing.T) gomatrixserverlib.RoomVersion { - t.Helper() - capabilities := c.GetCapabilities(t) - defaultVersion := gjson.GetBytes(capabilities, `capabilities.m\.room_versions.default`) - if !defaultVersion.Exists() { - // spec says use RoomV1 in this case - return gomatrixserverlib.RoomVersionV1 - } - - return gomatrixserverlib.RoomVersion(defaultVersion.Str) -} - -// MustDo will do the HTTP request and fail the test if the response is not 2xx -// -// Deprecated: Prefer MustDoFunc. MustDo is the older format which doesn't allow for vargs -// and will be removed in the future. MustDoFunc also logs HTTP response bodies on error. -func (c *CSAPI) MustDo(t *testing.T, method string, paths []string, jsonBody interface{}) *http.Response { - t.Helper() - res := c.DoFunc(t, method, paths, WithJSONBody(t, jsonBody)) - if res.StatusCode < 200 || res.StatusCode >= 300 { - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - t.Fatalf("CSAPI.MustDo %s %s returned HTTP %d : %s", method, res.Request.URL.String(), res.StatusCode, string(body)) - } - return res -} - -// WithRawBody sets the HTTP request body to `body` -func WithRawBody(body []byte) RequestOpt { - return func(req *http.Request) { - req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) - // we need to manually set this because we don't set the body - // in http.NewRequest due to using functional options, and only in NewRequest - // does the stdlib set this for us. - req.ContentLength = int64(len(body)) - } -} - -// WithContentType sets the HTTP request Content-Type header to `cType` -func WithContentType(cType string) RequestOpt { - return func(req *http.Request) { - req.Header.Set("Content-Type", cType) - } -} - -// WithJSONBody sets the HTTP request body to the JSON serialised form of `obj` -func WithJSONBody(t *testing.T, obj interface{}) RequestOpt { - return func(req *http.Request) { - t.Helper() - b, err := json.Marshal(obj) - if err != nil { - t.Fatalf("CSAPI.Do failed to marshal JSON body: %s", err) - } - WithRawBody(b)(req) - } -} - -// WithQueries sets the query parameters on the request. -// This function should not be used to set an "access_token" parameter for Matrix authentication. -// Instead, set CSAPI.AccessToken. -func WithQueries(q url.Values) RequestOpt { - return func(req *http.Request) { - req.URL.RawQuery = q.Encode() - } -} - -// WithRetryUntil will retry the request until the provided function returns true. Times out after -// `timeout`, which will then fail the test. -func WithRetryUntil(timeout time.Duration, untilFn func(res *http.Response) bool) RequestOpt { - return func(req *http.Request) { - until := req.Context().Value(CtxKeyWithRetryUntil).(*retryUntilParams) - until.timeout = timeout - until.untilFn = untilFn - } -} - -// MustDoFunc is the same as DoFunc but fails the test if the returned HTTP response code is not 2xx. -func (c *CSAPI) MustDoFunc(t *testing.T, method string, paths []string, opts ...RequestOpt) *http.Response { - t.Helper() - res := c.DoFunc(t, method, paths, opts...) - if res.StatusCode < 200 || res.StatusCode >= 300 { - defer res.Body.Close() - body, _ := ioutil.ReadAll(res.Body) - t.Fatalf("CSAPI.MustDoFunc response return non-2xx code: %s - body: %s", res.Status, string(body)) - } - return res -} - -func (c *CSAPI) SendToDevice(t *testing.T, eventType, userID, deviceID string, content map[string]interface{}) { - c.txnID++ - c.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "sendToDevice", eventType, strconv.Itoa(c.txnID)}, WithJSONBody(t, map[string]interface{}{ - "messages": map[string]interface{}{ - userID: map[string]interface{}{ - deviceID: content, - }, - }, - })) + *client.CSAPI + Localpart string + Domain string + AvatarURL string } // SlidingSync performs a single sliding sync request -func (c *CSAPI) SlidingSync(t *testing.T, data sync3.Request, opts ...RequestOpt) (resBody *sync3.Response) { +func (c *CSAPI) SlidingSync(t *testing.T, data sync3.Request, opts ...client.RequestOpt) (resBody *sync3.Response) { t.Helper() if len(opts) == 0 { - opts = append(opts, WithQueries(url.Values{ + opts = append(opts, client.WithQueries(url.Values{ "timeout": []string{"500"}, })) } - opts = append(opts, WithJSONBody(t, data)) - res := c.MustDoFunc(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, opts...) - body := ParseJSON(t, res) + opts = append(opts, client.WithJSONBody(t, data)) + // copy the CSAPI struct and tweak the base URL so we talk to the proxy not synapse + csapi := *c.CSAPI + csapi.BaseURL = proxyBaseURL + res := csapi.MustDo(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, opts...) + body := client.ParseJSON(t, res) if err := json.Unmarshal(body, &resBody); err != nil { t.Fatalf("failed to unmarshal response: %v", err) } @@ -687,7 +81,7 @@ func (c *CSAPI) SlidingSyncUntilMembership(t *testing.T, pos string, roomID stri } if membership == "join" || membership == "invite" { - content["displayname"] = target.Localpart + content["displayname"] = target.CSAPI.UserID // FIXME } if membership == "invite" && c == target { @@ -750,8 +144,8 @@ func (c *CSAPI) SlidingSyncUntil(t *testing.T, pos string, data sync3.Request, c if pos != "" { qps["pos"] = []string{pos} } - opts := []RequestOpt{ - WithQueries(qps), + opts := []client.RequestOpt{ + client.WithQueries(qps), } res := c.SlidingSync(t, data, opts...) pos = res.Pos @@ -783,319 +177,77 @@ func (c *CSAPI) SlidingSyncUntilEvent(t *testing.T, pos string, data sync3.Reque }) } -// DoFunc performs an arbitrary HTTP request to the server. This function supports RequestOpts to set -// extra information on the request such as an HTTP request body, query parameters and content-type. -// See all functions in this package starting with `With...`. -// -// Fails the test if an HTTP request could not be made or if there was a network error talking to the -// server. To do assertions on the HTTP response, see the `must` package. For example: -// -// must.MatchResponse(t, res, match.HTTPResponse{ -// StatusCode: 400, -// JSON: []match.JSON{ -// match.JSONKeyEqual("errcode", "M_INVALID_USERNAME"), -// }, -// }) -func (c *CSAPI) DoFunc(t *testing.T, method string, paths []string, opts ...RequestOpt) *http.Response { +// LoginUser will create a new device for the given user. +// The new access token and device ID are overwrite those of the current CSAPI instance. +func (c *CSAPI) Login(t *testing.T, password, deviceID string) { t.Helper() - isSlidingSync := false - for i := range paths { - if paths[i] == "org.matrix.msc3575" { - isSlidingSync = true - } - paths[i] = url.PathEscape(paths[i]) + reqBody := map[string]interface{}{ + "type": "m.login.password", + "password": password, + "identifier": map[string]interface{}{ + "type": "m.id.user", + "user": c.UserID, + }, + "device_id": deviceID, } - var reqURL string - if isSlidingSync { - reqURL = proxyBaseURL + "/" + strings.Join(paths, "/") - } else { - reqURL = c.BaseURL + "/" + strings.Join(paths, "/") - } - req, err := http.NewRequest(method, reqURL, nil) + res := c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "login"}, client.WithJSONBody(t, reqBody)) + + body, err := io.ReadAll(res.Body) if err != nil { - t.Fatalf("CSAPI.DoFunc failed to create http.NewRequest: %s", err) + t.Fatalf("unable to read response body: %v", err) } - // set defaults before RequestOpts - if c.AccessToken != "" { - req.Header.Set("Authorization", "Bearer "+c.AccessToken) - } - retryUntil := &retryUntilParams{} - ctx := context.WithValue(req.Context(), CtxKeyWithRetryUntil, retryUntil) - req = req.WithContext(ctx) - // set functional options - for _, o := range opts { - o(req) + userID := gjson.GetBytes(body, "user_id").Str + if c.UserID != userID { + t.Fatalf("Logged in as %s but response included user_id=%s", c.UserID, userID) } - // set defaults after RequestOpts - if req.Header.Get("Content-Type") == "" { - req.Header.Set("Content-Type", "application/json") + gotDeviceID := gjson.GetBytes(body, "device_id").Str + if gotDeviceID != deviceID { + t.Fatalf("Asked for device ID %s but got %s", deviceID, gotDeviceID) } - // debug log the request - if c.Debug { - t.Logf("Making %s request to %s (%s)", method, req.URL, c.AccessToken) - contentType := req.Header.Get("Content-Type") - if contentType == "application/json" || strings.HasPrefix(contentType, "text/") { - if req.Body != nil { - body, _ := ioutil.ReadAll(req.Body) - t.Logf("Request body: %s", string(body)) - req.Body = ioutil.NopCloser(bytes.NewBuffer(body)) - } - } else { - t.Logf("Request body: ", contentType) - } + accessToken := gjson.GetBytes(body, "access_token").Str + if c.AccessToken == accessToken { + t.Fatalf("Logged in as %s but access token did not change (still %s)", c.UserID, c.AccessToken) } - now := time.Now() - for { - // Perform the HTTP request - res, err := c.Client.Do(req) - if err != nil { - t.Fatalf("CSAPI.DoFunc response returned error: %s", err) - } - // debug log the response - if c.Debug && res != nil { - var dump []byte - dump, err = httputil.DumpResponse(res, true) - if err != nil { - t.Fatalf("CSAPI.DoFunc failed to dump response body: %s", err) - } - t.Logf("%s", string(dump)) - } - if retryUntil == nil || retryUntil.timeout == 0 { - return res // don't retry - } - // check the condition, make a copy of the response body first in case the check consumes it - var resBody []byte - if res.Body != nil { - resBody, err = ioutil.ReadAll(res.Body) - if err != nil { - t.Fatalf("CSAPI.DoFunc failed to read response body for RetryUntil check: %s", err) - } - res.Body = io.NopCloser(bytes.NewBuffer(resBody)) - } - if retryUntil.untilFn(res) { - // remake the response and return - res.Body = io.NopCloser(bytes.NewBuffer(resBody)) - return res - } - // condition not satisfied, do we timeout yet? - if time.Since(now) > retryUntil.timeout { - t.Fatalf("CSAPI.DoFunc RetryUntil: %v %v timed out after %v", method, req.URL, retryUntil.timeout) - } - t.Logf("CSAPI.DoFunc RetryUntil: %v %v response condition not yet met, retrying", method, req.URL) - // small sleep to avoid tight-looping - time.Sleep(100 * time.Millisecond) - } + c.AccessToken = accessToken + c.DeviceID = deviceID } -// NewLoggedClient returns an http.Client which logs requests/responses -func NewLoggedClient(t *testing.T, hsName string, cli *http.Client) *http.Client { +// Use an empty string to remove a custom displayname. +func (c *CSAPI) SetDisplayname(t *testing.T, name string) { t.Helper() - if cli == nil { - cli = &http.Client{ - Timeout: 30 * time.Second, - } + reqBody := map[string]any{} + if name != "" { + reqBody["displayname"] = name } - transport := cli.Transport - if transport == nil { - transport = http.DefaultTransport - } - cli.Transport = &loggedRoundTripper{t, hsName, transport} - return cli + c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "profile", c.UserID, "displayname"}, client.WithJSONBody(t, reqBody)) } -type loggedRoundTripper struct { - t *testing.T - hsName string - wrap http.RoundTripper -} - -func (t *loggedRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - start := time.Now() - res, err := t.wrap.RoundTrip(req) - if err != nil { - t.t.Logf("%s %s%s => error: %s (%s)", req.Method, t.hsName, req.URL.Path, err, time.Since(start)) - } else { - t.t.Logf("%s %s%s => %s (%s)", req.Method, t.hsName, req.URL.Path, res.Status, time.Since(start)) - } - return res, err -} - -// GetJSONFieldStr extracts a value from a byte-encoded JSON body given a search key -func GetJSONFieldStr(t *testing.T, body []byte, wantKey string) string { +// Use an empty string to remove your avatar. +func (c *CSAPI) SetAvatar(t *testing.T, avatarURL string) { t.Helper() - res := gjson.GetBytes(body, wantKey) - if !res.Exists() { - t.Fatalf("JSONFieldStr: key '%s' missing from %s", wantKey, string(body)) + reqBody := map[string]interface{}{ + "avatar_url": avatarURL, } - if res.Str == "" { - t.Fatalf("JSONFieldStr: key '%s' is not a string, body: %s", wantKey, string(body)) - } - return res.Str + c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "profile", c.UserID, "avatar_url"}, client.WithJSONBody(t, reqBody)) + c.AvatarURL = avatarURL } -func GetJSONFieldStringArray(t *testing.T, body []byte, wantKey string) []string { - t.Helper() - - res := gjson.GetBytes(body, wantKey) - - if !res.Exists() { - t.Fatalf("JSONFieldStr: key '%s' missing from %s", wantKey, string(body)) - } - - arrLength := len(res.Array()) - arr := make([]string, arrLength) - i := 0 - res.ForEach(func(key, value gjson.Result) bool { - arr[i] = value.Str - - // Keep iterating - i++ - return true - }) - - return arr +func (c *CSAPI) SendReceipt(t *testing.T, roomID, eventID, receiptType string) *http.Response { + return c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"}, client.WithJSONBody(t, map[string]interface{}{ + receiptType: eventID, + })) } -// ParseJSON parses a JSON-encoded HTTP Response body into a byte slice -func ParseJSON(t *testing.T, res *http.Response) []byte { - t.Helper() - defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatalf("MustParseJSON: reading HTTP response body returned %s", err) - } - if !gjson.ValidBytes(body) { - t.Fatalf("MustParseJSON: Response is not valid JSON") - } - return body -} - -// GjsonEscape escapes . and * from the input so it can be used with gjson.Get -func GjsonEscape(in string) string { - in = strings.ReplaceAll(in, ".", `\.`) - in = strings.ReplaceAll(in, "*", `\*`) - return in -} - -// Check that the timeline for `roomID` has an event which passes the check function. -func SyncTimelineHas(roomID string, check func(gjson.Result) bool) SyncCheckOpt { - return func(clientUserID string, topLevelSyncJSON gjson.Result) error { - err := loopArray( - topLevelSyncJSON, "rooms.join."+GjsonEscape(roomID)+".timeline.events", check, - ) - if err == nil { - return nil - } - return fmt.Errorf("SyncTimelineHas(%s): %s", roomID, err) +func NewEncryptionEvent() b.Event { + return b.Event{ + Type: "m.room.encryption", + StateKey: ptr(""), + Content: map[string]interface{}{ + "algorithm": "m.megolm.v1.aes-sha2", + "rotation_period_ms": 604800000, + "rotation_period_msgs": 100, + }, } } - -// Check that the timeline for `roomID` has an event which matches the event ID. -func SyncTimelineHasEventID(roomID string, eventID string) SyncCheckOpt { - return SyncTimelineHas(roomID, func(ev gjson.Result) bool { - return ev.Get("event_id").Str == eventID - }) -} - -// Checks that `userID` gets invited to `roomID`. -// -// This checks different parts of the /sync response depending on the client making the request. -// If the client is also the person being invited to the room then the 'invite' block will be inspected. -// If the client is different to the person being invited then the 'join' block will be inspected. -func SyncInvitedTo(userID, roomID string) SyncCheckOpt { - return func(clientUserID string, topLevelSyncJSON gjson.Result) error { - // two forms which depend on what the client user is: - // - passively viewing an invite for a room you're joined to (timeline events) - // - actively being invited to a room. - if clientUserID == userID { - // active - err := loopArray( - topLevelSyncJSON, "rooms.invite."+GjsonEscape(roomID)+".invite_state.events", - func(ev gjson.Result) bool { - return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == userID && ev.Get("content.membership").Str == "invite" - }, - ) - if err != nil { - return fmt.Errorf("SyncInvitedTo(%s): %s", roomID, err) - } - return nil - } - // passive - return SyncTimelineHas(roomID, func(ev gjson.Result) bool { - return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == userID && ev.Get("content.membership").Str == "invite" - })(clientUserID, topLevelSyncJSON) - } -} - -// Check that `userID` gets joined to `roomID` by inspecting the join timeline for a membership event -func SyncJoinedTo(userID, roomID string) SyncCheckOpt { - return func(clientUserID string, topLevelSyncJSON gjson.Result) error { - // awkward wrapping to get the error message correct at the start :/ - err := SyncTimelineHas(roomID, func(ev gjson.Result) bool { - return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == userID && ev.Get("content.membership").Str == "join" - })(clientUserID, topLevelSyncJSON) - if err == nil { - return nil - } - return fmt.Errorf("SyncJoinedTo(%s,%s): %s", userID, roomID, err) - } -} - -// Check that `userID` is leaving `roomID` by inspecting the timeline for a membership event, or witnessing `roomID` in `rooms.leave` -// Note: This will not work properly with initial syncs, see https://github.com/matrix-org/matrix-doc/issues/3537 -func SyncLeftFrom(userID, roomID string) SyncCheckOpt { - return func(clientUserID string, topLevelSyncJSON gjson.Result) error { - // two forms which depend on what the client user is: - // - passively viewing a membership for a room you're joined in - // - actively leaving the room - if clientUserID == userID { - // active - events := topLevelSyncJSON.Get("rooms.leave." + GjsonEscape(roomID)) - if !events.Exists() { - return fmt.Errorf("no leave section for room %s", roomID) - } else { - return nil - } - } - // passive - return SyncTimelineHas(roomID, func(ev gjson.Result) bool { - return ev.Get("type").Str == "m.room.member" && ev.Get("state_key").Str == userID && ev.Get("content.membership").Str == "leave" - })(clientUserID, topLevelSyncJSON) - } -} - -// Calls the `check` function for each global account data event, and returns with success if the -// `check` function returns true for at least one event. -func SyncGlobalAccountDataHas(check func(gjson.Result) bool) SyncCheckOpt { - return func(clientUserID string, topLevelSyncJSON gjson.Result) error { - return loopArray(topLevelSyncJSON, "account_data.events", check) - } -} - -func loopArray(object gjson.Result, key string, check func(gjson.Result) bool) error { - array := object.Get(key) - if !array.Exists() { - return fmt.Errorf("Key %s does not exist", key) - } - if !array.IsArray() { - return fmt.Errorf("Key %s exists but it isn't an array", key) - } - goArray := array.Array() - for _, ev := range goArray { - if check(ev) { - return nil - } - } - return fmt.Errorf("check function did not pass while iterating over %d elements: %v", len(goArray), array.Raw) -} - -// Splits an MXC URI into its origin and media ID parts -func SplitMxc(mxcUri string) (string, string) { - mxcParts := strings.Split(strings.TrimPrefix(mxcUri, "mxc://"), "/") - origin := mxcParts[0] - mediaId := strings.Join(mxcParts[1:], "/") - - return origin, mediaId -} diff --git a/tests-e2e/conns_test.go b/tests-e2e/conns_test.go index a9c19c8..ce5182c 100644 --- a/tests-e2e/conns_test.go +++ b/tests-e2e/conns_test.go @@ -9,13 +9,15 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) func TestInvalidTokenReturnsMUnknownTokenError(t *testing.T) { alice := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{}) // normal sliding sync alice.SlidingSync(t, sync3.Request{ ConnID: "A", @@ -26,15 +28,15 @@ func TestInvalidTokenReturnsMUnknownTokenError(t *testing.T) { }, }) // invalidate the access token - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "logout"}) + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "logout"}) // let the proxy realise the token is expired and tell downstream time.Sleep(time.Second) var invalidResponses []*http.Response // using the same token now returns a 401 with M_UNKNOWN_TOKEN - httpRes := alice.DoFunc(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, WithQueries(url.Values{ + httpRes := alice.Do(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, client.WithQueries(url.Values{ "timeout": []string{"500"}, - }), WithJSONBody(t, sync3.Request{ + }), client.WithJSONBody(t, sync3.Request{ ConnID: "A", RoomSubscriptions: map[string]sync3.RoomSubscription{ roomID: { @@ -45,9 +47,9 @@ func TestInvalidTokenReturnsMUnknownTokenError(t *testing.T) { invalidResponses = append(invalidResponses, httpRes) // using a bogus access token returns a 401 with M_UNKNOWN_TOKEN alice.AccessToken = "flibble_wibble" - httpRes = alice.DoFunc(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, WithQueries(url.Values{ + httpRes = alice.Do(t, "POST", []string{"_matrix", "client", "unstable", "org.matrix.msc3575", "sync"}, client.WithQueries(url.Values{ "timeout": []string{"500"}, - }), WithJSONBody(t, sync3.Request{ + }), client.WithJSONBody(t, sync3.Request{ ConnID: "A", RoomSubscriptions: map[string]sync3.RoomSubscription{ roomID: { @@ -84,7 +86,7 @@ func TestInvalidTokenReturnsMUnknownTokenError(t *testing.T) { // Test that you can have multiple connections with the same device, and they work independently. func TestMultipleConns(t *testing.T) { alice := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{}) resA := alice.SlidingSync(t, sync3.Request{ ConnID: "A", @@ -111,7 +113,7 @@ func TestMultipleConns(t *testing.T) { }, }) - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.name", StateKey: ptr(""), Content: map[string]interface{}{"name": "pub"}, diff --git a/tests-e2e/gappy_state_test.go b/tests-e2e/gappy_state_test.go index a8004a1..ce6a3fd 100644 --- a/tests-e2e/gappy_state_test.go +++ b/tests-e2e/gappy_state_test.go @@ -2,9 +2,11 @@ package syncv3_test import ( "fmt" + "testing" + + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" - "testing" ) // Test that state changes "missed" by a poller are injected back into the room when a @@ -15,10 +17,10 @@ func TestGappyState(t *testing.T) { t.Log("Alice creates a room") firstRoomName := "Romeo Oscar Oscar Mike" - roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": firstRoomName}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": firstRoomName}) t.Log("Alice sends a message into that room") - firstMessageID := alice.SendEventSynced(t, roomID, Event{ + firstMessageID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -50,19 +52,23 @@ func TestGappyState(t *testing.T) { ) t.Log("Alice logs out of her first device.") - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "logout"}) + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "logout"}) t.Log("Alice logs in again on her second device.") alice.Login(t, "password", "device2") t.Log("Alice changes the room name while the proxy isn't polling.") nameContent := map[string]interface{}{"name": "potato"} - alice.SetState(t, roomID, "m.room.name", "", nameContent) + alice.Unsafe_SendEventUnsynced(t, roomID, b.Event{ + Type: "m.room.name", + StateKey: ptr(""), + Content: nameContent, + }) t.Log("Alice sends lots of message events (more than the poller will request in a timeline.") var latestMessageID string for i := 0; i < 51; i++ { - latestMessageID = alice.SendEventUnsynced(t, roomID, Event{ + latestMessageID = alice.Unsafe_SendEventUnsynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", diff --git a/tests-e2e/ignored_user_test.go b/tests-e2e/ignored_user_test.go index 52c340b..1aba8df 100644 --- a/tests-e2e/ignored_user_test.go +++ b/tests-e2e/ignored_user_test.go @@ -2,9 +2,11 @@ package syncv3_test import ( "fmt" + "testing" + + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" - "testing" ) func TestInvitesFromIgnoredUsersOmitted(t *testing.T) { @@ -13,16 +15,16 @@ func TestInvitesFromIgnoredUsersOmitted(t *testing.T) { nigel := registerNamedUser(t, "nigel") t.Log("Nigel create two public rooms. Bob joins both.") - room1 := nigel.CreateRoom(t, map[string]any{"preset": "public_chat", "name": "room 1"}) - room2 := nigel.CreateRoom(t, map[string]any{"preset": "public_chat", "name": "room 2"}) + room1 := nigel.MustCreateRoom(t, map[string]any{"preset": "public_chat", "name": "room 1"}) + room2 := nigel.MustCreateRoom(t, map[string]any{"preset": "public_chat", "name": "room 2"}) bob.JoinRoom(t, room1, nil) bob.JoinRoom(t, room2, nil) t.Log("Alice makes a room for dumping sentinel messages.") - aliceRoom := alice.CreateRoom(t, map[string]any{"preset": "private_chat"}) + aliceRoom := alice.MustCreateRoom(t, map[string]any{"preset": "private_chat"}) t.Log("Alice ignores Nigel.") - alice.SetGlobalAccountData(t, "m.ignored_user_list", map[string]any{ + alice.MustSetGlobalAccountData(t, "m.ignored_user_list", map[string]any{ "ignored_users": map[string]any{ nigel.UserID: map[string]any{}, }, @@ -35,7 +37,7 @@ func TestInvitesFromIgnoredUsersOmitted(t *testing.T) { bob.SlidingSyncUntilMembership(t, "", room1, alice, "invite") t.Log("Alice sends a sentinel message in her private room.") - sentinel := alice.SendEventSynced(t, aliceRoom, Event{ + sentinel := alice.SendEventSynced(t, aliceRoom, b.Event{ Type: "m.room.message", Content: map[string]any{ "body": "Hello, world!", @@ -69,7 +71,7 @@ func TestInvitesFromIgnoredUsersOmitted(t *testing.T) { bob.SlidingSyncUntilMembership(t, "", room1, alice, "invite") t.Log("Alice sends a sentinel message in her private room.") - sentinel = alice.SendEventSynced(t, aliceRoom, Event{ + sentinel = alice.SendEventSynced(t, aliceRoom, b.Event{ Type: "m.room.message", Content: map[string]any{ "body": "Hello, world, again", diff --git a/tests-e2e/lazy_loading_test.go b/tests-e2e/lazy_loading_test.go index 3cb99f9..b3e68af 100644 --- a/tests-e2e/lazy_loading_test.go +++ b/tests-e2e/lazy_loading_test.go @@ -3,6 +3,7 @@ package syncv3_test import ( "testing" + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -14,12 +15,12 @@ func TestLazyLoading(t *testing.T) { sentinel := registerNewUser(t) // dummy user to ensure that the proxy has processed sent events // all 3 join the room and say something - roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, roomID, nil) charlie.JoinRoom(t, roomID, nil) sentinel.JoinRoom(t, roomID, nil) - alice.SendEventSynced(t, roomID, Event{ + alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hello world", @@ -27,7 +28,7 @@ func TestLazyLoading(t *testing.T) { }, }) - bob.SendEventSynced(t, roomID, Event{ + bob.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hello world", @@ -35,7 +36,7 @@ func TestLazyLoading(t *testing.T) { }, }) - lastEventID := charlie.SendEventSynced(t, roomID, Event{ + lastEventID := charlie.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hello world", @@ -142,7 +143,7 @@ func TestLazyLoading(t *testing.T) { })) // alice now sends a message - aliceEventID := alice.SendEventSynced(t, roomID, Event{Type: "m.room.message", Content: map[string]interface{}{"body": "hello", "msgtype": "m.text"}}) + aliceEventID := alice.SendEventSynced(t, roomID, b.Event{Type: "m.room.message", Content: map[string]interface{}{"body": "hello", "msgtype": "m.text"}}) sentinel.SlidingSyncUntilEventID(t, "", roomID, aliceEventID) // bob, who didn't previously get alice's m.room.member event, should now see this @@ -161,7 +162,7 @@ func TestLazyLoading(t *testing.T) { })) // alice sends another message - aliceEventID2 := alice.SendEventSynced(t, roomID, Event{Type: "m.room.message", Content: map[string]interface{}{"body": "hello2", "msgtype": "m.text"}}) + aliceEventID2 := alice.SendEventSynced(t, roomID, b.Event{Type: "m.room.message", Content: map[string]interface{}{"body": "hello2", "msgtype": "m.text"}}) sentinel.SlidingSyncUntilEventID(t, "", roomID, aliceEventID2) // bob, who had just got alice's m.room.member event, shouldn't see it again. diff --git a/tests-e2e/lists_test.go b/tests-e2e/lists_test.go index 11dd97c..e39c9c0 100644 --- a/tests-e2e/lists_test.go +++ b/tests-e2e/lists_test.go @@ -2,12 +2,14 @@ package syncv3_test import ( "fmt" - "github.com/matrix-org/sliding-sync/sync3/extensions" - "github.com/tidwall/gjson" "sync" "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/sliding-sync/sync3/extensions" + "github.com/tidwall/gjson" + "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -20,13 +22,13 @@ func TestMultipleLists(t *testing.T) { var encryptedRoomIDs []string var unencryptedRoomIDs []string for i := 0; i < 10; i++ { - unencryptedRoomID := alice.CreateRoom(t, map[string]interface{}{ + unencryptedRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) unencryptedRoomIDs = append([]string{unencryptedRoomID}, unencryptedRoomIDs...) // push in array - encryptedRoomID := alice.CreateRoom(t, map[string]interface{}{ + encryptedRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", - "initial_state": []Event{ + "initial_state": []b.Event{ NewEncryptionEvent(), }, }) @@ -164,11 +166,11 @@ func TestMultipleListsDMUpdate(t *testing.T) { // make 5 group rooms and make 5 DMs rooms. Room 0 is most recent to ease checks for i := 0; i < 5; i++ { dmUserID := fmt.Sprintf("@dm_%d:synapse", i) // TODO: domain brittle - groupRoomID := alice.CreateRoom(t, map[string]interface{}{ + groupRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) groupRoomIDs = append([]string{groupRoomID}, groupRoomIDs...) // push in array - dmRoomID := alice.CreateRoom(t, map[string]interface{}{ + dmRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", "is_direct": true, "invite": []string{dmUserID}, @@ -178,7 +180,7 @@ func TestMultipleListsDMUpdate(t *testing.T) { time.Sleep(time.Millisecond) // ensure timestamp changes } // set the account data - alice.SetGlobalAccountData(t, "m.direct", dmContent) + alice.MustSetGlobalAccountData(t, "m.direct", dmContent) // request 2 lists, one set DM, one set no DM res := alice.SlidingSync(t, sync3.Request{ @@ -222,7 +224,7 @@ func TestMultipleListsDMUpdate(t *testing.T) { })) // now bring the last DM room to the top with a notif - pingEventID := alice.SendEventSynced(t, dmRoomIDs[len(dmRoomIDs)-1], Event{ + pingEventID := alice.SendEventSynced(t, dmRoomIDs[len(dmRoomIDs)-1], b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "ping", "msgtype": "m.text"}, }) @@ -269,7 +271,7 @@ func TestNewListMidConnection(t *testing.T) { var roomIDs []string // make rooms for i := 0; i < 4; i++ { - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) roomIDs = append([]string{roomID}, roomIDs...) // push in array @@ -319,7 +321,7 @@ func TestMultipleOverlappingLists(t *testing.T) { "preset": "private_chat", } if isEncrypted { - createContent["initial_state"] = []Event{ + createContent["initial_state"] = []b.Event{ NewEncryptionEvent(), } } @@ -327,7 +329,7 @@ func TestMultipleOverlappingLists(t *testing.T) { createContent["is_direct"] = true createContent["invite"] = []string{dmUserID} } - roomID := alice.CreateRoom(t, createContent) + roomID := alice.MustCreateRoom(t, createContent) time.Sleep(time.Millisecond) if isDM { var roomIDs []string @@ -346,7 +348,7 @@ func TestMultipleOverlappingLists(t *testing.T) { // set the account data t.Logf("DM rooms: %v", dmRoomIDs) t.Logf("Encrypted rooms: %v", encryptedRoomIDs) - alice.SetGlobalAccountData(t, "m.direct", dmContent) + alice.MustSetGlobalAccountData(t, "m.direct", dmContent) // seed the proxy: so we can get timeline correctly as it uses limit:1 initially. alice.SlidingSync(t, sync3.Request{}) @@ -357,7 +359,7 @@ func TestMultipleOverlappingLists(t *testing.T) { // - This ping message (always) roomToEventID := make(map[string]string, len(allRoomIDs)) for i := len(allRoomIDs) - 1; i >= 0; i-- { - roomToEventID[allRoomIDs[i]] = alice.SendEventSynced(t, allRoomIDs[i], Event{ + roomToEventID[allRoomIDs[i]] = alice.SendEventSynced(t, allRoomIDs[i], b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "ping", "msgtype": "m.text"}, }) @@ -539,7 +541,7 @@ func TestNot500OnNewRooms(t *testing.T) { }, }, }) - alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) alice.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ "a": { @@ -556,7 +558,7 @@ func TestNot500OnNewRooms(t *testing.T) { }, }, }, WithPos(res.Pos)) - alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) // should not 500 alice.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ @@ -594,7 +596,7 @@ func TestNewRoomNameCalculations(t *testing.T) { go func() { for i := range ch { roomName := fmt.Sprintf("room %d", i) - roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": roomName}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": roomName}) roomIDToName.Store(roomID, roomName) } }() @@ -698,7 +700,7 @@ func TestChangeSortOrder(t *testing.T) { } for i, name := range roomNames { - roomIDs[i] = alice.CreateRoom(t, map[string]interface{}{ + roomIDs[i] = alice.MustCreateRoom(t, map[string]interface{}{ "name": name, }) // we cannot guarantee we will see the right state yet, so just keep track of the room names @@ -739,7 +741,7 @@ func TestShrinkRange(t *testing.T) { var roomIDs []string // most recent first for i := 0; i < 10; i++ { time.Sleep(time.Millisecond) // ensure creation timestamp changes - roomIDs = append([]string{alice.CreateRoom(t, map[string]interface{}{ + roomIDs = append([]string{alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "name": fmt.Sprintf("Room %d", i), })}, roomIDs...) @@ -775,7 +777,7 @@ func TestExpandRange(t *testing.T) { var roomIDs []string // most recent first for i := 0; i < 10; i++ { time.Sleep(time.Millisecond) // ensure creation timestamp changes - roomIDs = append([]string{alice.CreateRoom(t, map[string]interface{}{ + roomIDs = append([]string{alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "name": fmt.Sprintf("Room %d", i), })}, roomIDs...) @@ -809,7 +811,7 @@ func TestMultipleSameList(t *testing.T) { var roomIDs []string // most recent first for i := 0; i < 16; i++ { time.Sleep(time.Millisecond) // ensure creation timestamp changes - roomIDs = append([]string{alice.CreateRoom(t, map[string]interface{}{ + roomIDs = append([]string{alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "name": fmt.Sprintf("Room %d", i), })}, roomIDs...) @@ -865,14 +867,14 @@ func TestBumpEventTypesHandling(t *testing.T) { charlie := registerNamedUser(t, "charlie") t.Log("Alice creates two rooms") - room1 := alice.CreateRoom( + room1 := alice.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", "name": "room1", }, ) - room2 := alice.CreateRoom( + room2 := alice.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", @@ -885,14 +887,14 @@ func TestBumpEventTypesHandling(t *testing.T) { bob.JoinRoom(t, room2, nil) t.Log("Bob sends a message in room 2 then room 1.") - bob.SendEventSynced(t, room2, Event{ + bob.SendEventSynced(t, room2, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hi room 2", "msgtype": "m.text", }, }) - bob.SendEventSynced(t, room1, Event{ + bob.SendEventSynced(t, room1, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hello world", @@ -1017,18 +1019,18 @@ func TestBumpEventTypesInOverlappingLists(t *testing.T) { bob := registerNamedUser(t, "bob") t.Log("Alice creates four rooms") - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room1"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room2"}) - room3 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room3"}) - room4 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room4"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room1"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room2"}) + room3 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room3"}) + room4 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room4"}) t.Log("Alice writes a message in all four rooms.") // Note: all lists bump on messages, so this will ensure the recency order is sensible. helloWorld := map[string]interface{}{"body": "Hello world", "msgtype": "m.text"} - alice.SendEventUnsynced(t, room1, Event{Type: "m.room.message", Content: helloWorld}) - alice.SendEventUnsynced(t, room2, Event{Type: "m.room.message", Content: helloWorld}) - alice.SendEventUnsynced(t, room3, Event{Type: "m.room.message", Content: helloWorld}) - alice.SendEventSynced(t, room4, Event{Type: "m.room.message", Content: helloWorld}) + alice.Unsafe_SendEventUnsynced(t, room1, b.Event{Type: "m.room.message", Content: helloWorld}) + alice.Unsafe_SendEventUnsynced(t, room2, b.Event{Type: "m.room.message", Content: helloWorld}) + alice.Unsafe_SendEventUnsynced(t, room3, b.Event{Type: "m.room.message", Content: helloWorld}) + alice.SendEventSynced(t, room4, b.Event{Type: "m.room.message", Content: helloWorld}) t.Log("Alice requests a sync with three lists: one bumping on messages, a second bumping on messages and memberships, and a third bumping on all events.") const listMsg = "message" @@ -1093,7 +1095,11 @@ func TestBumpEventTypesInOverlappingLists(t *testing.T) { })) t.Log("Alice sets a room topic in room 3, and syncs until she sees the topic.") - topicEventID := alice.SetState(t, room3, "m.room.topic", "", map[string]interface{}{"topic": "spicy meatballs"}) + topicEventID := alice.Unsafe_SendEventUnsynced(t, room3, b.Event{ + Type: "m.room.topic", + StateKey: ptr(""), + Content: map[string]interface{}{"topic": "spicy meatballs"}, + }) res = alice.SlidingSyncUntilEventID(t, res.Pos, room3, topicEventID) t.Logf("Alice sees room3 bump in the %s list only", listAll) @@ -1104,7 +1110,7 @@ func TestBumpEventTypesInOverlappingLists(t *testing.T) { })) t.Logf("Alice sends a message in room 2, and syncs until she sees it.") - msgEventID := alice.SendEventUnsynced(t, room2, Event{Type: "m.room.message", Content: helloWorld}) + msgEventID := alice.Unsafe_SendEventUnsynced(t, room2, b.Event{Type: "m.room.message", Content: helloWorld}) res = alice.SlidingSyncUntilEventID(t, res.Pos, room2, msgEventID) t.Logf("Alice sees room2 bump in all lists") @@ -1121,25 +1127,33 @@ func TestBumpEventTypesDoesntLeakOnNewConnAfterJoin(t *testing.T) { bob := registerNamedUser(t, "bob") t.Log("Alice creates a room and sends a secret state event.") - room1 := alice.CreateRoom( + room1 := alice.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", "name": "room1", }, ) - alice.SetState(t, room1, "secret", "", map[string]interface{}{}) + alice.Unsafe_SendEventUnsynced(t, room1, b.Event{ + Type: "secret", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Bob creates a room and sends a secret state event.") time.Sleep(1 * time.Millisecond) - room2 := bob.CreateRoom( + room2 := bob.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", "name": "room1", }, ) - bob.SetState(t, room2, "secret", "", map[string]interface{}{}) + bob.Unsafe_SendEventUnsynced(t, room2, b.Event{ + Type: "secret", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Alice invites Bob, who accepts.") alice.InviteRoom(t, room1, bob.UserID) @@ -1184,25 +1198,33 @@ func TestBumpEventTypesDoesntLeakOnNewConnAfterInvite(t *testing.T) { bob := registerNamedUser(t, "bob") t.Log("Alice creates a room and sends a secret state event.") - room1 := alice.CreateRoom( + room1 := alice.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", "name": "room1", }, ) - alice.SetState(t, room1, "secret", "", map[string]interface{}{}) + alice.Unsafe_SendEventUnsynced(t, room1, b.Event{ + Type: "secret", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Bob creates a room and sends a secret state event.") time.Sleep(1 * time.Millisecond) - room2 := bob.CreateRoom( + room2 := bob.MustCreateRoom( t, map[string]interface{}{ "preset": "public_chat", "name": "room1", }, ) - bob.SetState(t, room2, "secret", "", map[string]interface{}{}) + bob.Unsafe_SendEventUnsynced(t, room2, b.Event{ + Type: "secret", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Alice invites Bob, who does not respond.") alice.InviteRoom(t, room1, bob.UserID) @@ -1245,9 +1267,9 @@ func TestRangeOutsideTotalRooms(t *testing.T) { alice := registerNewUser(t) t.Log("Alice makes three public rooms.") - room0 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "A"}) - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "B"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "C"}) + room0 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "A"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "B"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "C"}) t.Log("Alice initial syncs, requesting room ranges [0, 1] and [8, 9]") syncRes := alice.SlidingSync(t, sync3.Request{ @@ -1331,24 +1353,24 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { chris.SetAvatar(t, chrisAvatar) t.Log("Alice makes a public room, a DM with herself, a DM with Bob, a DM with Chris, and a group-DM with Bob and Chris.") - public := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + public := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) // TODO: you can create a DM with yourself e.g. as below. It probably ought to have // your own face as an avatar. - // dmAlice := alice.CreateRoom(t, map[string]interface{}{ + // dmAlice := alice.MustCreateRoom(t, map[string]interface{}{ // "preset": "trusted_private_chat", // "is_direct": true, // }) - dmBob := alice.CreateRoom(t, map[string]interface{}{ + dmBob := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", "is_direct": true, "invite": []string{bob.UserID}, }) - dmChris := alice.CreateRoom(t, map[string]interface{}{ + dmChris := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", "is_direct": true, "invite": []string{chris.UserID}, }) - dmBobChris := alice.CreateRoom(t, map[string]interface{}{ + dmBobChris := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", "is_direct": true, "invite": []string{bob.UserID, chris.UserID}, @@ -1380,7 +1402,7 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Run("Avatar not resent on message", func(t *testing.T) { t.Log("Bob sends a sentinel message.") - sentinel := bob.SendEventSynced(t, dmBob, Event{ + sentinel := bob.SendEventSynced(t, dmBob, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "Hello world", @@ -1497,8 +1519,12 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Run("Explicit avatar propagates in non-DM room", func(t *testing.T) { t.Log("Alice sets an avatar for the public room.") publicAvatar := uploadAvatar(alice, "public.png") - alice.SetState(t, public, "m.room.avatar", "", map[string]interface{}{ - "url": publicAvatar, + alice.Unsafe_SendEventUnsynced(t, public, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{ + "url": publicAvatar, + }, }) t.Log("Alice syncs until she sees that avatar.") res = alice.SlidingSyncUntil( @@ -1512,8 +1538,12 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Log("Alice changes the avatar for the public room.") publicAvatar2 := uploadAvatar(alice, "public2.png") - alice.SetState(t, public, "m.room.avatar", "", map[string]interface{}{ - "url": publicAvatar2, + alice.Unsafe_SendEventUnsynced(t, public, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{ + "url": publicAvatar2, + }, }) t.Log("Alice syncs until she sees that avatar.") res = alice.SlidingSyncUntil( @@ -1526,7 +1556,11 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { ) t.Log("Alice removes the avatar for the public room.") - alice.SetState(t, public, "m.room.avatar", "", map[string]interface{}{}) + alice.Unsafe_SendEventUnsynced(t, public, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Alice syncs until she sees that avatar vanish.") res = alice.SlidingSyncUntil( t, @@ -1559,8 +1593,12 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Log("Chris gives their DM a bespoke avatar.") dmAvatar := uploadAvatar(chris, "dm.png") - chris.SetState(t, dmChris, "m.room.avatar", "", map[string]interface{}{ - "url": dmAvatar, + chris.Unsafe_SendEventUnsynced(t, dmChris, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{ + "url": dmAvatar, + }, }) t.Log("Alice syncs until she sees that avatar.") @@ -1586,15 +1624,23 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Log("Chris updates the DM's avatar.") dmAvatar2 := uploadAvatar(chris, "dm2.png") - chris.SetState(t, dmChris, "m.room.avatar", "", map[string]interface{}{ - "url": dmAvatar2, + chris.Unsafe_SendEventUnsynced(t, dmChris, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{ + "url": dmAvatar2, + }, }) t.Log("Alice syncs until she sees that avatar.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(dmChris, m.MatchRoomAvatar(dmAvatar2))) t.Log("Chris removes the DM's avatar.") - chris.SetState(t, dmChris, "m.room.avatar", "", map[string]interface{}{}) + chris.Unsafe_SendEventUnsynced(t, dmChris, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{}, + }) t.Log("Alice syncs until the DM avatar returns to Chris's most recent avatar.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(dmChris, m.MatchRoomAvatar(chris.AvatarURL))) @@ -1603,7 +1649,7 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Run("Changing DM flag", func(t *testing.T) { t.Skip("TODO: unimplemented") t.Log("Alice clears the DM flag on Bob's room.") - alice.SetGlobalAccountData(t, "m.direct", map[string]interface{}{ + alice.MustSetGlobalAccountData(t, "m.direct", map[string]interface{}{ "content": map[string][]string{ bob.UserID: {}, // no dmBob here chris.UserID: {dmChris, dmBobChris}, @@ -1636,7 +1682,7 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { })) t.Log("Alice sets the DM flag on Bob's room.") - alice.SetGlobalAccountData(t, "m.direct", map[string]interface{}{ + alice.MustSetGlobalAccountData(t, "m.direct", map[string]interface{}{ "content": map[string][]string{ bob.UserID: {dmBob}, // dmBob reinstated chris.UserID: {dmChris, dmBobChris}, @@ -1672,7 +1718,7 @@ func TestAvatarFieldInRoomResponse(t *testing.T) { t.Run("See avatar when invited", func(t *testing.T) { t.Log("Chris invites Alice to a DM.") - dmInvited := chris.CreateRoom(t, map[string]interface{}{ + dmInvited := chris.MustCreateRoom(t, map[string]interface{}{ "preset": "trusted_private_chat", "is_direct": true, "invite": []string{alice.UserID}, diff --git a/tests-e2e/main_test.go b/tests-e2e/main_test.go index 62466d0..926a94a 100644 --- a/tests-e2e/main_test.go +++ b/tests-e2e/main_test.go @@ -3,7 +3,6 @@ package syncv3_test import ( "encoding/json" "fmt" - "net/url" "os" "reflect" "sort" @@ -12,6 +11,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" "github.com/tidwall/gjson" @@ -33,13 +33,6 @@ func TestMain(m *testing.M) { os.Exit(exitCode) } -func WithPos(pos string) RequestOpt { - return WithQueries(url.Values{ - "pos": []string{pos}, - "timeout": []string{"500"}, // 0.5s - }) -} - func assertEventsEqual(t *testing.T, wantList []Event, gotList []json.RawMessage) { t.Helper() err := eventsEqual(wantList, gotList) @@ -212,11 +205,13 @@ func registerNewUser(t *testing.T) *CSAPI { func registerNamedUser(t *testing.T, localpartPrefix string) *CSAPI { // create user localpart := fmt.Sprintf("%s-%d-%d", localpartPrefix, time.Now().Unix(), atomic.AddUint64(&userCounter, 1)) - httpClient := NewLoggedClient(t, "localhost", nil) + httpClient := client.NewLoggedClient(t, "localhost", nil) client := &CSAPI{ - Client: httpClient, - BaseURL: homeserverBaseURL, - SyncUntilTimeout: 3 * time.Second, + CSAPI: &client.CSAPI{ + Client: httpClient, + BaseURL: homeserverBaseURL, + SyncUntilTimeout: 3 * time.Second, + }, } client.UserID, client.AccessToken, client.DeviceID = client.RegisterUser(t, localpart, "password") diff --git a/tests-e2e/membership_transitions_test.go b/tests-e2e/membership_transitions_test.go index 8074a6f..3a1a1bb 100644 --- a/tests-e2e/membership_transitions_test.go +++ b/tests-e2e/membership_transitions_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" "github.com/tidwall/gjson" @@ -15,19 +17,19 @@ func TestRoomStateTransitions(t *testing.T) { bob := registerNewUser(t) // make 4 rooms and set bob's membership state in each to a different value. - joinRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + joinRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, joinRoomID, nil) - kickRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + kickRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, kickRoomID, nil) - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", kickRoomID, "kick"}, WithJSONBody(t, map[string]interface{}{ + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", kickRoomID, "kick"}, client.WithJSONBody(t, map[string]interface{}{ "user_id": bob.UserID, })) - banRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + banRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, banRoomID, nil) - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", banRoomID, "ban"}, WithJSONBody(t, map[string]interface{}{ + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", banRoomID, "ban"}, client.WithJSONBody(t, map[string]interface{}{ "user_id": bob.UserID, })) - inviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + inviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) alice.InviteRoom(t, inviteRoomID, bob.UserID) // seed the proxy with Alice data @@ -134,9 +136,9 @@ func TestInviteRejection(t *testing.T) { // ensure that invite state correctly propagates. One room will already be in 'invite' state // prior to the first proxy sync, whereas the 2nd will transition. - firstInviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "First"}) + firstInviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "First"}) alice.InviteRoom(t, firstInviteRoomID, bob.UserID) - secondInviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "Second"}) + secondInviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "Second"}) t.Logf("TestInviteRejection first %s second %s", firstInviteRoomID, secondInviteRoomID) // sync as bob, we should see 1 invite @@ -169,10 +171,10 @@ func TestInviteRejection(t *testing.T) { }, })) - _, since := bob.MustSync(t, SyncReq{}) + _, since := bob.MustSync(t, client.SyncReq{}) // now invite bob alice.InviteRoom(t, secondInviteRoomID, bob.UserID) - since = bob.MustSyncUntil(t, SyncReq{Since: since}, SyncInvitedTo(bob.UserID, secondInviteRoomID)) + since = bob.MustSyncUntil(t, client.SyncReq{Since: since}, client.SyncInvitedTo(bob.UserID, secondInviteRoomID)) res = bob.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ @@ -203,7 +205,7 @@ func TestInviteRejection(t *testing.T) { bob.LeaveRoom(t, firstInviteRoomID) bob.LeaveRoom(t, secondInviteRoomID) - bob.MustSyncUntil(t, SyncReq{Since: since}, SyncLeftFrom(bob.UserID, secondInviteRoomID)) + bob.MustSyncUntil(t, client.SyncReq{Since: since}, client.SyncLeftFrom(bob.UserID, secondInviteRoomID)) // TODO: proxy needs to have processed this event enough for it to be waiting for us time.Sleep(100 * time.Millisecond) @@ -241,9 +243,9 @@ func TestInviteAcceptance(t *testing.T) { // ensure that invite state correctly propagates. One room will already be in 'invite' state // prior to the first proxy sync, whereas the 2nd will transition. t.Logf("Alice creates two rooms and invites Bob to the first.") - firstInviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "First"}) + firstInviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "First"}) alice.InviteRoom(t, firstInviteRoomID, bob.UserID) - secondInviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "Second"}) + secondInviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": "Second"}) t.Logf("first %s second %s", firstInviteRoomID, secondInviteRoomID) t.Log("Sync as Bob, requesting invites only. He should see 1 invite") @@ -353,7 +355,7 @@ func TestInviteRejectionTwice(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) roomName := "It's-a-me-invitio" - inviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) + inviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) t.Logf("TestInviteRejectionTwice room %s", inviteRoomID) // sync as bob, we see no invites yet. @@ -406,7 +408,7 @@ func TestLeavingRoomReturnsOneEvent(t *testing.T) { for _, aliceSyncing := range []bool{false, true} { t.Run(fmt.Sprintf("leaving a room returns one leave event (multiple poller=%v)", aliceSyncing), func(t *testing.T) { - inviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) + inviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) t.Logf("TestLeavingRoomReturnsOneEvent room %s", inviteRoomID) if aliceSyncing { @@ -476,7 +478,7 @@ func TestRejectingInviteReturnsOneEvent(t *testing.T) { for _, aliceSyncing := range []bool{false, true} { t.Run(fmt.Sprintf("rejecting an invite returns one leave event (multiple poller=%v)", aliceSyncing), func(t *testing.T) { - inviteRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) + inviteRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "private_chat", "name": roomName}) t.Logf("TestRejectingInviteReturnsOneEvent room %s", inviteRoomID) if aliceSyncing { @@ -542,7 +544,7 @@ func TestHeroesOnMembershipChanges(t *testing.T) { t.Run("nameless room uses heroes to calculate roomname", func(t *testing.T) { // create a room without a name, to ensure we calculate the room name based on // room heroes - roomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, roomID, []string{}) @@ -571,7 +573,7 @@ func TestHeroesOnMembershipChanges(t *testing.T) { } // Send a message, the heroes shouldn't change - msgEv := bob.SendEventSynced(t, roomID, Event{ + msgEv := bob.SendEventSynced(t, roomID, b.Event{ Type: "m.room.roomID", Content: map[string]interface{}{"body": "Hello world", "msgtype": "m.text"}, }) @@ -593,7 +595,7 @@ func TestHeroesOnMembershipChanges(t *testing.T) { }) t.Run("named rooms don't have heroes", func(t *testing.T) { - namedRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "my room without heroes"}) + namedRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "my room without heroes"}) // this makes sure that even if bob is joined, we don't return any heroes bob.JoinRoom(t, namedRoomID, []string{}) @@ -604,14 +606,18 @@ func TestHeroesOnMembershipChanges(t *testing.T) { }) t.Run("rooms with aliases don't have heroes", func(t *testing.T) { - aliasRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + aliasRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) alias := fmt.Sprintf("#%s-%d:%s", t.Name(), time.Now().Unix(), alice.Domain) - alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "directory", "room", alias}, - WithJSONBody(t, map[string]any{"room_id": aliasRoomID}), + alice.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "directory", "room", alias}, + client.WithJSONBody(t, map[string]any{"room_id": aliasRoomID}), ) - alice.SetState(t, aliasRoomID, "m.room.canonical_alias", "", map[string]any{ - "alias": alias, + alice.Unsafe_SendEventUnsynced(t, aliasRoomID, b.Event{ + Type: "m.room.canonical_alias", + StateKey: ptr(""), + Content: map[string]any{ + "alias": alias, + }, }) bob.JoinRoom(t, aliasRoomID, []string{}) @@ -623,7 +629,7 @@ func TestHeroesOnMembershipChanges(t *testing.T) { }) t.Run("can set heroes=true on room subscriptions", func(t *testing.T) { - subRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + subRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, subRoomID, []string{}) res := alice.SlidingSyncUntilMembership(t, "", subRoomID, bob, "join") @@ -636,7 +642,7 @@ func TestHeroesOnMembershipChanges(t *testing.T) { }) t.Run("can set heroes=true in lists", func(t *testing.T) { - listRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + listRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) bob.JoinRoom(t, listRoomID, []string{}) res := alice.SlidingSyncUntil(t, "", sync3.Request{ @@ -683,9 +689,9 @@ func TestMemberCounts(t *testing.T) { bob := registerNewUser(t) charlie := registerNewUser(t) - firstRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "First"}) + firstRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "First"}) alice.InviteRoom(t, firstRoomID, bob.UserID) - secondRoomID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "Second"}) + secondRoomID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "Second"}) alice.InviteRoom(t, secondRoomID, bob.UserID) charlie.JoinRoom(t, secondRoomID, nil) t.Logf("first %s second %s", firstRoomID, secondRoomID) @@ -758,7 +764,7 @@ func TestMemberCounts(t *testing.T) { })) // sending a message shouldn't update the count as it wastes bandwidth - charlie.SendEventSynced(t, secondRoomID, Event{ + charlie.SendEventSynced(t, secondRoomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "ping", "msgtype": "m.text"}, }) @@ -779,7 +785,7 @@ func TestMemberCounts(t *testing.T) { // leaving a room should update the count charlie.LeaveRoom(t, secondRoomID) - bob.MustSyncUntil(t, SyncReq{}, SyncLeftFrom(charlie.UserID, secondRoomID)) + bob.MustSyncUntil(t, client.SyncReq{}, client.SyncLeftFrom(charlie.UserID, secondRoomID)) res = bob.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ diff --git a/tests-e2e/num_live_test.go b/tests-e2e/num_live_test.go index acc9726..47ba7ca 100644 --- a/tests-e2e/num_live_test.go +++ b/tests-e2e/num_live_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" "github.com/tidwall/gjson" @@ -13,11 +14,11 @@ import ( func TestNumLive(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -43,7 +44,7 @@ func TestNumLive(t *testing.T) { )) // live event -> 1 num live - eventID2 := alice.SendEventSynced(t, roomID, Event{ + eventID2 := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -72,11 +73,11 @@ func TestNumLive(t *testing.T) { )) // now the big one -> 3 rooms, ask for top 2, bump 3rd room to top twice -> num_live=2 - roomID2 := alice.CreateRoom(t, map[string]interface{}{ + roomID2 := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID2, nil) - roomID3 := alice.CreateRoom(t, map[string]interface{}{ + roomID3 := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID3, nil) @@ -98,14 +99,14 @@ func TestNumLive(t *testing.T) { m.MatchV3SyncOp(0, 1, []string{roomID3, roomID2}), ))) // now send 2 live events into roomID to bump it to the top - eventID3 := alice.SendEventSynced(t, roomID, Event{ + eventID3 := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", "body": "bump 1", }, }) - eventID4 := alice.SendEventSynced(t, roomID, Event{ + eventID4 := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -138,12 +139,12 @@ func TestNumLive(t *testing.T) { func TestReqParamStarvation(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) numOtherRooms := 10 for i := 0; i < numOtherRooms; i++ { - bob.CreateRoom(t, map[string]interface{}{ + bob.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) } @@ -172,7 +173,7 @@ func TestReqParamStarvation(t *testing.T) { // We do this for the first few /syncs and don't dictate which response they should arrive // in, as we do not know and cannot force the proxy to deliver the event in a particular response. if i < 3 { - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", diff --git a/tests-e2e/prev_batch_test.go b/tests-e2e/prev_batch_test.go index a7852b0..14c123b 100644 --- a/tests-e2e/prev_batch_test.go +++ b/tests-e2e/prev_batch_test.go @@ -3,17 +3,21 @@ package syncv3_test import ( "encoding/json" "fmt" + "io" "net/url" "testing" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" + "github.com/matrix-org/complement/must" "github.com/matrix-org/sliding-sync/sync3" ) func TestPrevBatch(t *testing.T) { - client := registerNewUser(t) + cli := registerNewUser(t) // create room - roomID := client.CreateRoom(t, map[string]interface{}{}) + roomID := cli.MustCreateRoom(t, map[string]interface{}{}) var timeline []Event // send messages for i := 0; i < 30; i++ { @@ -24,12 +28,15 @@ func TestPrevBatch(t *testing.T) { "msgtype": "m.text", }, } - ev.ID = client.SendEventSynced(t, roomID, ev) + ev.ID = cli.SendEventSynced(t, roomID, b.Event{ + Type: ev.Type, + Content: ev.Content, + }) timeline = append(timeline, ev) } // hit proxy - res := client.SlidingSync(t, sync3.Request{ + res := cli.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ "a": { Ranges: sync3.SliceRanges{[2]int64{0, 10}}, @@ -49,13 +56,22 @@ func TestPrevBatch(t *testing.T) { assertEventsEqual(t, []Event{timeline[len(timeline)-1]}, room.Timeline) // hit /messages with prev_batch token - msgRes := client.MustDoFunc(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, WithQueries(url.Values{ + msgRes := cli.MustDo(t, "GET", []string{"_matrix", "client", "v3", "rooms", roomID, "messages"}, client.WithQueries(url.Values{ "dir": []string{"b"}, "from": []string{room.PrevBatch}, "limit": []string{"10"}, })) + body, err := io.ReadAll(msgRes.Body) + msgRes.Body.Close() + must.NotError(t, "failed to read response body", err) + + type MessagesBatch struct { + Chunk []json.RawMessage `json:"chunk"` + Start string `json:"start"` + End string `json:"end"` + } var msgBody MessagesBatch - if err := json.Unmarshal(ParseJSON(t, msgRes), &msgBody); err != nil { + if err := json.Unmarshal(body, &msgBody); err != nil { t.Fatalf("failed to unmarshal /messages response: %v", err) } // reverse it diff --git a/tests-e2e/receipts_test.go b/tests-e2e/receipts_test.go index e5a2acf..330402f 100644 --- a/tests-e2e/receipts_test.go +++ b/tests-e2e/receipts_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/sync3/extensions" "github.com/matrix-org/sliding-sync/testutils/m" @@ -14,11 +15,11 @@ import ( func TestReceipts(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -73,13 +74,13 @@ func TestReceiptsLazy(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) charlie := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) charlie.JoinRoom(t, roomID, nil) alice.SlidingSync(t, sync3.Request{}) // proxy begins tracking - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -94,7 +95,7 @@ func TestReceiptsLazy(t *testing.T) { // alice sends 5 new events, bob and alice ACK the last event var fifthEventID string for i := 0; i < 5; i++ { - fifthEventID = alice.SendEventSynced(t, roomID, Event{ + fifthEventID = alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -108,7 +109,7 @@ func TestReceiptsLazy(t *testing.T) { // alice sends another 5 events and ACKs nothing var lastEventID string for i := 0; i < 5; i++ { - lastEventID = alice.SendEventSynced(t, roomID, Event{ + lastEventID = alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -171,12 +172,12 @@ func TestReceiptsLazy(t *testing.T) { func TestReceiptsPrivate(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -231,10 +232,10 @@ func TestReceiptsRespectsExtensionScope(t *testing.T) { var syncResp *sync3.Response t.Log("Alice creates four rooms.") - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) - room3 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 3"}) - room4 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 4"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) + room3 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 3"}) + room4 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 4"}) t.Logf("room1=%s room2=%s room3=%s room4=%s", room1, room2, room3, room4) t.Log("Bob joins those rooms.") @@ -244,7 +245,7 @@ func TestReceiptsRespectsExtensionScope(t *testing.T) { bob.JoinRoom(t, room4, nil) t.Log("Alice posts a message to each room") - messageEvent := Event{ + messageEvent := b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -323,8 +324,8 @@ func TestReceiptsOnRoomsOnly(t *testing.T) { bob := registerNamedUser(t, "bob") t.Log("Alice creates two rooms.") - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) t.Logf("room1=%s room2=%s", room1, room2) t.Log("Bob joins those rooms.") @@ -332,7 +333,7 @@ func TestReceiptsOnRoomsOnly(t *testing.T) { bob.JoinRoom(t, room2, nil) t.Log("Alice posts a message to each room") - messageEvent := Event{ + messageEvent := b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", diff --git a/tests-e2e/redaction_test.go b/tests-e2e/redaction_test.go index 96a7718..5c2f24a 100644 --- a/tests-e2e/redaction_test.go +++ b/tests-e2e/redaction_test.go @@ -5,6 +5,8 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" "github.com/tidwall/gjson" @@ -12,9 +14,9 @@ import ( func TestRedactionsAreRedactedWherePossible(t *testing.T) { alice := registerNamedUser(t, "alice") - room := alice.CreateRoom(t, map[string]any{"preset": "public_chat"}) + room := alice.MustCreateRoom(t, map[string]any{"preset": "public_chat"}) - eventID := alice.SendEventSynced(t, room, Event{ + eventID := alice.SendEventSynced(t, room, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -40,7 +42,7 @@ func TestRedactionsAreRedactedWherePossible(t *testing.T) { })) // redact the event - redactionEventID := alice.RedactEvent(t, room, eventID) + redactionEventID := alice.MustSendRedaction(t, room, map[string]interface{}{}, eventID) // see the redaction alice.SlidingSyncUntilEventID(t, res.Pos, room, redactionEventID) @@ -83,21 +85,29 @@ func TestRedactingRoomStateIsReflectedInNextSync(t *testing.T) { bob := registerNamedUser(t, "bob") t.Log("Alice creates a room, then sets a room alias and name.") - room := alice.CreateRoom(t, map[string]any{ + room := alice.MustCreateRoom(t, map[string]any{ "preset": "public_chat", }) alias := fmt.Sprintf("#%s-%d:%s", t.Name(), time.Now().Unix(), alice.Domain) - alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "directory", "room", alias}, - WithJSONBody(t, map[string]any{"room_id": room}), + alice.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "directory", "room", alias}, + client.WithJSONBody(t, map[string]any{"room_id": room}), ) - aliasID := alice.SetState(t, room, "m.room.canonical_alias", "", map[string]any{ - "alias": alias, + aliasID := alice.Unsafe_SendEventUnsynced(t, room, b.Event{ + Type: "m.room.canonical_alias", + StateKey: ptr(""), + Content: map[string]interface{}{ + "alias": alias, + }, }) const naughty = "naughty room for naughty people" - nameID := alice.SetState(t, room, "m.room.name", "", map[string]any{ - "name": naughty, + nameID := alice.Unsafe_SendEventUnsynced(t, room, b.Event{ + Type: "m.room.name", + StateKey: ptr(""), + Content: map[string]interface{}{ + "name": naughty, + }, }) t.Log("Alice sliding syncs, subscribing to that room explicitly.") @@ -113,7 +123,7 @@ func TestRedactingRoomStateIsReflectedInNextSync(t *testing.T) { m.MatchResponse(t, res, m.MatchRoomSubscription(room, m.MatchRoomName(naughty))) t.Log("Alice redacts the room name.") - redactionID := alice.RedactEvent(t, room, nameID) + redactionID := alice.MustSendRedaction(t, room, map[string]interface{}{}, nameID) t.Log("Alice syncs until she sees her redaction.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription( @@ -126,15 +136,19 @@ func TestRedactingRoomStateIsReflectedInNextSync(t *testing.T) { t.Log("Alice sets a room avatar.") avatarURL := alice.UploadContent(t, smallPNG, "avatar.png", "image/png") - avatarID := alice.SetState(t, room, "m.room.avatar", "", map[string]interface{}{ - "url": avatarURL, + avatarID := alice.Unsafe_SendEventUnsynced(t, room, b.Event{ + Type: "m.room.avatar", + StateKey: ptr(""), + Content: map[string]interface{}{ + "url": avatarURL, + }, }) t.Log("Alice waits to see the avatar.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(room, m.MatchRoomAvatar(avatarURL))) t.Log("Alice redacts the avatar.") - redactionID = alice.RedactEvent(t, room, avatarID) + redactionID = alice.MustSendRedaction(t, room, map[string]interface{}{}, avatarID) t.Log("Alice sees the avatar revert to blank.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(room, m.MatchRoomUnsetAvatar())) @@ -160,13 +174,13 @@ func TestRedactingRoomStateIsReflectedInNextSync(t *testing.T) { bobJoinID := gjson.GetBytes(timeline[len(timeline)-1], "event_id").Str t.Log("Alice redacts the alias.") - redactionID = alice.RedactEvent(t, room, aliasID) + redactionID = alice.MustSendRedaction(t, room, map[string]interface{}{}, aliasID) t.Log("Alice sees the room name reset to Bob's display name.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(room, m.MatchRoomName(bobDisplayName))) t.Log("Bob redacts his membership") - redactionID = bob.RedactEvent(t, room, bobJoinID) + redactionID = bob.MustSendRedaction(t, room, map[string]interface{}{}, bobJoinID) t.Log("Alice sees the room name reset to Bob's username.") res = alice.SlidingSyncUntil(t, res.Pos, sync3.Request{}, m.MatchRoomSubscription(room, m.MatchRoomName(bob.UserID))) diff --git a/tests-e2e/security_test.go b/tests-e2e/security_test.go index 2077d7d..d762c78 100644 --- a/tests-e2e/security_test.go +++ b/tests-e2e/security_test.go @@ -5,6 +5,8 @@ import ( "fmt" "testing" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -26,7 +28,7 @@ func TestSecurityLiveStreamEventLeftLeak(t *testing.T) { eve := registerNewUser(t) // Alice and Eve in the room - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) eve.JoinRoom(t, roomID, nil) @@ -62,12 +64,12 @@ func TestSecurityLiveStreamEventLeftLeak(t *testing.T) { ))) // kick Eve - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "kick"}, WithJSONBody(t, map[string]interface{}{ + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "kick"}, client.WithJSONBody(t, map[string]interface{}{ "user_id": eve.UserID, })) // send message as Alice, note it shouldn't go down Eve's v2 stream - sensitiveEventID := alice.SendEventSynced(t, roomID, Event{ + sensitiveEventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.name", StateKey: ptr(""), Content: map[string]interface{}{ @@ -157,12 +159,12 @@ func TestSecurityRoomSubscriptionLeak(t *testing.T) { eve := registerNewUser(t) // Alice in the room - alicePrivateRoomID := alice.CreateRoom(t, map[string]interface{}{ + alicePrivateRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", }) // Eve is in an unrelated room - eveUnrelatedRoomID := eve.CreateRoom(t, map[string]interface{}{ + eveUnrelatedRoomID := eve.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", }) @@ -194,7 +196,7 @@ func TestSecurityRoomSubscriptionLeak(t *testing.T) { })) // Assert that live updates still don't feed through to Eve - alice.SendEventSynced(t, alicePrivateRoomID, Event{ + alice.SendEventSynced(t, alicePrivateRoomID, b.Event{ Type: "m.room.name", StateKey: ptr(""), Content: map[string]interface{}{ @@ -226,16 +228,16 @@ func TestSecuritySpaceDataLeak(t *testing.T) { alice := registerNewUser(t) eve := registerNewUser(t) - roomA := alice.CreateRoom(t, map[string]interface{}{ + roomA := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "creation_content": map[string]string{ "type": "m.space", }, }) - roomB := alice.CreateRoom(t, map[string]interface{}{ + roomB := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "private_chat", }) - alice.SendEventSynced(t, roomA, Event{ + alice.SendEventSynced(t, roomA, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{ @@ -273,17 +275,17 @@ func TestSecuritySpaceMetadataLeak(t *testing.T) { alice := registerNewUser(t) eve := registerNewUser(t) - roomA := alice.CreateRoom(t, map[string]interface{}{ + roomA := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "creation_content": map[string]string{ "type": "m.space", }, }) - roomB := alice.CreateRoom(t, map[string]interface{}{ + roomB := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) // Alice has a space A -> B - alice.SendEventSynced(t, roomA, Event{ + alice.SendEventSynced(t, roomA, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{ @@ -294,14 +296,14 @@ func TestSecuritySpaceMetadataLeak(t *testing.T) { alice.SlidingSync(t, sync3.Request{}) // now Eve also has a space... C -> B - roomC := eve.CreateRoom(t, map[string]interface{}{ + roomC := eve.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "creation_content": map[string]string{ "type": "m.space", }, }) eve.JoinRoom(t, roomB, nil) - eve.SendEventSynced(t, roomC, Event{ + eve.SendEventSynced(t, roomC, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{ diff --git a/tests-e2e/spaces_test.go b/tests-e2e/spaces_test.go index da963be..f34301e 100644 --- a/tests-e2e/spaces_test.go +++ b/tests-e2e/spaces_test.go @@ -4,6 +4,8 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -21,46 +23,46 @@ import ( // spaces[A,B] => B,C,E func TestSpacesFilter(t *testing.T) { alice := registerNewUser(t) - parentA := alice.CreateRoom(t, map[string]interface{}{ + parentA := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "creation_content": map[string]string{ "type": "m.space", }, }) - parentD := alice.CreateRoom(t, map[string]interface{}{ + parentD := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "creation_content": map[string]string{ "type": "m.space", }, }) - roomB := alice.CreateRoom(t, map[string]interface{}{ + roomB := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) - roomC := alice.CreateRoom(t, map[string]interface{}{ + roomC := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) - roomE := alice.CreateRoom(t, map[string]interface{}{ + roomE := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) - roomF := alice.CreateRoom(t, map[string]interface{}{ + roomF := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) t.Logf("A: %s B: %s C: %s D: %s E: %s F: %s", parentA, roomB, roomC, parentD, roomE, roomF) - alice.SendEventSynced(t, parentA, Event{ + alice.SendEventSynced(t, parentA, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{ "via": []string{"example.com"}, }, }) - alice.SendEventSynced(t, parentA, Event{ + alice.SendEventSynced(t, parentA, b.Event{ Type: "m.space.child", StateKey: &roomC, Content: map[string]interface{}{ "via": []string{"example.com"}, }, }) - alice.SendEventSynced(t, parentD, Event{ + alice.SendEventSynced(t, parentD, b.Event{ Type: "m.space.child", StateKey: &roomE, Content: map[string]interface{}{ @@ -71,7 +73,7 @@ func TestSpacesFilter(t *testing.T) { doSpacesListRequest := func(spaces []string, pos *string, listMatchers ...m.ListMatcher) *sync3.Response { t.Helper() - var opts []RequestOpt + var opts []client.RequestOpt if pos != nil { opts = append(opts, WithPos(*pos)) } @@ -116,7 +118,7 @@ func TestSpacesFilter(t *testing.T) { } // now move F into D and re-query D - alice.SendEventSynced(t, parentD, Event{ + alice.SendEventSynced(t, parentD, b.Event{ Type: "m.space.child", StateKey: &roomF, Content: map[string]interface{}{ @@ -127,7 +129,7 @@ func TestSpacesFilter(t *testing.T) { doInitialSpacesListRequest([]string{parentD}, []string{roomF, roomE}) // now remove B and re-query A - alice.SendEventSynced(t, parentA, Event{ + alice.SendEventSynced(t, parentA, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{}, @@ -136,7 +138,7 @@ func TestSpacesFilter(t *testing.T) { res := doInitialSpacesListRequest([]string{parentA}, []string{roomC}) // now live stream an update to ensure it gets added - alice.SendEventSynced(t, parentA, Event{ + alice.SendEventSynced(t, parentA, b.Event{ Type: "m.space.child", StateKey: &roomB, Content: map[string]interface{}{ @@ -165,14 +167,14 @@ func TestSpacesFilter(t *testing.T) { func TestSpacesFilterInvite(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - spaceRoomID := alice.CreateRoom(t, map[string]interface{}{ + spaceRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "name": "Space Room", "creation_content": map[string]string{ "type": "m.space", }, }) - normalRoomID := alice.CreateRoom(t, map[string]interface{}{ + normalRoomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", "name": "Normal Room", }) @@ -205,7 +207,7 @@ func TestAddingUnknownChildToSpace(t *testing.T) { bob := registerNewUser(t) t.Log("Alice creates a space and invites Bob.") - parentID := alice.CreateRoom(t, map[string]interface{}{ + parentID := alice.MustCreateRoom(t, map[string]interface{}{ "type": "m.space", "invite": []string{bob.UserID}, }) @@ -226,9 +228,13 @@ func TestAddingUnknownChildToSpace(t *testing.T) { }) t.Log("Alice creates a room and marks it as a child of the space.") - childID := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat"}) - childEventID := alice.SetState(t, parentID, "m.space.child", childID, map[string]interface{}{ - "via": []string{"localhost"}, + childID := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat"}) + childEventID := alice.Unsafe_SendEventUnsynced(t, parentID, b.Event{ + Type: "m.space.child", + StateKey: ptr(childID), + Content: map[string]interface{}{ + "via": []string{"localhost"}, + }, }) t.Log("Bob syncs until he sees the m.space.child event in the space.") diff --git a/tests-e2e/timestamp_test.go b/tests-e2e/timestamp_test.go index 4284a7f..96f2302 100644 --- a/tests-e2e/timestamp_test.go +++ b/tests-e2e/timestamp_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -13,7 +14,7 @@ func TestTimestamp(t *testing.T) { bob := registerNewUser(t) charlie := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) @@ -67,7 +68,7 @@ func TestTimestamp(t *testing.T) { // Send an event which should NOT bump Bobs timestamp, because it is not listed it // any BumpEventTypes emptyStateKey := "" - eventID := alice.SendEventSynced(t, roomID, Event{ + eventID := alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.topic", StateKey: &emptyStateKey, Content: map[string]interface{}{ @@ -85,7 +86,7 @@ func TestTimestamp(t *testing.T) { expectedTs = gotTs // Now send a message which bumps the timestamp in myFirstList - eventID = alice.SendEventSynced(t, roomID, Event{ + eventID = alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -102,7 +103,7 @@ func TestTimestamp(t *testing.T) { expectedTs = gotTs // Now send a message which bumps the timestamp in mySecondList - eventID = alice.SendEventSynced(t, roomID, Event{ + eventID = alice.SendEventSynced(t, roomID, b.Event{ Type: "m.reaction", Content: map[string]interface{}{ "m.relates.to": map[string]interface{}{ @@ -122,7 +123,7 @@ func TestTimestamp(t *testing.T) { expectedTs = bobTimestampReaction // Send another event which should NOT bump Bobs timestamp - eventID = alice.SendEventSynced(t, roomID, Event{ + eventID = alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.name", StateKey: &emptyStateKey, Content: map[string]interface{}{ diff --git a/tests-e2e/to_device_test.go b/tests-e2e/to_device_test.go index 103a9f2..2f7a18b 100644 --- a/tests-e2e/to_device_test.go +++ b/tests-e2e/to_device_test.go @@ -17,7 +17,11 @@ import ( func TestToDeviceDeliveryInitialLogin(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - bob.SendToDevice(t, "m.dummy", alice.UserID, alice.DeviceID, map[string]interface{}{}) + bob.SendToDeviceMessages(t, "m.dummy", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{}, + }, + }) // loop until we see the event loopUntilToDeviceEvent(t, alice, nil, "", "m.dummy", bob.UserID) } @@ -33,7 +37,11 @@ func TestToDeviceDeliveryStream(t *testing.T) { }, }, }) - bob.SendToDevice(t, "m.dummy", alice.UserID, alice.DeviceID, map[string]interface{}{}) + bob.SendToDeviceMessages(t, "m.dummy", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{}, + }, + }) // loop until we see the event loopUntilToDeviceEvent(t, alice, res, res.Extensions.ToDevice.NextBatch, "m.dummy", bob.UserID) @@ -52,7 +60,11 @@ func TestToDeviceDeliveryReconnect(t *testing.T) { }, }, }) - bob.SendToDevice(t, "m.dummy", alice.UserID, alice.DeviceID, map[string]interface{}{}) + bob.SendToDeviceMessages(t, "m.dummy", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{}, + }, + }) // loop until we see the event loopUntilToDeviceEvent(t, alice, nil, "", "m.dummy", bob.UserID) } @@ -65,18 +77,30 @@ func TestToDeviceDropStaleKeyRequestsInitial(t *testing.T) { // send a few dummy messages, cancelling each other for i := 0; i < sendMessages; i++ { reqID := util.RandomString(8) - bob.SendToDevice(t, "m.room_key_request", alice.UserID, alice.DeviceID, map[string]interface{}{ - "request_id": reqID, - "action": "request", - "requesting_device_id": "mydevice", + bob.SendToDeviceMessages(t, "m.room_key_request", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{ + "request_id": reqID, + "action": "request", + "requesting_device_id": "mydevice", + }, + }, }) - bob.SendToDevice(t, "m.room_key_request", alice.UserID, alice.DeviceID, map[string]interface{}{ - "request_id": reqID, - "action": "request_cancellation", - "requesting_device_id": "mydevice", + bob.SendToDeviceMessages(t, "m.room_key_request", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{ + "request_id": reqID, + "action": "request_cancellation", + "requesting_device_id": "mydevice", + }, + }, }) } - bob.SendToDevice(t, "sentinel", alice.UserID, alice.DeviceID, map[string]interface{}{}) + bob.SendToDeviceMessages(t, "sentinel", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{}, + }, + }) // Loop until we have the sentinel event, the rest should cancel out. gotMessages, _ := loopUntilToDeviceEvent(t, alice, nil, "", "sentinel", bob.UserID) wantCount := 1 @@ -89,10 +113,14 @@ func TestToDeviceDropStaleKeyRequestsStreamNoDelete(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - bob.SendToDevice(t, "m.room_key_request", alice.UserID, alice.DeviceID, map[string]interface{}{ - "request_id": "A", - "action": "request", - "requesting_device_id": "mydevice", + bob.SendToDeviceMessages(t, "m.room_key_request", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{ + "request_id": "A", + "action": "request", + "requesting_device_id": "mydevice", + }, + }, }) msgs1, res := loopUntilToDeviceEvent(t, alice, nil, "", "m.room_key_request", bob.UserID) if len(msgs1) != 1 { @@ -100,10 +128,14 @@ func TestToDeviceDropStaleKeyRequestsStreamNoDelete(t *testing.T) { } // now send a cancellation: we should not delete the cancellation - bob.SendToDevice(t, "m.room_key_request", alice.UserID, alice.DeviceID, map[string]interface{}{ - "request_id": "A", - "action": "request_cancellation", - "requesting_device_id": "mydevice", + bob.SendToDeviceMessages(t, "m.room_key_request", map[string]map[string]map[string]interface{}{ + alice.UserID: map[string]map[string]interface{}{ + alice.DeviceID: map[string]interface{}{ + "request_id": "A", + "action": "request_cancellation", + "requesting_device_id": "mydevice", + }, + }, }) time.Sleep(100 * time.Millisecond) msgs2, _ := loopUntilToDeviceEvent(t, alice, res, res.Extensions.ToDevice.NextBatch, "m.room_key_request", bob.UserID) diff --git a/tests-e2e/tombstone_test.go b/tests-e2e/tombstone_test.go index 1639013..8c9c418 100644 --- a/tests-e2e/tombstone_test.go +++ b/tests-e2e/tombstone_test.go @@ -6,6 +6,8 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" "github.com/tidwall/gjson" @@ -14,7 +16,7 @@ import ( // tests that if we upgrade a room it is removed from the list. If we request old rooms it should be included. func TestIncludeOldRooms(t *testing.T) { client := registerNewUser(t) - roomID := client.CreateRoom(t, map[string]interface{}{}) + roomID := client.MustCreateRoom(t, map[string]interface{}{}) res := client.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ @@ -158,23 +160,23 @@ func TestIncludeOldRoomsLongChain(t *testing.T) { // seed the server with this client, we need to do this so the proxy has timeline history to // return so we can assert events appear in the right rooms res := client.SlidingSync(t, sync3.Request{}) - roomA := client.CreateRoom(t, map[string]interface{}{}) - client.SendEventSynced(t, roomA, Event{ + roomA := client.MustCreateRoom(t, map[string]interface{}{}) + client.SendEventSynced(t, roomA, b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "A", "msgtype": "m.text"}, }) roomB := upgradeRoom(t, client, roomA) - eventB := client.SendEventSynced(t, roomB, Event{ + eventB := client.SendEventSynced(t, roomB, b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "B", "msgtype": "m.text"}, }) roomC := upgradeRoom(t, client, roomB) - client.SendEventSynced(t, roomC, Event{ + client.SendEventSynced(t, roomC, b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "C", "msgtype": "m.text"}, }) roomD := upgradeRoom(t, client, roomC) - eventD := client.SendEventSynced(t, roomD, Event{ + eventD := client.SendEventSynced(t, roomD, b.Event{ Type: "m.room.message", Content: map[string]interface{}{"body": "D", "msgtype": "m.text"}, }) @@ -266,7 +268,7 @@ func TestIncludeOldRoomsLongChain(t *testing.T) { // test that if you have a list version and direct sub version of include_old_rooms, they get unioned correctly. func TestIncludeOldRoomsSubscriptionUnion(t *testing.T) { client := registerNewUser(t) - roomA := client.CreateRoom(t, map[string]interface{}{}) + roomA := client.MustCreateRoom(t, map[string]interface{}{}) roomB := upgradeRoom(t, client, roomA) // should union to timeline_limit=2, req_state=create+member+tombstone @@ -322,8 +324,8 @@ func TestIncludeOldRoomsSubscriptionUnion(t *testing.T) { })) } -func upgradeRoom(t *testing.T, client *CSAPI, roomID string) (newRoomID string) { - upgradeRes := client.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "upgrade"}, WithJSONBody(t, map[string]interface{}{ +func upgradeRoom(t *testing.T, c *CSAPI, roomID string) (newRoomID string) { + upgradeRes := c.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "upgrade"}, client.WithJSONBody(t, map[string]interface{}{ "new_version": "9", })) var body map[string]interface{} diff --git a/tests-e2e/transaction_id_test.go b/tests-e2e/transaction_id_test.go index c3a53ee..ceb85ea 100644 --- a/tests-e2e/transaction_id_test.go +++ b/tests-e2e/transaction_id_test.go @@ -5,23 +5,24 @@ import ( "fmt" "testing" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) func TestTransactionIDsAppear(t *testing.T) { - client := registerNewUser(t) - roomID := client.CreateRoom(t, map[string]interface{}{}) - sendRes := client.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", "foobar"}, - WithJSONBody(t, map[string]interface{}{ + c := registerNewUser(t) + roomID := c.MustCreateRoom(t, map[string]interface{}{}) + sendRes := c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", "foobar"}, + client.WithJSONBody(t, map[string]interface{}{ "msgtype": "m.text", "body": "Hello World!", })) - body := ParseJSON(t, sendRes) - eventID := GetJSONFieldStr(t, body, "event_id") + body := client.ParseJSON(t, sendRes) + eventID := client.GetJSONFieldStr(t, body, "event_id") // ensure initial syncs include the txn id - res := client.SlidingSync(t, sync3.Request{ + res := c.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ "a": { Ranges: [][2]int64{{0, 10}}, @@ -40,15 +41,15 @@ func TestTransactionIDsAppear(t *testing.T) { })) // now live stream another event and ensure that too has a txn ID - sendRes = client.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", "foobar2"}, - WithJSONBody(t, map[string]interface{}{ + sendRes = c.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", "foobar2"}, + client.WithJSONBody(t, map[string]interface{}{ "msgtype": "m.text", "body": "Hello World 2!", })) - body = ParseJSON(t, sendRes) - eventID = GetJSONFieldStr(t, body, "event_id") + body = client.ParseJSON(t, sendRes) + eventID = client.GetJSONFieldStr(t, body, "event_id") - res = client.SlidingSyncUntilEvent(t, res.Pos, sync3.Request{}, roomID, Event{ID: eventID}) + res = c.SlidingSyncUntilEvent(t, res.Pos, sync3.Request{}, roomID, Event{ID: eventID}) m.MatchResponse(t, res, m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{ roomID: { matchTransactionID(t, eventID, "foobar2"), @@ -64,7 +65,7 @@ func TestTransactionIDsAppearWithMultiplePollers(t *testing.T) { alice := registerNamedUser(t, "alice") t.Log("Alice creates a room and syncs until she sees it.") - roomID := alice.CreateRoom(t, map[string]interface{}{}) + roomID := alice.MustCreateRoom(t, map[string]interface{}{}) res := alice.SlidingSync(t, sync3.Request{ Lists: map[string]sync3.RequestList{ "a": { @@ -95,13 +96,13 @@ func TestTransactionIDsAppearWithMultiplePollers(t *testing.T) { t.Log("Alice sends a message with a transaction ID.") const txnID = "foobar" - sendRes := alice.MustDoFunc(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", txnID}, - WithJSONBody(t, map[string]interface{}{ + sendRes := alice.MustDo(t, "PUT", []string{"_matrix", "client", "v3", "rooms", roomID, "send", "m.room.message", txnID}, + client.WithJSONBody(t, map[string]interface{}{ "msgtype": "m.text", "body": "Hello, world!", })) - body := ParseJSON(t, sendRes) - eventID := GetJSONFieldStr(t, body, "event_id") + body := client.ParseJSON(t, sendRes) + eventID := client.GetJSONFieldStr(t, body, "event_id") t.Log("Alice syncs on her main devices until she sees her message.") res = alice.SlidingSyncUntilEventID(t, res.Pos, roomID, eventID) diff --git a/tests-e2e/typing_test.go b/tests-e2e/typing_test.go index 6d65b6f..661886b 100644 --- a/tests-e2e/typing_test.go +++ b/tests-e2e/typing_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/matrix-org/complement/b" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/sync3/extensions" "github.com/matrix-org/sliding-sync/testutils/m" @@ -17,7 +18,7 @@ import ( func TestTyping(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) @@ -65,7 +66,7 @@ func TestTyping(t *testing.T) { m.MatchResponse(t, res, m.MatchTyping(roomID, []string{bob.UserID, alice.UserID})) // make sure if you type in a room not returned in the window it does not go through - roomID2 := alice.CreateRoom(t, map[string]interface{}{ + roomID2 := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID2, nil) @@ -108,7 +109,7 @@ func TestTyping(t *testing.T) { func TestTypingNoUpdate(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) @@ -136,19 +137,19 @@ func TestTypingNoUpdate(t *testing.T) { func TestTypingLazyLoad(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) - alice.SendEventSynced(t, roomID, Event{ + alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "hello world!", "msgtype": "m.text", }, }) - alice.SendEventSynced(t, roomID, Event{ + alice.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "body": "hello world!", @@ -202,8 +203,8 @@ func TestTypingRespectsExtensionScope(t *testing.T) { // separate to the incremental sync behaviour (hits `AppendLive`) t.Run("Can limit by room in an initial sync", func(t *testing.T) { t.Log("Alice creates rooms 1 and 2. Bob joins both.") - room1 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) - room2 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) + room1 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 1"}) + room2 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 2"}) bob.JoinRoom(t, room1, nil) bob.JoinRoom(t, room2, nil) t.Logf("room1=%s room2=%s", room1, room2) @@ -244,8 +245,8 @@ func TestTypingRespectsExtensionScope(t *testing.T) { t.Run("Can limit by list in an incremental sync", func(t *testing.T) { t.Log("Alice creates rooms 3 and 4. Bob joins both.") - room3 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 3"}) - room4 := alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 4"}) + room3 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 3"}) + room4 := alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": "room 4"}) bob.JoinRoom(t, room3, nil) bob.JoinRoom(t, room4, nil) t.Logf("room3=%s room4=%s", room3, room4) @@ -300,7 +301,7 @@ func TestTypingRespectsExtensionScopeWithOmittedFields(t *testing.T) { t.Log("Alice creates four rooms. Bob joins each one.") rooms := make([]string, 4) for i := 0; i < len(rooms); i++ { - rooms[i] = alice.CreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": fmt.Sprintf("room %d", i)}) + rooms[i] = alice.MustCreateRoom(t, map[string]interface{}{"preset": "public_chat", "name": fmt.Sprintf("room %d", i)}) bob.JoinRoom(t, rooms[i], nil) } t.Logf("rooms = %v", rooms) @@ -356,7 +357,7 @@ func TestTypingRespectsExtensionScopeWithOmittedFields(t *testing.T) { } t.Log("Bob sends a sentinel message.") // Use room 2 because Alice explicitly subscribes to it - bobMsg := bob.SendEventSynced(t, rooms[2], Event{ + bobMsg := bob.SendEventSynced(t, rooms[2], b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -388,7 +389,7 @@ func TestTypingRespectsExtensionScopeWithOmittedFields(t *testing.T) { bob.SendTyping(t, room, true, 5000) } t.Log("Bob sends a sentinel message.") - bobMsg = bob.SendEventSynced(t, rooms[2], Event{ + bobMsg = bob.SendEventSynced(t, rooms[2], b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -396,7 +397,6 @@ func TestTypingRespectsExtensionScopeWithOmittedFields(t *testing.T) { }, }) - t.Log("Alice now requests typing notifications in all windows, and explicitly in rooms 0 and 3.") t.Log("Alice incremental syncs until she sees Bob's latest sentinel. She should see no ops.") seenTyping := map[int]int{} diff --git a/tests-e2e/unread_count_test.go b/tests-e2e/unread_count_test.go index 7022518..d5c2cfa 100644 --- a/tests-e2e/unread_count_test.go +++ b/tests-e2e/unread_count_test.go @@ -4,6 +4,8 @@ import ( "fmt" "testing" + "github.com/matrix-org/complement/b" + "github.com/matrix-org/complement/client" "github.com/matrix-org/sliding-sync/sync3" "github.com/matrix-org/sliding-sync/testutils/m" ) @@ -11,11 +13,11 @@ import ( func TestUnreadCountsUpdate(t *testing.T) { alice := registerNewUser(t) bob := registerNewUser(t) - roomID := alice.CreateRoom(t, map[string]interface{}{ + roomID := alice.MustCreateRoom(t, map[string]interface{}{ "preset": "public_chat", }) bob.JoinRoom(t, roomID, nil) - eventID := bob.SendEventSynced(t, roomID, Event{ + eventID := bob.SendEventSynced(t, roomID, b.Event{ Type: "m.room.message", Content: map[string]interface{}{ "msgtype": "m.text", @@ -35,7 +37,7 @@ func TestUnreadCountsUpdate(t *testing.T) { m.MatchRoomNotificationCount(1), }, })) - alice.MustDoFunc(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"}, WithJSONBody(t, map[string]interface{}{ + alice.MustDo(t, "POST", []string{"_matrix", "client", "v3", "rooms", roomID, "read_markers"}, client.WithJSONBody(t, map[string]interface{}{ "m.fully_read": eventID, "m.read": eventID, }))