Skip to content

Commit de7d53f

Browse files
committed
test(wownero): add Wownero SWB round trip test
1 parent 0d2ee6f commit de7d53f

File tree

1 file changed

+261
-1
lines changed

1 file changed

+261
-1
lines changed

lib/services/testing/test_suites/wownero_integration_test_suite.dart

Lines changed: 261 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,17 @@
99
*/
1010

1111
import 'dart:async';
12+
import 'dart:convert';
1213
import 'dart:io';
1314
import 'dart:math';
1415
import 'package:compat/old_cw_core/path_for_wallet.dart' as lib_monero_compat;
1516
import 'package:flutter/material.dart';
1617
import 'package:logger/logger.dart';
1718
import 'package:cs_monero/cs_monero.dart' as lib_monero;
19+
import 'package:tuple/tuple.dart';
1820
import '../../../utilities/logger.dart';
1921
import '../../../utilities/stack_file_system.dart';
22+
import '../../../pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart';
2023
import '../test_suite_interface.dart';
2124
import '../testing_models.dart';
2225

@@ -47,7 +50,9 @@ class WowneroIntegrationTestSuite implements TestSuiteInterface {
4750
Logging.instance.log(Level.info, "Starting Wownero integration test suite...");
4851

4952
await _testWowneroMnemonicGeneration();
50-
53+
54+
await _testWowneroStackWalletBackupRoundTrip();
55+
5156
stopwatch.stop();
5257
_updateStatus(TestSuiteStatus.passed);
5358

@@ -198,6 +203,261 @@ class WowneroIntegrationTestSuite implements TestSuiteInterface {
198203
}
199204
}
200205

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

0 commit comments

Comments
 (0)