Skip to content

Commit e7e7964

Browse files
committed
feat(test): ltc mweb integration test
1 parent 8b6adba commit e7e7964

File tree

3 files changed

+310
-1
lines changed

3 files changed

+310
-1
lines changed
Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,302 @@
1+
/*
2+
* This file is part of Stack Wallet.
3+
*
4+
* Copyright (c) 2025 Cypher Stack
5+
* All Rights Reserved.
6+
* The code is distributed under GPLv3 license, see LICENSE file for details.
7+
* Generated by Cypher Stack on 2025-08-14
8+
*
9+
*/
10+
11+
import 'dart:async';
12+
import 'dart:io';
13+
import 'dart:math';
14+
import 'dart:typed_data';
15+
import 'package:flutter/material.dart';
16+
import 'package:logger/logger.dart';
17+
import 'package:flutter_mwebd/flutter_mwebd.dart';
18+
import 'package:mweb_client/mweb_client.dart';
19+
import '../../../utilities/logger.dart';
20+
import '../../../utilities/stack_file_system.dart';
21+
import '../test_suite_interface.dart';
22+
import '../testing_models.dart';
23+
24+
class LitecoinMwebIntegrationTestSuite implements TestSuiteInterface {
25+
final StreamController<TestSuiteStatus> _statusController =
26+
StreamController<TestSuiteStatus>.broadcast();
27+
TestSuiteStatus _status = TestSuiteStatus.waiting;
28+
29+
@override
30+
String get displayName => "Litecoin MWEB Integration";
31+
32+
@override
33+
Widget get icon => const Icon(Icons.currency_bitcoin, size: 32);
34+
35+
@override
36+
TestSuiteStatus get status => _status;
37+
38+
@override
39+
Stream<TestSuiteStatus> get statusStream => _statusController.stream;
40+
41+
@override
42+
Future<TestResult> runTests() async {
43+
final stopwatch = Stopwatch()..start();
44+
45+
try {
46+
_updateStatus(TestSuiteStatus.running);
47+
48+
Logging.instance.log(Level.info, "Starting Litecoin MWEB integration test suite...");
49+
50+
await _testMwebdServerCreation();
51+
// await _testMwebdServerStatus();
52+
// await _testMwebClientConnection();
53+
54+
stopwatch.stop();
55+
_updateStatus(TestSuiteStatus.passed);
56+
57+
return TestResult(
58+
success: true,
59+
message: "👍👍 All Litecoin MWEB integration tests passed successfully",
60+
executionTime: stopwatch.elapsed,
61+
);
62+
63+
} catch (e, stackTrace) {
64+
stopwatch.stop();
65+
_updateStatus(TestSuiteStatus.failed);
66+
67+
Logging.instance.log(Level.error,
68+
"Litecoin MWEB integration test suite failed: $e\n$stackTrace"
69+
);
70+
71+
return TestResult(
72+
success: false,
73+
message: "Litecoin MWEB integration tests failed: $e",
74+
executionTime: stopwatch.elapsed,
75+
);
76+
}
77+
}
78+
79+
Future<void> _testMwebdServerCreation() async {
80+
Logging.instance.log(Level.info, "Testing MWEB server creation...");
81+
82+
MwebdServer? server;
83+
try {
84+
// Get a random unused port for testing.
85+
final port = await _getRandomUnusedPort();
86+
if (port == null) {
87+
throw Exception("Could not find an unused port for mwebd test");
88+
}
89+
90+
// Get test data directory.
91+
final dir = await StackFileSystem.applicationMwebdDirectory("testnet");
92+
93+
// Create MwebdServer instance - this tests FFI integration.
94+
server = MwebdServer(
95+
chain: "testnet",
96+
dataDir: dir.path,
97+
peer: "litecoin.stackwallet.com:19335", // testnet peer
98+
proxy: "", // no proxy for test
99+
serverPort: port,
100+
);
101+
102+
// Test server creation - this exercises the FFI bindings.
103+
await server.createServer();
104+
await server.stopServer();
105+
106+
Logging.instance.log(Level.info,
107+
"👍 MWEB server creation test passed"
108+
);
109+
110+
} catch (e) {
111+
throw Exception("MWEB server creation test failed: $e");
112+
} finally {
113+
// Cleanup: stop server if it was created.
114+
if (server != null) {
115+
try {
116+
await server.stopServer();
117+
} catch (e) {
118+
Logging.instance.log(Level.warning, "Failed to stop test server: $e");
119+
}
120+
}
121+
}
122+
}
123+
124+
Future<void> _testMwebdServerStatus() async {
125+
Logging.instance.log(Level.info, "Testing MWEB server status...");
126+
127+
MwebdServer? server;
128+
try {
129+
// Get a random unused port for testing.
130+
final port = await _getRandomUnusedPort();
131+
if (port == null) {
132+
throw Exception("Could not find an unused port for mwebd test");
133+
}
134+
135+
// Get test data directory.
136+
final dir = await StackFileSystem.applicationMwebdDirectory("testnet");
137+
138+
// Create and start MwebdServer.
139+
server = MwebdServer(
140+
chain: "testnet",
141+
dataDir: dir.path,
142+
peer: "litecoin.stackwallet.com:19335", // Testnet peer.
143+
proxy: "", // No proxy for test.
144+
serverPort: port,
145+
);
146+
147+
await server.createServer();
148+
await server.startServer();
149+
150+
// Test getting server status - this tests FFI status calls.
151+
final status = await server.getStatus();
152+
153+
// Verify we got a status response.
154+
if (status.blockHeaderHeight < 0) {
155+
throw Exception("Invalid block header height in status: ${status.blockHeaderHeight}");
156+
}
157+
158+
// Status should have reasonable values (not necessarily synced for test).
159+
if (status.mwebHeaderHeight < 0) {
160+
throw Exception("Invalid MWEB header height in status: ${status.mwebHeaderHeight}");
161+
}
162+
163+
Logging.instance.log(Level.info,
164+
"👍 MWEB server status test passed (blockHeight: ${status.blockHeaderHeight}, mwebHeight: ${status.mwebHeaderHeight})"
165+
);
166+
167+
} catch (e) {
168+
throw Exception("MWEB server status test failed: $e");
169+
} finally {
170+
// Cleanup: stop server if it was created.
171+
if (server != null) {
172+
try {
173+
await server.stopServer();
174+
} catch (e) {
175+
Logging.instance.log(Level.warning, "Failed to stop test server: $e");
176+
}
177+
}
178+
}
179+
}
180+
181+
Future<void> _testMwebClientConnection() async {
182+
Logging.instance.log(Level.info, "Testing MWEB client connection...");
183+
184+
MwebdServer? server;
185+
MwebClient? client;
186+
try {
187+
// Get a random unused port for testing.
188+
final port = await _getRandomUnusedPort();
189+
if (port == null) {
190+
throw Exception("Could not find an unused port for mwebd test");
191+
}
192+
193+
// Get test data directory.
194+
final dir = await StackFileSystem.applicationMwebdDirectory("testnet");
195+
196+
// Create and start MwebdServer.
197+
server = MwebdServer(
198+
chain: "testnet",
199+
dataDir: dir.path,
200+
peer: "litecoin.stackwallet.com:19335", // testnet peer.
201+
proxy: "", // no proxy for test.
202+
serverPort: port,
203+
);
204+
205+
await server.createServer();
206+
await server.startServer();
207+
208+
// Create MwebClient to connect to the server.
209+
client = MwebClient.fromHost("127.0.0.1", port);
210+
211+
// Test basic client operations.
212+
// Generate dummy scan and spend secrets for testing.
213+
final testScanSecret = Uint8List.fromList(
214+
List.generate(32, (index) => index + 1)
215+
);
216+
final testSpendPub = Uint8List.fromList(
217+
List.generate(33, (index) => index + 1)
218+
);
219+
220+
// Test address generation - this tests the client FFI integration.
221+
final mwebAddress = await client.address(
222+
testScanSecret,
223+
testSpendPub,
224+
0, // index
225+
);
226+
227+
// Verify we got a valid MWEB address.
228+
if (mwebAddress.isEmpty) {
229+
throw Exception("Generated MWEB address is empty");
230+
}
231+
232+
// MWEB addresses should start with "ltcmweb" for testnet.
233+
if (!mwebAddress.startsWith("ltcmweb")) {
234+
throw Exception("Generated MWEB address does not have expected prefix: $mwebAddress");
235+
}
236+
237+
Logging.instance.log(Level.info,
238+
"👍 MWEB client connection test passed: $mwebAddress"
239+
);
240+
241+
} catch (e) {
242+
throw Exception("MWEB client connection test failed: $e");
243+
} finally {
244+
// Cleanup.
245+
if (client != null) {
246+
try {
247+
await client.cleanup();
248+
} catch (e) {
249+
Logging.instance.log(Level.warning, "Failed to cleanup test client: $e");
250+
}
251+
}
252+
if (server != null) {
253+
try {
254+
await server.stopServer();
255+
} catch (e) {
256+
Logging.instance.log(Level.warning, "Failed to stop test server: $e");
257+
}
258+
}
259+
}
260+
}
261+
262+
void _updateStatus(TestSuiteStatus newStatus) {
263+
_status = newStatus;
264+
_statusController.add(newStatus);
265+
}
266+
267+
@override
268+
Future<void> cleanup() async {
269+
await _statusController.close();
270+
}
271+
}
272+
273+
// Helper function to get a random unused port.
274+
Future<int?> _getRandomUnusedPort({Set<int> excluded = const {}}) async {
275+
const int minPort = 1024;
276+
const int maxPort = 65535;
277+
const int maxAttempts = 100;
278+
279+
final random = Random.secure();
280+
281+
for (int i = 0; i < maxAttempts; i++) {
282+
final int potentialPort = minPort + random.nextInt(maxPort - minPort + 1);
283+
284+
if (excluded.contains(potentialPort)) {
285+
continue;
286+
}
287+
288+
try {
289+
final serverSocket = await ServerSocket.bind(
290+
InternetAddress.anyIPv4,
291+
potentialPort,
292+
);
293+
await serverSocket.close();
294+
return potentialPort;
295+
} catch (_) {
296+
excluded.add(potentialPort);
297+
continue;
298+
}
299+
}
300+
301+
return null;
302+
}

lib/services/testing/testing_models.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ enum IntegrationTestType implements TestType {
3131
wowneroIntegration,
3232
salviumIntegration,
3333
epiccashIntegration,
34-
firoIntegration;
34+
firoIntegration,
35+
litecoinMwebIntegration;
3536

3637
@override
3738
String get displayName {
@@ -48,6 +49,8 @@ enum IntegrationTestType implements TestType {
4849
return "Epic Cash Integration";
4950
case IntegrationTestType.firoIntegration:
5051
return "Firo Integration";
52+
case IntegrationTestType.litecoinMwebIntegration:
53+
return "Litecoin MWEB Integration";
5154
}
5255
}
5356

@@ -66,6 +69,8 @@ enum IntegrationTestType implements TestType {
6669
return "Tests Epic Cash FFI plugin integration and basic functionality";
6770
case IntegrationTestType.firoIntegration:
6871
return "Tests Firo flutter_libsparkmobile FFI plugin integration and basic functionality";
72+
case IntegrationTestType.litecoinMwebIntegration:
73+
return "Tests Litecoin MWEB flutter_mwebd FFI plugin integration and basic functionality";
6974
}
7075
}
7176
}

lib/services/testing/testing_service.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import 'test_suites/wownero_integration_test_suite.dart';
2121
import 'test_suites/salvium_integration_test_suite.dart';
2222
import 'test_suites/epiccash_integration_test_suite.dart';
2323
import 'test_suites/firo_integration_test_suite.dart';
24+
import 'test_suites/litecoin_mweb_integration_test_suite.dart';
2425

2526
final testingServiceProvider = StateNotifierProvider<TestingService, TestingSessionState>((ref) {
2627
return TestingService();
@@ -58,6 +59,7 @@ class TestingService extends StateNotifier<TestingSessionState> {
5859
_integrationTestSuites[IntegrationTestType.salviumIntegration] = SalviumIntegrationTestSuite();
5960
_integrationTestSuites[IntegrationTestType.epiccashIntegration] = EpiccashIntegrationTestSuite();
6061
_integrationTestSuites[IntegrationTestType.firoIntegration] = FiroIntegrationTestSuite();
62+
_integrationTestSuites[IntegrationTestType.litecoinMwebIntegration] = LitecoinMwebIntegrationTestSuite();
6163
}
6264

6365
void _initializeWalletTestSuites() {

0 commit comments

Comments
 (0)