Redact events in the DB on m.room.redaction

Fixes #279
This commit is contained in:
Kegan Dougal 2023-08-31 17:06:44 +01:00
parent c806b264bf
commit b2c26b7e93
11 changed files with 330 additions and 26 deletions

2
go.mod
View File

@ -9,7 +9,7 @@ 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-20230105074811-965b10ae73ab
github.com/matrix-org/gomatrixserverlib v0.0.0-20230822143230-740f742d6993
github.com/matrix-org/util v0.0.0-20200807132607-55161520e1d4
github.com/pressly/goose/v3 v3.14.0
github.com/prometheus/client_golang v1.13.0

2
go.sum
View File

@ -187,6 +187,8 @@ github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16 h1:ZtO5uywdd5d
github.com/matrix-org/gomatrix v0.0.0-20210324163249-be2af5ef2e16/go.mod h1:/gBX06Kw0exX1HrwmoBibFA98yBk/jxKpGVeyQbff+s=
github.com/matrix-org/gomatrixserverlib v0.0.0-20230105074811-965b10ae73ab h1:ChaQdT2mpxMm3GRXNOZzLDQ/wOnlKZ8o60LmZGOjdj8=
github.com/matrix-org/gomatrixserverlib v0.0.0-20230105074811-965b10ae73ab/go.mod h1:Mtifyr8q8htcBeugvlDnkBcNUy5LO8OzUoplAf1+mb4=
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/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=

View File

@ -4,6 +4,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"github.com/matrix-org/sliding-sync/internal"
"github.com/getsentry/sentry-go"
@ -398,11 +399,13 @@ func (a *Accumulator) Accumulate(txn *sqlx.Tx, userID, roomID string, prevBatch
var latestNID int64
newEvents := make([]Event, 0, len(eventIDToNID))
var redactTheseEventIDs []string
for _, ev := range dedupedEvents {
nid, ok := eventIDToNID[ev.ID]
if ok {
ev.NID = int64(nid)
if gjson.GetBytes(ev.JSON, "state_key").Exists() {
parsedEv := gjson.ParseBytes(ev.JSON)
if parsedEv.Get("state_key").Exists() {
// XXX: reusing this to mean "it's a state event" as well as "it's part of the state v2 response"
// its important that we don't insert 'ev' at this point as this should be False in the DB.
ev.IsState = true
@ -412,11 +415,40 @@ func (a *Accumulator) Accumulate(txn *sqlx.Tx, userID, roomID string, prevBatch
if ev.NID > latestNID {
latestNID = ev.NID
}
// if this is a redaction, try to redact the referenced event (best effort)
if !ev.IsState && ev.Type == "m.room.redaction" {
// look for top-level redacts then content.redacts (room version 11+)
redactsEventID := parsedEv.Get("redacts").Str
if redactsEventID == "" {
redactsEventID = parsedEv.Get("content.redacts").Str
}
if redactsEventID != "" {
redactTheseEventIDs = append(redactTheseEventIDs, redactsEventID)
}
}
newEvents = append(newEvents, ev)
timelineNIDs = append(timelineNIDs, ev.NID)
}
}
// if we are going to redact things, we need the room version to know the redaction algorithm
// so pull it out once now.
var roomVersion string
if len(redactTheseEventIDs) > 0 {
createEventJSON, err := a.eventsTable.SelectCreateEvent(txn, roomID)
if err != nil {
return 0, nil, fmt.Errorf("SelectCreateEvent: %s", err)
}
roomVersion = gjson.GetBytes(createEventJSON, "content.room_version").Str
if roomVersion == "" {
// Defaults to "1" if the key does not exist.
roomVersion = "1"
}
if err = a.eventsTable.Redact(txn, roomVersion, redactTheseEventIDs); err != nil {
return 0, nil, err
}
}
for _, ev := range newEvents {
var replacesNID int64
// the snapshot ID we assign to this event is unaffected by whether /this/ event is state or not,

View File

@ -2,6 +2,7 @@ package state
import (
"database/sql"
"encoding/json"
"fmt"
"math"
@ -10,6 +11,7 @@ import (
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sqlutil"
)
@ -336,6 +338,30 @@ func (t *EventTable) LatestEventNIDInRooms(txn *sqlx.Tx, roomIDs []string, highe
return
}
func (t *EventTable) Redact(txn *sqlx.Tx, roomVer string, eventIDs []string) error {
// verifyAll=false so if we are asked to redact an event we don't have we don't fall over.
eventsToRedact, err := t.SelectByIDs(txn, false, eventIDs)
if err != nil {
return fmt.Errorf("EventTable.Redact[%v]: %s", eventIDs, err)
}
rv, err := gomatrixserverlib.GetRoomVersion(gomatrixserverlib.RoomVersion(roomVer))
if err != nil {
// unknown room version... let's just default to "1"
rv = gomatrixserverlib.MustGetRoomVersion(gomatrixserverlib.RoomVersionV1)
}
for i := range eventsToRedact {
eventsToRedact[i].JSON, err = rv.RedactEventJSON(eventsToRedact[i].JSON)
if err != nil {
return fmt.Errorf("RedactEventJSON[%s]: %s", eventsToRedact[i].ID, err)
}
_, err = txn.Exec(`UPDATE syncv3_events SET event=$1 WHERE event_id=$2`, eventsToRedact[i].JSON, eventsToRedact[i].ID)
if err != nil {
return fmt.Errorf("cannot update event %s: %v", eventsToRedact[i].ID, err)
}
}
return nil
}
func (t *EventTable) SelectLatestEventsBetween(txn *sqlx.Tx, roomID string, lowerExclusive, upperInclusive int64, limit int) ([]Event, error) {
var events []Event
// do not pull in events which were in the v2 state block
@ -440,6 +466,13 @@ func (t *EventTable) SelectClosestPrevBatch(txn *sqlx.Tx, roomID string, eventNI
return
}
func (t *EventTable) SelectCreateEvent(txn *sqlx.Tx, roomID string) (json.RawMessage, error) {
var evJSON []byte
// there is only 1 create event
err := txn.QueryRow(`SELECT event FROM syncv3_events WHERE room_id=$1 AND event_type='m.room.create' AND state_key=''`, roomID).Scan(&evJSON)
return evJSON, err
}
type EventChunker []Event
func (c EventChunker) Len() int {

View File

@ -3,6 +3,7 @@ package state
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"reflect"
"testing"
@ -924,3 +925,168 @@ func TestEventTableSelectUnknownEventIDs(t *testing.T) {
}
}
}
func TestEventTableRedact(t *testing.T) {
db, close := connectToDB(t)
defer close()
table := NewEventTable(db)
txn, err := db.Beginx()
if err != nil {
t.Fatalf("failed to start txn: %s", err)
}
defer txn.Rollback()
testCases := []struct {
original map[string]interface{}
roomVer string
wantContent map[string]interface{}
}{
// sanity check
{
original: map[string]interface{}{
"type": "m.room.message",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"msgtype": "m.text",
"body": "Something not very nice",
},
},
wantContent: map[string]interface{}{},
roomVer: "10",
},
// keep membership
{
original: map[string]interface{}{
"type": "m.room.member",
"state_key": "@nasty-mc-nastyface:localhost",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"membership": "join",
"displayname": "Something not very nice",
"avatar_url": "mxc://something/not/very/nice",
},
},
wantContent: map[string]interface{}{
"membership": "join",
},
roomVer: "10",
},
// keep join rule
{
original: map[string]interface{}{
"type": "m.room.join_rules",
"state_key": "",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"join_rule": "invite",
"extra_data": "not very nice",
},
},
wantContent: map[string]interface{}{
"join_rule": "invite",
},
roomVer: "10",
},
// keep his vis
{
original: map[string]interface{}{
"type": "m.room.history_visibility",
"state_key": "",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"history_visibility": "shared",
"extra_data": "not very nice",
},
},
wantContent: map[string]interface{}{
"history_visibility": "shared",
},
roomVer: "10",
},
// keep version specific fields
{
original: map[string]interface{}{
"type": "m.room.member",
"state_key": "@nasty-mc-nastyface:localhost",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"membership": "join",
"displayname": "Something not very nice",
"avatar_url": "mxc://something/not/very/nice",
"join_authorised_via_users_server": "@bob:localhost",
},
},
wantContent: map[string]interface{}{
"membership": "join",
"join_authorised_via_users_server": "@bob:localhost",
},
roomVer: "10",
},
// remove same field on lower room version
{
original: map[string]interface{}{
"type": "m.room.member",
"state_key": "@nasty-mc-nastyface:localhost",
"sender": "@nasty-mc-nastyface:localhost",
"content": map[string]interface{}{
"membership": "join",
"displayname": "Something not very nice",
"avatar_url": "mxc://something/not/very/nice",
"join_authorised_via_users_server": "@bob:localhost",
},
},
wantContent: map[string]interface{}{
"membership": "join",
},
roomVer: "2",
},
}
roomID := "!TestEventTableRedact"
for i, tc := range testCases {
eventID := fmt.Sprintf("$TestEventTableRedact_%d", i)
tc.original["event_id"] = eventID
tc.original["room_id"] = roomID
stateKey := ""
if tc.original["state_key"] != nil {
stateKey = tc.original["state_key"].(string)
}
j, err := json.Marshal(tc.original)
assertNoError(t, err)
table.Insert(txn, []Event{
{
ID: eventID,
Type: tc.original["type"].(string),
RoomID: roomID,
StateKey: stateKey,
BeforeStateSnapshotID: 22,
JSON: j,
},
}, false)
assertNoError(t, table.Redact(txn, tc.roomVer, []string{eventID}))
gots, err := table.SelectByIDs(txn, true, []string{eventID})
assertNoError(t, err)
if len(gots) != 1 {
t.Errorf("SelectByIDs: got %v results, want 1", len(gots))
continue
}
got := gots[0]
assertVal(t, "event id mismatch", got.ID, eventID)
assertVal(t, "room id mismatch", got.RoomID, roomID)
var gotJSON map[string]interface{}
assertNoError(t, json.Unmarshal(got.JSON, &gotJSON))
assertVal(t, "content mismatch", gotJSON["content"], tc.wantContent)
}
}
func TestEventTableRedactMissingOK(t *testing.T) {
db, close := connectToDB(t)
defer close()
table := NewEventTable(db)
txn, err := db.Beginx()
if err != nil {
t.Fatalf("failed to start txn: %s", err)
}
defer txn.Rollback()
assertNoError(t, table.Redact(txn, "2", []string{"$unknown", "$event", "$ids"}))
}

View File

@ -11,7 +11,7 @@ import (
"time"
"github.com/jmoiron/sqlx"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sqlutil"
"github.com/matrix-org/sliding-sync/testutils"
@ -308,7 +308,7 @@ func TestVisibleEventNIDsBetween(t *testing.T) {
t.Fatalf("LatestEventNID: %s", err)
}
baseTimestamp := gomatrixserverlib.Timestamp(1632131678061).Time()
baseTimestamp := spec.Timestamp(1632131678061).Time()
// Test the examples
// Stream Positions
// 1 2 3 4 5 6 7 8 9 10

View File

@ -9,7 +9,6 @@ import (
"net/url"
"time"
"github.com/matrix-org/gomatrixserverlib"
"github.com/tidwall/gjson"
)
@ -135,7 +134,7 @@ type SyncResponse struct {
NextBatch string `json:"next_batch"`
AccountData EventsResponse `json:"account_data"`
Presence struct {
Events []gomatrixserverlib.ClientEvent `json:"events,omitempty"`
Events []json.RawMessage `json:"events,omitempty"`
} `json:"presence"`
Rooms SyncRoomsResponse `json:"rooms"`
ToDevice EventsResponse `json:"to_device"`

View File

@ -8,7 +8,7 @@ import (
"testing"
"time"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
"github.com/matrix-org/sliding-sync/internal"
"github.com/matrix-org/sliding-sync/sync3"
"github.com/matrix-org/sliding-sync/sync3/caches"
@ -37,7 +37,7 @@ func (t *NopTransactionFetcher) TransactionIDForEvents(userID, deviceID string,
return
}
func newRoomMetadata(roomID string, lastMsgTimestamp gomatrixserverlib.Timestamp) internal.RoomMetadata {
func newRoomMetadata(roomID string, lastMsgTimestamp spec.Timestamp) internal.RoomMetadata {
m := internal.NewRoomMetadata(roomID)
m.NameEvent = "Room " + roomID
m.LastMessageTimestamp = uint64(lastMsgTimestamp)
@ -62,11 +62,11 @@ func TestConnStateInitial(t *testing.T) {
}
userID := "@TestConnStateInitial_alice:localhost"
deviceID := "yep"
timestampNow := gomatrixserverlib.Timestamp(1632131678061).Time()
timestampNow := spec.Timestamp(1632131678061).Time()
// initial sort order B, C, A
roomA := newRoomMetadata("!a:localhost", gomatrixserverlib.AsTimestamp(timestampNow.Add(-8*time.Second)))
roomB := newRoomMetadata("!b:localhost", gomatrixserverlib.AsTimestamp(timestampNow))
roomC := newRoomMetadata("!c:localhost", gomatrixserverlib.AsTimestamp(timestampNow.Add(-4*time.Second)))
roomA := newRoomMetadata("!a:localhost", spec.AsTimestamp(timestampNow.Add(-8*time.Second)))
roomB := newRoomMetadata("!b:localhost", spec.AsTimestamp(timestampNow))
roomC := newRoomMetadata("!c:localhost", spec.AsTimestamp(timestampNow.Add(-4*time.Second)))
timeline := map[string]json.RawMessage{
roomA.RoomID: testutils.NewEvent(t, "m.room.message", userID, map[string]interface{}{"body": "a"}),
roomB.RoomID: testutils.NewEvent(t, "m.room.message", userID, map[string]interface{}{"body": "b"}),
@ -232,7 +232,7 @@ func TestConnStateMultipleRanges(t *testing.T) {
}
userID := "@TestConnStateMultipleRanges_alice:localhost"
deviceID := "yep"
timestampNow := gomatrixserverlib.Timestamp(1632131678061)
timestampNow := spec.Timestamp(1632131678061)
var rooms []*internal.RoomMetadata
var roomIDs []string
globalCache := caches.NewGlobalCache(nil)
@ -373,7 +373,7 @@ func TestConnStateMultipleRanges(t *testing.T) {
// ` ` ` `
// 8,0,1,9,2,3,4,5,6,7 room
middleTimestamp := int64((roomIDToRoom[roomIDs[1]].LastMessageTimestamp + roomIDToRoom[roomIDs[2]].LastMessageTimestamp) / 2)
newEvent = testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(gomatrixserverlib.Timestamp(middleTimestamp).Time()))
newEvent = testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(spec.Timestamp(middleTimestamp).Time()))
dispatcher.OnNewEvent(context.Background(), roomIDs[9], newEvent, 1)
t.Logf("new event %s : %s", roomIDs[9], string(newEvent))
res, err = cs.OnIncomingRequest(context.Background(), ConnID, &sync3.Request{
@ -414,7 +414,7 @@ func TestBumpToOutsideRange(t *testing.T) {
}
userID := "@TestBumpToOutsideRange_alice:localhost"
deviceID := "yep"
timestampNow := gomatrixserverlib.Timestamp(1632131678061)
timestampNow := spec.Timestamp(1632131678061)
roomA := newRoomMetadata("!a:localhost", timestampNow)
roomB := newRoomMetadata("!b:localhost", timestampNow-1000)
roomC := newRoomMetadata("!c:localhost", timestampNow-2000)
@ -482,7 +482,7 @@ func TestBumpToOutsideRange(t *testing.T) {
})
// D gets bumped to C's position but it's still outside the range so nothing should happen
newEvent := testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(gomatrixserverlib.Timestamp(roomC.LastMessageTimestamp+2).Time()))
newEvent := testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(spec.Timestamp(roomC.LastMessageTimestamp+2).Time()))
dispatcher.OnNewEvent(context.Background(), roomD.RoomID, newEvent, 1)
// expire the context after 10ms so we don't wait forevar
@ -511,11 +511,11 @@ func TestConnStateRoomSubscriptions(t *testing.T) {
}
userID := "@TestConnStateRoomSubscriptions_alice:localhost"
deviceID := "yep"
timestampNow := gomatrixserverlib.Timestamp(1632131678061)
timestampNow := spec.Timestamp(1632131678061)
roomA := newRoomMetadata("!a:localhost", timestampNow)
roomB := newRoomMetadata("!b:localhost", gomatrixserverlib.Timestamp(timestampNow-1000))
roomC := newRoomMetadata("!c:localhost", gomatrixserverlib.Timestamp(timestampNow-2000))
roomD := newRoomMetadata("!d:localhost", gomatrixserverlib.Timestamp(timestampNow-3000))
roomB := newRoomMetadata("!b:localhost", spec.Timestamp(timestampNow-1000))
roomC := newRoomMetadata("!c:localhost", spec.Timestamp(timestampNow-2000))
roomD := newRoomMetadata("!d:localhost", spec.Timestamp(timestampNow-3000))
roomIDs := []string{roomA.RoomID, roomB.RoomID, roomC.RoomID, roomD.RoomID}
globalCache := caches.NewGlobalCache(nil)
globalCache.Startup(map[string]internal.RoomMetadata{
@ -620,7 +620,7 @@ func TestConnStateRoomSubscriptions(t *testing.T) {
},
})
// room D gets a new event but it's so old it doesn't bump to the top of the list
newEvent := testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(gomatrixserverlib.Timestamp(timestampNow-20000).Time()))
newEvent := testutils.NewEvent(t, "unimportant", "me", struct{}{}, testutils.WithTimestamp(spec.Timestamp(timestampNow-20000).Time()))
dispatcher.OnNewEvent(context.Background(), roomD.RoomID, newEvent, 1)
// we should get this message even though it's not in the range because we are subscribed to this room.
res, err = cs.OnIncomingRequest(context.Background(), ConnID, &sync3.Request{

View File

@ -275,6 +275,15 @@ func (c *CSAPI) SendEventSynced(t *testing.T, roomID string, e Event) string {
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,

View File

@ -0,0 +1,63 @@
package syncv3_test
import (
"testing"
"github.com/matrix-org/sliding-sync/sync3"
"github.com/matrix-org/sliding-sync/testutils/m"
)
func TestRedactionsAreRedactedWherePossible(t *testing.T) {
alice := registerNamedUser(t, "alice")
room := alice.CreateRoom(t, map[string]any{"preset": "public_chat"})
eventID := alice.SendEventSynced(t, room, Event{
Type: "m.room.message",
Content: map[string]interface{}{
"msgtype": "m.text",
"body": "I will be redacted",
},
})
res := alice.SlidingSync(t, sync3.Request{
Lists: map[string]sync3.RequestList{
"a": {
Ranges: sync3.SliceRanges{{0, 20}},
RoomSubscription: sync3.RoomSubscription{
TimelineLimit: 1,
},
},
},
})
m.MatchResponse(t, res, m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{
room: {MatchRoomTimelineMostRecent(1, []Event{{ID: eventID, Content: map[string]interface{}{
"msgtype": "m.text",
"body": "I will be redacted",
}}})},
}))
// redact the event
redactionEventID := alice.RedactEvent(t, room, eventID)
// see the redaction
alice.SlidingSyncUntilEventID(t, res.Pos, room, redactionEventID)
// now resync from scratch, the event should be redacted this time around.
res = alice.SlidingSync(t, sync3.Request{
Lists: map[string]sync3.RequestList{
"a": {
Ranges: sync3.SliceRanges{{0, 20}},
RoomSubscription: sync3.RoomSubscription{
TimelineLimit: 2,
},
},
},
})
m.MatchResponse(t, res, m.MatchRoomSubscriptionsStrict(map[string][]m.RoomMatcher{
room: {MatchRoomTimelineMostRecent(2, []Event{
{ID: eventID, Content: map[string]interface{}{}},
{ID: redactionEventID},
})},
}))
}

View File

@ -7,7 +7,7 @@ import (
"testing"
"time"
"github.com/matrix-org/gomatrixserverlib"
"github.com/matrix-org/gomatrixserverlib/spec"
)
// Common functions between testing.T and testing.B
@ -47,7 +47,7 @@ type eventMockModifier func(e *eventMock)
func WithTimestamp(ts time.Time) eventMockModifier {
return func(e *eventMock) {
e.OriginServerTS = int64(gomatrixserverlib.AsTimestamp(ts))
e.OriginServerTS = int64(spec.AsTimestamp(ts))
}
}
@ -79,7 +79,7 @@ func NewStateEvent(t TestBenchInterface, evType, stateKey, sender string, conten
Sender: sender,
Content: content,
EventID: generateEventID(t),
OriginServerTS: int64(gomatrixserverlib.AsTimestamp(time.Now())),
OriginServerTS: int64(spec.AsTimestamp(time.Now())),
}
for _, m := range modifiers {
m(e)
@ -98,7 +98,7 @@ func NewEvent(t TestBenchInterface, evType, sender string, content interface{},
Sender: sender,
Content: content,
EventID: generateEventID(t),
OriginServerTS: int64(gomatrixserverlib.AsTimestamp(time.Now())),
OriginServerTS: int64(spec.AsTimestamp(time.Now())),
}
for _, m := range modifiers {
m(e)
@ -132,7 +132,7 @@ func SetTimestamp(t *testing.T, event json.RawMessage, ts time.Time) json.RawMes
t.Errorf("Failed to parse eventMock: %s", err)
return nil
}
parsed.OriginServerTS = int64(gomatrixserverlib.AsTimestamp(ts))
parsed.OriginServerTS = int64(spec.AsTimestamp(ts))
edited, err := json.Marshal(parsed)
if err != nil {
t.Errorf("Failed to serialise eventMock: %s", err)