@@ -144,6 +144,117 @@ describe("MatrixClientPeg", () => {
144
144
expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , true ) ;
145
145
} ) ;
146
146
147
+ describe ( "Rust staged rollout" , ( ) => {
148
+ function mockSettingStore (
149
+ userIsUsingRust : boolean ,
150
+ newLoginShouldUseRust : boolean ,
151
+ rolloutPercent : number | null ,
152
+ ) {
153
+ const originalGetValue = SettingsStore . getValue ;
154
+ jest . spyOn ( SettingsStore , "getValue" ) . mockImplementation (
155
+ ( settingName : string , roomId : string | null = null , excludeDefault = false ) => {
156
+ if ( settingName === "feature_rust_crypto" ) {
157
+ return userIsUsingRust ;
158
+ }
159
+ return originalGetValue ( settingName , roomId , excludeDefault ) ;
160
+ } ,
161
+ ) ;
162
+ const originalGetValueAt = SettingsStore . getValueAt ;
163
+ jest . spyOn ( SettingsStore , "getValueAt" ) . mockImplementation (
164
+ ( level : SettingLevel , settingName : string ) => {
165
+ if ( settingName === "feature_rust_crypto" ) {
166
+ return newLoginShouldUseRust ;
167
+ }
168
+ // if null we let the original implementation handle it to get the default
169
+ if ( settingName === "RustCrypto.staged_rollout_percent" && rolloutPercent !== null ) {
170
+ return rolloutPercent ;
171
+ }
172
+ return originalGetValueAt ( level , settingName ) ;
173
+ } ,
174
+ ) ;
175
+ }
176
+
177
+ let mockSetValue : jest . SpyInstance ;
178
+ let mockInitCrypto : jest . SpyInstance ;
179
+ let mockInitRustCrypto : jest . SpyInstance ;
180
+
181
+ beforeEach ( ( ) => {
182
+ mockSetValue = jest . spyOn ( SettingsStore , "setValue" ) . mockResolvedValue ( undefined ) ;
183
+ mockInitCrypto = jest . spyOn ( testPeg . safeGet ( ) , "initCrypto" ) . mockResolvedValue ( undefined ) ;
184
+ mockInitRustCrypto = jest . spyOn ( testPeg . safeGet ( ) , "initRustCrypto" ) . mockResolvedValue ( undefined ) ;
185
+ } ) ;
186
+
187
+ it ( "Should not migrate existing login if rollout is 0" , async ( ) => {
188
+ mockSettingStore ( false , true , 0 ) ;
189
+
190
+ await testPeg . start ( ) ;
191
+ expect ( mockInitCrypto ) . toHaveBeenCalled ( ) ;
192
+ expect ( mockInitRustCrypto ) . not . toHaveBeenCalledTimes ( 1 ) ;
193
+
194
+ // we should have stashed the setting in the settings store
195
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , false ) ;
196
+ } ) ;
197
+
198
+ it ( "Should migrate existing login if rollout is 100" , async ( ) => {
199
+ mockSettingStore ( false , true , 100 ) ;
200
+ await testPeg . start ( ) ;
201
+ expect ( mockInitCrypto ) . not . toHaveBeenCalled ( ) ;
202
+ expect ( mockInitRustCrypto ) . toHaveBeenCalledTimes ( 1 ) ;
203
+
204
+ // we should have stashed the setting in the settings store
205
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , true ) ;
206
+ } ) ;
207
+
208
+ it ( "Should migrate existing login if user is in rollout bucket" , async ( ) => {
209
+ mockSettingStore ( false , true , 30 ) ;
210
+
211
+ // Use a device id that is known to be in the 30% bucket (hash modulo 100 < 30)
212
+ const spy = jest . spyOn ( testPeg . get ( ) ! , "getDeviceId" ) . mockReturnValue ( "AAA" ) ;
213
+
214
+ await testPeg . start ( ) ;
215
+ expect ( mockInitCrypto ) . not . toHaveBeenCalled ( ) ;
216
+ expect ( mockInitRustCrypto ) . toHaveBeenCalledTimes ( 1 ) ;
217
+
218
+ // we should have stashed the setting in the settings store
219
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , true ) ;
220
+
221
+ spy . mockReset ( ) ;
222
+ } ) ;
223
+
224
+ it ( "Should not migrate existing login if rollout is malformed" , async ( ) => {
225
+ mockSettingStore ( false , true , 100.1 ) ;
226
+
227
+ await testPeg . start ( ) ;
228
+ expect ( mockInitCrypto ) . toHaveBeenCalled ( ) ;
229
+ expect ( mockInitRustCrypto ) . not . toHaveBeenCalledTimes ( 1 ) ;
230
+
231
+ // we should have stashed the setting in the settings store
232
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , false ) ;
233
+ } ) ;
234
+
235
+ it ( "Default is to not migrate" , async ( ) => {
236
+ mockSettingStore ( false , true , null ) ;
237
+
238
+ await testPeg . start ( ) ;
239
+ expect ( mockInitCrypto ) . toHaveBeenCalled ( ) ;
240
+ expect ( mockInitRustCrypto ) . not . toHaveBeenCalledTimes ( 1 ) ;
241
+
242
+ // we should have stashed the setting in the settings store
243
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , false ) ;
244
+ } ) ;
245
+
246
+ it ( "Should not migrate if feature_rust_crypto is false" , async ( ) => {
247
+ mockSettingStore ( false , false , 100 ) ;
248
+
249
+ await testPeg . start ( ) ;
250
+ expect ( mockInitCrypto ) . toHaveBeenCalled ( ) ;
251
+ expect ( mockInitRustCrypto ) . not . toHaveBeenCalledTimes ( 1 ) ;
252
+
253
+ // we should have stashed the setting in the settings store
254
+ expect ( mockSetValue ) . toHaveBeenCalledWith ( "feature_rust_crypto" , null , SettingLevel . DEVICE , false ) ;
255
+ } ) ;
256
+ } ) ;
257
+
147
258
it ( "should reload when store database closes for a guest user" , async ( ) => {
148
259
testPeg . safeGet ( ) . isGuest = ( ) => true ;
149
260
const emitter = new EventEmitter ( ) ;
0 commit comments