@@ -16,9 +16,21 @@ limitations under the License.
16
16
17
17
import "fake-indexeddb/auto" ;
18
18
import { IDBFactory } from "fake-indexeddb" ;
19
+ import * as RustSdkCryptoJs from "@matrix-org/matrix-sdk-crypto-js" ;
20
+ import {
21
+ KeysBackupRequest ,
22
+ KeysClaimRequest ,
23
+ KeysQueryRequest ,
24
+ KeysUploadRequest ,
25
+ SignatureUploadRequest ,
26
+ } from "@matrix-org/matrix-sdk-crypto-js" ;
27
+ import { Mocked } from "jest-mock" ;
28
+ import MockHttpBackend from "matrix-mock-request" ;
19
29
20
30
import { RustCrypto } from "../../src/rust-crypto/rust-crypto" ;
21
31
import { initRustCrypto } from "../../src/rust-crypto" ;
32
+ import { HttpApiEvent , HttpApiEventHandlerMap , IHttpOpts , MatrixHttpApi } from "../../src" ;
33
+ import { TypedEventEmitter } from "../../src/models/typed-event-emitter" ;
22
34
23
35
afterEach ( ( ) => {
24
36
// reset fake-indexeddb after each test, to make sure we don't leak connections
@@ -31,16 +43,163 @@ describe("RustCrypto", () => {
31
43
const TEST_USER = "@alice:example.com" ;
32
44
const TEST_DEVICE_ID = "TEST_DEVICE" ;
33
45
34
- let rustCrypto : RustCrypto ;
46
+ describe ( ".exportRoomKeys" , ( ) => {
47
+ let rustCrypto : RustCrypto ;
35
48
36
- beforeEach ( async ( ) => {
37
- rustCrypto = ( await initRustCrypto ( TEST_USER , TEST_DEVICE_ID ) ) as RustCrypto ;
38
- } ) ;
49
+ beforeEach ( async ( ) => {
50
+ const mockHttpApi = { } as MatrixHttpApi < IHttpOpts > ;
51
+ rustCrypto = ( await initRustCrypto ( mockHttpApi , TEST_USER , TEST_DEVICE_ID ) ) as RustCrypto ;
52
+ } ) ;
39
53
40
- describe ( ".exportRoomKeys" , ( ) => {
41
54
it ( "should return a list" , async ( ) => {
42
55
const keys = await rustCrypto . exportRoomKeys ( ) ;
43
56
expect ( Array . isArray ( keys ) ) . toBeTruthy ( ) ;
44
57
} ) ;
45
58
} ) ;
59
+
60
+ describe ( "outgoing requests" , ( ) => {
61
+ /** the RustCrypto implementation under test */
62
+ let rustCrypto : RustCrypto ;
63
+
64
+ /** A mock http backend which rustCrypto is connected to */
65
+ let httpBackend : MockHttpBackend ;
66
+
67
+ /** a mocked-up OlmMachine which rustCrypto is connected to */
68
+ let olmMachine : Mocked < RustSdkCryptoJs . OlmMachine > ;
69
+
70
+ /** A list of results to be returned from olmMachine.outgoingRequest. Each call will shift a result off
71
+ * the front of the queue, until it is empty. */
72
+ let outgoingRequestQueue : Array < Array < any > > ;
73
+
74
+ /** wait for a call to olmMachine.markRequestAsSent */
75
+ function awaitCallToMarkAsSent ( ) : Promise < void > {
76
+ return new Promise ( ( resolve , _reject ) => {
77
+ olmMachine . markRequestAsSent . mockImplementationOnce ( async ( ) => {
78
+ resolve ( undefined ) ;
79
+ } ) ;
80
+ } ) ;
81
+ }
82
+
83
+ beforeEach ( async ( ) => {
84
+ httpBackend = new MockHttpBackend ( ) ;
85
+
86
+ await RustSdkCryptoJs . initAsync ( ) ;
87
+
88
+ const dummyEventEmitter = new TypedEventEmitter < HttpApiEvent , HttpApiEventHandlerMap > ( ) ;
89
+ const httpApi = new MatrixHttpApi ( dummyEventEmitter , {
90
+ baseUrl : "https://example.com" ,
91
+ prefix : "/_matrix" ,
92
+ fetchFn : httpBackend . fetchFn as typeof global . fetch ,
93
+ } ) ;
94
+
95
+ // for these tests we use a mock OlmMachine, with an implementation of outgoingRequests that
96
+ // returns objects from outgoingRequestQueue
97
+ outgoingRequestQueue = [ ] ;
98
+ olmMachine = {
99
+ outgoingRequests : jest . fn ( ) . mockImplementation ( ( ) => {
100
+ return Promise . resolve ( outgoingRequestQueue . shift ( ) ?? [ ] ) ;
101
+ } ) ,
102
+ markRequestAsSent : jest . fn ( ) ,
103
+ close : jest . fn ( ) ,
104
+ } as unknown as Mocked < RustSdkCryptoJs . OlmMachine > ;
105
+
106
+ rustCrypto = new RustCrypto ( olmMachine , httpApi , TEST_USER , TEST_DEVICE_ID ) ;
107
+ } ) ;
108
+
109
+ it ( "should poll for outgoing messages" , ( ) => {
110
+ rustCrypto . onSyncCompleted ( { } ) ;
111
+ expect ( olmMachine . outgoingRequests ) . toHaveBeenCalled ( ) ;
112
+ } ) ;
113
+
114
+ /* simple requests that map directly to the request body */
115
+ const tests : Array < [ any , "POST" | "PUT" , string ] > = [
116
+ [ KeysUploadRequest , "POST" , "https://example.com/_matrix/client/v3/keys/upload" ] ,
117
+ [ KeysQueryRequest , "POST" , "https://example.com/_matrix/client/v3/keys/query" ] ,
118
+ [ KeysClaimRequest , "POST" , "https://example.com/_matrix/client/v3/keys/claim" ] ,
119
+ [ SignatureUploadRequest , "POST" , "https://example.com/_matrix/client/v3/keys/signatures/upload" ] ,
120
+ [ KeysBackupRequest , "PUT" , "https://example.com/_matrix/client/v3/room_keys/keys" ] ,
121
+ ] ;
122
+
123
+ for ( const [ RequestClass , expectedMethod , expectedPath ] of tests ) {
124
+ it ( `should handle ${ RequestClass . name } s` , async ( ) => {
125
+ const testBody = '{ "foo": "bar" }' ;
126
+ const outgoingRequest = new RequestClass ( "1234" , testBody ) ;
127
+ outgoingRequestQueue . push ( [ outgoingRequest ] ) ;
128
+
129
+ const testResponse = '{ "result": 1 }' ;
130
+ httpBackend
131
+ . when ( expectedMethod , "/_matrix" )
132
+ . check ( ( req ) => {
133
+ expect ( req . path ) . toEqual ( expectedPath ) ;
134
+ expect ( req . rawData ) . toEqual ( testBody ) ;
135
+ expect ( req . headers [ "Accept" ] ) . toEqual ( "application/json" ) ;
136
+ expect ( req . headers [ "Content-Type" ] ) . toEqual ( "application/json" ) ;
137
+ } )
138
+ . respond ( 200 , testResponse , true ) ;
139
+
140
+ rustCrypto . onSyncCompleted ( { } ) ;
141
+
142
+ expect ( olmMachine . outgoingRequests ) . toHaveBeenCalledTimes ( 1 ) ;
143
+
144
+ const markSentCallPromise = awaitCallToMarkAsSent ( ) ;
145
+ await httpBackend . flushAllExpected ( ) ;
146
+
147
+ await markSentCallPromise ;
148
+ expect ( olmMachine . markRequestAsSent ) . toHaveBeenCalledWith ( "1234" , outgoingRequest . type , testResponse ) ;
149
+ httpBackend . verifyNoOutstandingRequests ( ) ;
150
+ } ) ;
151
+ }
152
+
153
+ it ( "does not explode with unknown requests" , async ( ) => {
154
+ const outgoingRequest = { id : "5678" , type : 987 } ;
155
+ outgoingRequestQueue . push ( [ outgoingRequest ] ) ;
156
+
157
+ rustCrypto . onSyncCompleted ( { } ) ;
158
+
159
+ await awaitCallToMarkAsSent ( ) ;
160
+ expect ( olmMachine . markRequestAsSent ) . toHaveBeenCalledWith ( "5678" , 987 , "" ) ;
161
+ } ) ;
162
+
163
+ it ( "stops looping when stop() is called" , async ( ) => {
164
+ const testResponse = '{ "result": 1 }' ;
165
+
166
+ for ( let i = 0 ; i < 5 ; i ++ ) {
167
+ outgoingRequestQueue . push ( [ new KeysQueryRequest ( "1234" , "{}" ) ] ) ;
168
+ httpBackend . when ( "POST" , "/_matrix" ) . respond ( 200 , testResponse , true ) ;
169
+ }
170
+
171
+ rustCrypto . onSyncCompleted ( { } ) ;
172
+
173
+ expect ( rustCrypto [ "outgoingRequestLoopRunning" ] ) . toBeTruthy ( ) ;
174
+
175
+ // go a couple of times round the loop
176
+ await httpBackend . flush ( "/_matrix" , 1 ) ;
177
+ await awaitCallToMarkAsSent ( ) ;
178
+
179
+ await httpBackend . flush ( "/_matrix" , 1 ) ;
180
+ await awaitCallToMarkAsSent ( ) ;
181
+
182
+ // a second sync while this is going on shouldn't make any difference
183
+ rustCrypto . onSyncCompleted ( { } ) ;
184
+
185
+ await httpBackend . flush ( "/_matrix" , 1 ) ;
186
+ await awaitCallToMarkAsSent ( ) ;
187
+
188
+ // now stop...
189
+ rustCrypto . stop ( ) ;
190
+
191
+ // which should (eventually) cause the loop to stop with no further calls to outgoingRequests
192
+ olmMachine . outgoingRequests . mockReset ( ) ;
193
+
194
+ await new Promise ( ( resolve ) => {
195
+ setTimeout ( resolve , 100 ) ;
196
+ } ) ;
197
+ expect ( rustCrypto [ "outgoingRequestLoopRunning" ] ) . toBeFalsy ( ) ;
198
+ httpBackend . verifyNoOutstandingRequests ( ) ;
199
+ expect ( olmMachine . outgoingRequests ) . not . toHaveBeenCalled ( ) ;
200
+
201
+ // we sent three, so there should be 2 left
202
+ expect ( outgoingRequestQueue . length ) . toEqual ( 2 ) ;
203
+ } ) ;
204
+ } ) ;
46
205
} ) ;
0 commit comments