Skip to content

Commit 4130100

Browse files
committed
test(salvium): add Salvium SWB round trip test
1 parent de7d53f commit 4130100

File tree

1 file changed

+252
-1
lines changed

1 file changed

+252
-1
lines changed

lib/services/testing/test_suites/salvium_integration_test_suite.dart

Lines changed: 252 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
*/
1010

1111
import 'dart:async';
12+
import 'dart:convert';
1213
import 'dart:io';
1314
import 'dart:math';
1415
import 'package:flutter/material.dart';
1516
import 'package:logger/logger.dart';
1617
import 'package:cs_salvium/cs_salvium.dart' as lib_salvium;
18+
import 'package:tuple/tuple.dart';
1719
import '../../../utilities/logger.dart';
1820
import '../../../utilities/stack_file_system.dart';
21+
import '../../../pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
1922
import '../test_suite_interface.dart';
2023
import '../testing_models.dart';
2124

@@ -46,7 +49,9 @@ class SalviumIntegrationTestSuite implements TestSuiteInterface {
4649
Logging.instance.log(Level.info, "Starting Salvium integration test suite...");
4750

4851
await _testSalviumMnemonicGeneration();
49-
52+
53+
await _testSalviumStackWalletBackupRoundTrip();
54+
5055
stopwatch.stop();
5156
_updateStatus(TestSuiteStatus.passed);
5257

@@ -188,6 +193,252 @@ class SalviumIntegrationTestSuite implements TestSuiteInterface {
188193
}
189194
}
190195

196+
/// Tests Stack Wallet Backup round-trip functionality.
197+
///
198+
/// Creates Salvium wallets with 25-word mnemonics, saves the mnemonics,
199+
/// creates backups, restores the backups, and verifies the restored mnemonics match the originals.
200+
Future<void> _testSalviumStackWalletBackupRoundTrip() async {
201+
Logging.instance.log(Level.info, "Testing Stack Wallet Backup round-trip for Salvium...");
202+
203+
final tempDir = await StackFileSystem.applicationRootDirectory();
204+
final testId = Random().nextInt(10000);
205+
206+
try {
207+
// Test 25-word mnemonic backup (Salvium only supports 25-word mnemonics).
208+
await _testSalviumBackupWithSeedType(
209+
tempDir: tempDir,
210+
testId: testId,
211+
seedType: lib_salvium.SalviumSeedType.twentyFive,
212+
expectedWordCount: 25,
213+
suffix: "25",
214+
);
215+
216+
Logging.instance.log(Level.info, "✓ Salvium Stack Wallet Backup round-trip test passed successfully!");
217+
} catch (e) {
218+
Logging.instance.log(Level.error, "Salvium Stack Wallet Backup round-trip test failed: $e");
219+
rethrow;
220+
}
221+
}
222+
223+
/// Tests Stack Wallet Backup round-trip functionality for Salvium.
224+
Future<void> _testSalviumBackupWithSeedType({
225+
required Directory tempDir,
226+
required int testId,
227+
required lib_salvium.SalviumSeedType seedType,
228+
required int expectedWordCount,
229+
required String suffix,
230+
}) async {
231+
Logging.instance.log(Level.info, "Testing ${expectedWordCount}-word Salvium mnemonic backup...");
232+
233+
final walletName = "test_salvium_backup_${testId}_$suffix";
234+
final walletPath = "${tempDir.path}/$walletName";
235+
final backupPath = "${tempDir.path}/${walletName}_backup.swb";
236+
const walletPassword = "testpass123";
237+
const backupPassword = "backuppass456";
238+
239+
lib_salvium.Wallet? originalWallet;
240+
String? originalMnemonic;
241+
242+
try {
243+
// Step 1: Create a new Salvium wallet using lib_salvium directly.
244+
Logging.instance.log(Level.info, "Step 1: Creating new ${expectedWordCount}-word Salvium wallet...");
245+
246+
originalWallet = await lib_salvium.SalviumWallet.create(
247+
path: walletPath,
248+
password: walletPassword,
249+
seedType: seedType,
250+
seedOffset: "",
251+
);
252+
253+
// Step 2: Save the original mnemonic out-of-band.
254+
Logging.instance.log(Level.info, "Step 2: Saving original mnemonic...");
255+
originalMnemonic = originalWallet.getSeed();
256+
257+
if (originalMnemonic.isEmpty) {
258+
throw Exception("Failed to retrieve mnemonic from created Salvium wallet");
259+
}
260+
261+
final originalWords = originalMnemonic.split(' ');
262+
Logging.instance.log(Level.info, "Original Salvium mnemonic has ${originalWords.length} words");
263+
264+
// Validate the mnemonic format.
265+
if (originalWords.length != expectedWordCount) {
266+
throw Exception("Expected ${expectedWordCount}-word mnemonic, got ${originalWords.length} words");
267+
}
268+
269+
// Step 3: Create a Stack Wallet Backup.
270+
Logging.instance.log(Level.info, "Step 3: Creating Stack Wallet Backup...");
271+
272+
// Create a minimal backup JSON with just our test wallet.
273+
final backupJson = {
274+
"wallets": [
275+
{
276+
"name": walletName,
277+
"id": "test_salvium_wallet_${testId}_$suffix",
278+
"mnemonic": originalMnemonic,
279+
"mnemonicPassphrase": "",
280+
"coinName": "salvium",
281+
"storedChainHeight": 0,
282+
"restoreHeight": 0,
283+
"notes": {},
284+
"isFavorite": false,
285+
"otherDataJsonString": null,
286+
}
287+
],
288+
"prefs": {
289+
"currency": "USD",
290+
"useBiometrics": false,
291+
"hasPin": false,
292+
"language": "en",
293+
"showFavoriteWallets": true,
294+
"wifiOnly": false,
295+
"syncType": "allWalletsOnStartup",
296+
"walletIdsSyncOnStartup": [],
297+
"showTestNetCoins": false,
298+
"isAutoBackupEnabled": false,
299+
"autoBackupLocation": null,
300+
"backupFrequencyType": "BackupFrequencyType.everyAppStart",
301+
"lastAutoBackup": DateTime.now().toString(),
302+
},
303+
"nodes": [],
304+
"addressBookEntries": [],
305+
"tradeHistory": [],
306+
"tradeTxidLookupData": [],
307+
"tradeNotes": {},
308+
};
309+
310+
final jsonString = jsonEncode(backupJson);
311+
312+
// Encrypt and save the backup.
313+
final success = await SWB.encryptStackWalletWithPassphrase(
314+
backupPath,
315+
backupPassword,
316+
jsonString,
317+
);
318+
319+
if (!success) {
320+
throw Exception("Failed to create Stack Wallet Backup for Salvium");
321+
}
322+
323+
Logging.instance.log(Level.info, "Backup created successfully at: $backupPath");
324+
325+
// Step 4: Restore the Stack Wallet Backup.
326+
Logging.instance.log(Level.info, "Step 4: Restoring Stack Wallet Backup...");
327+
328+
final restoredJsonString = await SWB.decryptStackWalletWithPassphrase(
329+
Tuple2(backupPath, backupPassword),
330+
);
331+
332+
if (restoredJsonString == null) {
333+
throw Exception("Failed to decrypt Stack Wallet Backup for Salvium");
334+
}
335+
336+
final restoredJson = jsonDecode(restoredJsonString) as Map<String, dynamic>;
337+
final restoredWallets = restoredJson["wallets"] as List<dynamic>;
338+
339+
if (restoredWallets.isEmpty) {
340+
throw Exception("No wallets found in restored Salvium backup");
341+
}
342+
343+
final restoredWalletData = restoredWallets.first as Map<String, dynamic>;
344+
final restoredMnemonic = restoredWalletData["mnemonic"] as String;
345+
346+
// Step 5: Verify that the restored mnemonic matches the original.
347+
Logging.instance.log(Level.info, "Step 5: Verifying Salvium mnemonic integrity...");
348+
349+
if (restoredMnemonic != originalMnemonic) {
350+
throw Exception(
351+
"Salvium mnemonic mismatch!\n"
352+
"Original: $originalMnemonic\n"
353+
"Restored: $restoredMnemonic"
354+
);
355+
}
356+
357+
// Additional verification: check word count.
358+
final restoredWords = restoredMnemonic.split(' ');
359+
360+
if (originalWords.length != restoredWords.length) {
361+
throw Exception(
362+
"Word count mismatch: original ${originalWords.length}, restored ${restoredWords.length}"
363+
);
364+
}
365+
366+
// Verify each word matches.
367+
for (int i = 0; i < originalWords.length; i++) {
368+
if (originalWords[i] != restoredWords[i]) {
369+
throw Exception(
370+
"Word mismatch at position $i: '${originalWords[i]}' != '${restoredWords[i]}'"
371+
);
372+
}
373+
}
374+
375+
// Step 6: Additional test - verify we can recreate the wallet from the restored mnemonic.
376+
Logging.instance.log(Level.info, "Step 6: Testing Salvium wallet restoration with recovered mnemonic...");
377+
378+
final testWalletPath = "${tempDir.path}/test_salvium_restore_${testId}_$suffix";
379+
lib_salvium.Wallet? restoredWallet;
380+
381+
try {
382+
restoredWallet = await lib_salvium.SalviumWallet.restoreWalletFromSeed(
383+
path: testWalletPath,
384+
password: walletPassword,
385+
seed: restoredMnemonic,
386+
restoreHeight: 0,
387+
seedOffset: "",
388+
);
389+
390+
final restoredMnemonicFromWallet = restoredWallet.getSeed();
391+
392+
if (restoredMnemonicFromWallet != originalMnemonic) {
393+
throw Exception(
394+
"Restored Salvium wallet mnemonic doesn't match original!\n"
395+
"Original: $originalMnemonic\n"
396+
"From restored wallet: $restoredMnemonicFromWallet"
397+
);
398+
}
399+
400+
Logging.instance.log(Level.info, "✓ Successfully restored ${expectedWordCount}-word Salvium wallet from backup mnemonic");
401+
402+
} finally {
403+
await restoredWallet?.close();
404+
// Clean up restored wallet files.
405+
final testWalletFile = File(testWalletPath);
406+
final testKeysFile = File("$testWalletPath.keys");
407+
final testAddressFile = File("$testWalletPath.address.txt");
408+
409+
if (await testWalletFile.exists()) await testWalletFile.delete();
410+
if (await testKeysFile.exists()) await testKeysFile.delete();
411+
if (await testAddressFile.exists()) await testAddressFile.delete();
412+
}
413+
414+
Logging.instance.log(Level.info, "✓ ${expectedWordCount}-word Salvium Stack Wallet Backup round-trip test passed!");
415+
Logging.instance.log(Level.info, "✓ Original and restored Salvium mnemonics match perfectly");
416+
Logging.instance.log(Level.info, "✓ Verified ${originalWords.length}-word Salvium mnemonic integrity");
417+
Logging.instance.log(Level.info, "✓ Confirmed ${expectedWordCount}-word Salvium wallet can be restored from backup mnemonic");
418+
419+
} finally {
420+
// Cleanup.
421+
try {
422+
await originalWallet?.close();
423+
424+
// Clean up test files.
425+
final walletFile = File(walletPath);
426+
final keysFile = File("$walletPath.keys");
427+
final addressFile = File("$walletPath.address.txt");
428+
final backupFile = File(backupPath);
429+
430+
if (await walletFile.exists()) await walletFile.delete();
431+
if (await keysFile.exists()) await keysFile.delete();
432+
if (await addressFile.exists()) await addressFile.delete();
433+
if (await backupFile.exists()) await backupFile.delete();
434+
435+
Logging.instance.log(Level.info, "Cleaned up test files for ${expectedWordCount}-word Salvium backup test");
436+
} catch (e) {
437+
Logging.instance.log(Level.warning, "Cleanup error for ${expectedWordCount}-word Salvium test: $e");
438+
}
439+
}
440+
}
441+
191442
void _updateStatus(TestSuiteStatus newStatus) {
192443
_status = newStatus;
193444
_statusController.add(newStatus);

0 commit comments

Comments
 (0)