@@ -65,25 +65,9 @@ func mustHaveTransactionIDForEvent(t *testing.T, roomID, eventID, expectedTxnId
65
65
})
66
66
}
67
67
68
- func mustNotHaveTransactionIDForEvent (t * testing.T , roomID , eventID string ) client.SyncCheckOpt {
69
- return client .SyncTimelineHas (roomID , func (r gjson.Result ) bool {
70
- if r .Get ("event_id" ).Str == eventID {
71
- unsignedTxnId := r .Get ("unsigned.transaction_id" )
72
- if unsignedTxnId .Exists () {
73
- t .Fatalf ("Event %s in room %s should NOT have a 'unsigned.transaction_id', but it did (%s)" , eventID , roomID , unsignedTxnId .Str )
74
- }
75
-
76
- return true
77
- }
78
-
79
- return false
80
- })
81
- }
82
-
83
- // TestTxnScopeOnLocalEcho tests that transaction IDs in the sync response are scoped to the "client session", not the device
68
+ // TestTxnScopeOnLocalEcho tests that transaction IDs in the sync response are scoped to the device
84
69
func TestTxnScopeOnLocalEcho (t * testing.T ) {
85
- // Conduit scope transaction IDs to the device ID, not the access token.
86
- runtime .SkipIf (t , runtime .Conduit )
70
+ runtime .SkipIf (t , runtime .Dendrite )
87
71
88
72
deployment := Deploy (t , b .BlueprintCleanHS )
89
73
defer deployment .Destroy (t )
@@ -115,15 +99,14 @@ func TestTxnScopeOnLocalEcho(t *testing.T) {
115
99
c2 .UserID , c2 .AccessToken , c2 .DeviceID = c2 .LoginUser (t , "alice" , "password" , client .WithDeviceID (c1 .DeviceID ))
116
100
must .EqualStr (t , c1 .DeviceID , c2 .DeviceID , "Device ID should be the same" )
117
101
118
- // When syncing, we should find the event and it should *not* have a transaction ID on the second client.
119
- c2 .MustSyncUntil (t , client.SyncReq {}, mustNotHaveTransactionIDForEvent (t , roomID , eventID ))
102
+ // When syncing, we should find the event and it should have the same transaction ID on the second client.
103
+ c2 .MustSyncUntil (t , client.SyncReq {}, mustHaveTransactionIDForEvent (t , roomID , eventID , txnId ))
120
104
}
121
105
122
- // TestTxnIdempotencyScopedToClientSession tests that transaction IDs are scoped to a "client session"
123
- // and behave as expected across multiple clients even if they use the same device ID
124
- func TestTxnIdempotencyScopedToClientSession (t * testing.T ) {
125
- // Conduit scope transaction IDs to the device ID, not the client session.
126
- runtime .SkipIf (t , runtime .Conduit )
106
+ // TestTxnIdempotencyScopedToDevice tests that transaction IDs are scoped to a device
107
+ // and behave as expected across multiple clients if they use the same device ID
108
+ func TestTxnIdempotencyScopedToDevice (t * testing.T ) {
109
+ runtime .SkipIf (t , runtime .Dendrite )
127
110
128
111
deployment := Deploy (t , b .BlueprintCleanHS )
129
112
defer deployment .Destroy (t )
@@ -156,8 +139,8 @@ func TestTxnIdempotencyScopedToClientSession(t *testing.T) {
156
139
// send another event with the same txnId via the second client
157
140
eventID2 := c2 .SendEventUnsyncedWithTxnID (t , roomID , event , txnId )
158
141
159
- // the two events should have different event IDs as they came from different clients
160
- must .NotEqualStr (t , eventID2 , eventID1 , "Expected eventID1 and eventID2 to be different from two clients sharing the same device ID" )
142
+ // the two events should have the same event IDs as they came from the same device
143
+ must .EqualStr (t , eventID2 , eventID1 , "Expected eventID1 and eventID2 to be the same from two clients sharing the same device ID" )
161
144
}
162
145
163
146
// TestTxnIdempotency tests that PUT requests idempotency follows required semantics
@@ -213,3 +196,52 @@ func TestTxnIdempotency(t *testing.T) {
213
196
214
197
must .NotEqualStr (t , eventID4 , eventID3 , "Expected eventID4 and eventID3 to be different, but they were not" )
215
198
}
199
+
200
+ // TestTxnIdWithRefreshToken tests that when a client refreshes its access token,
201
+ // it still gets back a transaction ID in the sync response and idempotency is respected.
202
+ func TestTxnIdWithRefreshToken (t * testing.T ) {
203
+ // Dendrite and Conduit don't support refresh tokens yet.
204
+ runtime .SkipIf (t , runtime .Dendrite , runtime .Conduit )
205
+
206
+ deployment := Deploy (t , b .BlueprintCleanHS )
207
+ defer deployment .Destroy (t )
208
+
209
+ deployment .RegisterUser (t , "hs1" , "alice" , "password" , false )
210
+
211
+ c := deployment .Client (t , "hs1" , "" )
212
+
213
+ var refreshToken string
214
+ c .UserID , c .AccessToken , refreshToken , c .DeviceID , _ = c .LoginUserWithRefreshToken (t , "alice" , "password" )
215
+
216
+ // Create a room where we can send events.
217
+ roomID := c .CreateRoom (t , map [string ]interface {}{})
218
+
219
+ txnId := "abcdef"
220
+ // We send an event
221
+ eventID1 := c .SendEventUnsyncedWithTxnID (t , roomID , b.Event {
222
+ Type : "m.room.message" ,
223
+ Content : map [string ]interface {}{
224
+ "msgtype" : "m.text" ,
225
+ "body" : "first" ,
226
+ },
227
+ }, txnId )
228
+
229
+ // Use the refresh token to get a new access token.
230
+ c .AccessToken , refreshToken , _ = c .ConsumeRefreshToken (t , refreshToken )
231
+
232
+ // When syncing, we should find the event and it should also have the correct transaction ID even
233
+ // though the access token is different.
234
+ c .MustSyncUntil (t , client.SyncReq {}, mustHaveTransactionIDForEvent (t , roomID , eventID1 , txnId ))
235
+
236
+ // We try sending the event again with the same transaction ID
237
+ eventID2 := c .SendEventUnsyncedWithTxnID (t , roomID , b.Event {
238
+ Type : "m.room.message" ,
239
+ Content : map [string ]interface {}{
240
+ "msgtype" : "m.text" ,
241
+ "body" : "first" ,
242
+ },
243
+ }, txnId )
244
+
245
+ // The event should have been deduplicated and we should get back the same event ID
246
+ must .EqualStr (t , eventID2 , eventID1 , "Expected eventID1 and eventID2 to be the same from a client using a refresh token" )
247
+ }
0 commit comments