Skip to content

Commit aff07e3

Browse files
committed
Add integration tests for mnemonic and preimage APIs
1 parent c5377a1 commit aff07e3

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
4+
import 'package:dio/dio.dart';
5+
import 'package:flutter/foundation.dart';
6+
import 'package:flutter_test/flutter_test.dart';
7+
import 'package:integration_test/integration_test.dart';
8+
import 'package:ldk_node/ldk_node.dart' as ldk;
9+
import 'package:ldk_node/src/generated/api/builder.dart' as builder;
10+
import 'package:ldk_node/src/generated/frb_generated.dart';
11+
import 'package:path_provider/path_provider.dart';
12+
13+
void main() {
14+
String esploraUrl = Platform.isAndroid
15+
? 'http://10.0.2.2:30000'
16+
: 'http://127.0.0.1:30000';
17+
final regTestClient = BtcClient("");
18+
final esploraConfig = ldk.EsploraSyncConfig(
19+
backgroundSyncConfig: ldk.BackgroundSyncConfig(
20+
onchainWalletSyncIntervalSecs: BigInt.from(60),
21+
lightningWalletSyncIntervalSecs: BigInt.from(60),
22+
feeRateCacheUpdateIntervalSecs: BigInt.from(600)));
23+
24+
Future<ldk.Config> initLdkConfig(
25+
String path, ldk.SocketAddress address) async {
26+
final directory = await getApplicationDocumentsDirectory();
27+
final nodePath = "${directory.path}/ldk_cache/integration/regtest/$path";
28+
final config = ldk.Config(
29+
probingLiquidityLimitMultiplier: BigInt.from(3),
30+
trustedPeers0Conf: [],
31+
storageDirPath: nodePath,
32+
network: ldk.Network.regtest,
33+
listeningAddresses: [address]);
34+
return config;
35+
}
36+
37+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
38+
39+
// Track if FRB has been initialized
40+
bool frbInitialized = false;
41+
42+
Future<void> ensureFrbInitialized() async {
43+
if (!frbInitialized) {
44+
await core.init();
45+
frbInitialized = true;
46+
}
47+
}
48+
49+
group('new_apis_integration', () {
50+
setUp(() async {});
51+
52+
testWidgets('mnemonic_word_count_test', (WidgetTester tester) async {
53+
// Initialize flutter_rust_bridge
54+
await ensureFrbInitialized();
55+
56+
debugPrint("Testing Mnemonic.generateWithWordCount()...");
57+
58+
// Test 12-word mnemonic using the generated FfiMnemonic API
59+
final mnemonic12 = await builder.FfiMnemonic.generateWithWordCount(wordCount: 12);
60+
final words12 = mnemonic12.seedPhrase.split(' ');
61+
debugPrint("Generated 12-word mnemonic: ${mnemonic12.seedPhrase}");
62+
expect(words12.length, equals(12));
63+
64+
// Test 24-word mnemonic
65+
final mnemonic24 = await builder.FfiMnemonic.generateWithWordCount(wordCount: 24);
66+
final words24 = mnemonic24.seedPhrase.split(' ');
67+
debugPrint("Generated 24-word mnemonic: ${mnemonic24.seedPhrase}");
68+
expect(words24.length, equals(24));
69+
70+
debugPrint("Mnemonic word count test completed successfully!");
71+
});
72+
73+
testWidgets('custom_preimage_api_test', (WidgetTester tester) async {
74+
await ensureFrbInitialized();
75+
76+
debugPrint("Testing PaymentPreimage creation and sendWithPreimageUnsafe API availability...");
77+
78+
// Test PaymentPreimage convenience extension
79+
final customPreimage = ldk.PaymentPreimageExtensions.fromBytes([
80+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
81+
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
82+
]);
83+
84+
debugPrint("PaymentPreimage created successfully");
85+
expect(customPreimage.data.inner.length, equals(32));
86+
87+
// Create a node to verify spontaneousPayment().sendWithPreimageUnsafe exists
88+
final aliceConfig = await initLdkConfig(
89+
'alice_preimage_api', const ldk.SocketAddress.hostname(addr: "0.0.0.0", port: 3017));
90+
final aliceBuilder = ldk.Builder.fromConfig(config: aliceConfig)
91+
.setEntropyBip39Mnemonic(
92+
mnemonic: ldk.Mnemonic(
93+
seedPhrase:
94+
"replace force spring cruise nothing select glass erupt medal raise consider pull"))
95+
.setChainSourceEsplora(
96+
esploraServerUrl: esploraUrl, syncConfig: esploraConfig);
97+
final aliceNode = await aliceBuilder.build();
98+
await aliceNode.start();
99+
100+
// Verify the spontaneousPayment handler and sendWithPreimageUnsafe method exist
101+
final spontaneousHandler = await aliceNode.spontaneousPayment();
102+
debugPrint("SpontaneousPayment handler obtained successfully");
103+
104+
// Test that the method exists by checking it's callable (will fail due to no route but that's ok)
105+
final testNodeId = await aliceNode.nodeId(); // Use own node ID for test
106+
try {
107+
await spontaneousHandler.sendWithPreimageUnsafe(
108+
nodeId: testNodeId,
109+
preimage: customPreimage,
110+
amountMsat: BigInt.from(1000),
111+
);
112+
} catch (e) {
113+
// Expected to fail - no route to self, but this proves the API exists
114+
debugPrint("sendWithPreimageUnsafe() API exists - got expected error: $e");
115+
}
116+
117+
debugPrint("Custom preimage API test completed successfully!");
118+
await aliceNode.stop();
119+
});
120+
});
121+
}
122+
123+
class BtcClient {
124+
String rpcUser = "admin1";
125+
String rpcPassword = "123";
126+
int rpcPort = 18443;
127+
128+
Dio? _dioClient;
129+
late Map<String, String> _headers;
130+
late String _url;
131+
final String wallet;
132+
133+
String getConnectionString(String host, int port, String wallet) {
134+
return 'http://$host:$port/wallet/$wallet';
135+
}
136+
137+
BtcClient(this.wallet) {
138+
_headers = {
139+
'Content-Type': 'application/json',
140+
'authorization':
141+
'Basic ${base64.encode(utf8.encode("$rpcUser:$rpcPassword"))}'
142+
};
143+
_url = getConnectionString(
144+
Platform.isAndroid ? "10.0.2.2" : "0.0.0.0", rpcPort, wallet);
145+
_dioClient = Dio();
146+
}
147+
148+
Future<void> loadWallet() async {
149+
try {
150+
var params = [wallet];
151+
await call("loadwallet", params);
152+
} on Exception catch (e) {
153+
if (e.toString().contains("-4")) {
154+
debugPrint(" $wallet already loaded!");
155+
} else if (e.toString().contains("-18")) {
156+
debugPrint("$wallet doesn't exist!");
157+
var params = [wallet];
158+
await call("createwallet", params);
159+
}
160+
}
161+
}
162+
163+
Future<List<dynamic>> generate(int nblocks, String address) async {
164+
var params = [
165+
nblocks,
166+
address,
167+
];
168+
final res = await call("generatetoaddress", params);
169+
return res;
170+
}
171+
172+
Future<String> sendToAddress(String address, int amount) async {
173+
var params = [address, amount];
174+
final res = await call("sendtoaddress", params);
175+
return res;
176+
}
177+
178+
Future<int> getBlockCount() async {
179+
var params = [];
180+
final res = await call("getblockcount", params);
181+
return res;
182+
}
183+
184+
Future<dynamic> call(var methodName, [var params]) async {
185+
var body = {
186+
'jsonrpc': '2.0',
187+
'method': methodName,
188+
'params': params ?? [],
189+
'id': '1'
190+
};
191+
192+
try {
193+
var response = await _dioClient!.post(
194+
_url,
195+
data: body,
196+
options: Options(
197+
headers: _headers,
198+
),
199+
);
200+
if (response.statusCode == HttpStatus.ok) {
201+
var body = response.data as Map<String, dynamic>;
202+
if (body.containsKey('error') && body["error"] != null) {
203+
var error = body['error'];
204+
205+
if (error["message"] is Map<String, dynamic>) {
206+
error = error['message'];
207+
}
208+
209+
throw Exception(
210+
"errorCode: ${error['code']},errorMsg: ${error['message']}",
211+
);
212+
}
213+
return body['result'];
214+
}
215+
} on DioException catch (e) {
216+
if (e.type == DioExceptionType.badResponse) {
217+
var errorResponseBody = e.response!.data;
218+
219+
switch (e.response!.statusCode) {
220+
case 401:
221+
throw Exception(
222+
" code: 401, message: Unauthorized",
223+
);
224+
case 403:
225+
throw Exception(
226+
"code: 403,message: Forbidden",
227+
);
228+
case 404:
229+
if (errorResponseBody['error'] != null) {
230+
var error = errorResponseBody['error'];
231+
throw Exception(
232+
"errorCode: ${error['code']},errorMsg: ${error['message']}",
233+
);
234+
}
235+
throw Exception(
236+
"code: 500, message: Internal Server Error",
237+
);
238+
default:
239+
if (errorResponseBody['error'] != null) {
240+
var error = errorResponseBody['error'];
241+
throw Exception(
242+
"errorCode: ${error['code']},errorMsg: ${error['message']}",
243+
);
244+
}
245+
throw Exception(
246+
"code: 500, message: 'Internal Server Error'",
247+
);
248+
}
249+
} else if (e.type == DioExceptionType.connectionError) {
250+
throw Exception(
251+
"code: 500,message: e.message ?? 'Connection Error'",
252+
);
253+
}
254+
throw Exception(
255+
"code: 500, message: e.message ?? 'Unknown Error'",
256+
);
257+
}
258+
}
259+
}

0 commit comments

Comments
 (0)