@@ -43,6 +43,7 @@ Future<void> main({bool isInitialized = false}) async {
4343
4444 late Wallet receiverWallet;
4545 late Wallet senderWallet;
46+ bool ? previousPayjoinEnabled;
4647
4748 Future <void > consolidateUtxos (String walletId) async {
4849 final utxos = await utxoRepository.getWalletUtxos (walletId: walletId);
@@ -85,29 +86,10 @@ Future<void> main({bool isInitialized = false}) async {
8586 const fixtureSkip =
8687 'requires TEST_ALICE_MNEMONIC and TEST_BOB_MNEMONIC (funded testnet wallets)' ;
8788
88- setUpAll (() async {
89- await locator <SetEnvironmentUsecase >().execute (Environment .testnet);
90- final enabled = await policy.setEnabled (true );
91- if (enabled case Err (: final failure)) throw failure;
92-
93- final receiverSeed = await seedRepository.createFromMnemonic (
94- mnemonicWords: receiverMnemonic! .split (' ' ),
95- );
96- final senderSeed = await seedRepository.createFromMnemonic (
97- mnemonicWords: senderMnemonic! .split (' ' ),
98- );
99- receiverWallet = await walletRepository.createWallet (
100- seed: receiverSeed,
101- network: Network .bitcoinTestnet,
102- scriptType: ScriptType .bip84,
103- );
104- senderWallet = await walletRepository.createWallet (
105- seed: senderSeed,
106- network: Network .bitcoinTestnet,
107- scriptType: ScriptType .bip84,
108- );
109- });
110-
89+ // Belongs to this file's own Bull.init above, not to the fixtures, so it
90+ // stays at the root scope and runs even when the funded group is skipped.
91+ // A no-op under all_test.dart, which calls main(isInitialized: true) and
92+ // therefore leaves payjoinDirectory null.
11193 tearDownAll (() async {
11294 if (payjoinDirectory == null ) return ;
11395 if (locator.isRegistered <PayjoinLifecycle >()) {
@@ -116,10 +98,6 @@ Future<void> main({bool isInitialized = false}) async {
11698 await payjoinDirectory.delete (recursive: true );
11799 });
118100
119- setUp (() async {
120- await walletRepository.getWallets (sync : true );
121- });
122-
123101 test (
124102 'resumes an ongoing Payjoin after an app restart' ,
125103 () {},
@@ -147,111 +125,169 @@ Future<void> main({bool isInitialized = false}) async {
147125 'selection bug' ,
148126 );
149127
150- test (
151- 'funded testnet wallets complete a Payjoin' ,
152- skip: hasFixtures ? null : fixtureSkip,
153- () async {
154- receiverWallet = (await walletRepository.getWallet (receiverWallet.id))! ;
155- senderWallet = (await walletRepository.getWallet (senderWallet.id))! ;
156- expect (receiverWallet.balanceSat, greaterThan (BigInt .zero));
157- expect (senderWallet.balanceSat, greaterThan (BigInt .zero));
158-
159- await consolidateUtxos (receiverWallet.id);
160- await consolidateUtxos (senderWallet.id);
161-
162- final address = await addressRepository.generateNewReceiveAddress (
163- walletId: receiverWallet.id,
164- );
165- final receiverResult = await receiverRole.start (
166- StartPayjoinReceiver (
167- walletId: receiverWallet.id,
168- network: BitcoinNetwork .testnet,
169- address: address.address,
170- amount: Sats .fromInt (10000 ),
171- ),
172- );
173- final receiver = switch (receiverResult) {
174- Ok (: final value) => value,
128+ // The skip has to sit on the group, not on each test: a group whose tests
129+ // are individually skipped still runs its setUpAll and tearDownAll, whereas
130+ // a skipped group runs neither. Verified against package:test 1.31.
131+ //
132+ // That distinction is what made every fork PR red. GitHub withholds secrets
133+ // from pull_request runs on forks, so TEST_ALICE_MNEMONIC/TEST_BOB_MNEMONIC
134+ // expand to the empty string — set but empty. The tests below skipped
135+ // correctly, yet this setUpAll still ran, and createFromMnemonic rejected a
136+ // 1-word mnemonic. Because gen_all_test.dart calls each file's main()
137+ // without wrapping it in a group, the failure landed on the root scope of
138+ // the aggregated suite and took down the whole integration job.
139+ //
140+ // Scoping the hooks to the group also stops them leaking into the other
141+ // seven aggregated files, which previously inherited this setUp and paid a
142+ // full wallet sync before every one of their tests.
143+ group ('funded testnet Payjoin' , () {
144+ setUpAll (() async {
145+ await locator <SetEnvironmentUsecase >().execute (Environment .testnet);
146+ final currentPolicy = await policy.load ();
147+ previousPayjoinEnabled = switch (currentPolicy) {
148+ Ok (: final value) => value.enabled,
175149 Err (: final failure) => throw failure,
176150 };
177- final uri = Uri .parse (receiver.pjUri);
178- expect (uri.scheme, 'bitcoin' );
179- expect (uri.path, address.address);
180- expect (uri.queryParameters, contains ('pj' ));
151+ final enabled = await policy.setEnabled (true );
152+ if (enabled case Err (: final failure)) throw failure;
181153
182- const feeRate = 1000.0 ;
183- final prepared = await prepareBitcoinSend.execute (
184- walletId: senderWallet.id,
185- address: address.address,
186- amountSat: 10000 ,
187- networkFee: NetworkFee .relativeFromSatPerVbyte (feeRate),
154+ final receiverSeed = await seedRepository.createFromMnemonic (
155+ mnemonicWords: receiverMnemonic! .split (' ' ),
188156 );
189- final senderResult = await senderRole.start (
190- StartPayjoinSender (
191- walletId: senderWallet.id,
192- network: BitcoinNetwork .testnet,
193- bip21Uri: receiver.pjUri,
194- unsignedOriginalPsbt: prepared.unsignedPsbt,
195- amount: Sats .fromInt (10000 ),
196- feeRate: FeeRate (feeRate),
197- ),
157+ final senderSeed = await seedRepository.createFromMnemonic (
158+ mnemonicWords: senderMnemonic! .split (' ' ),
198159 );
199- final sender = switch (senderResult) {
200- Ok (: final value) => value,
201- Err (: final failure) => throw failure,
202- };
160+ receiverWallet = await walletRepository.createWallet (
161+ seed: receiverSeed,
162+ network: Network .bitcoinTestnet,
163+ scriptType: ScriptType .bip84,
164+ );
165+ senderWallet = await walletRepository.createWallet (
166+ seed: senderSeed,
167+ network: Network .bitcoinTestnet,
168+ scriptType: ScriptType .bip84,
169+ );
170+ });
203171
204- final completed = await sessions
205- .watch (sessionIds: {sender.id})
206- .where (
207- (result) => switch (result) {
208- Ok (: final value) => value.status == PayjoinStatus .completed,
209- Err () => false ,
210- },
211- )
212- .first
213- .timeout (const Duration (seconds: 300 ));
214- expect (completed, isA <Ok <PayjoinSession , PayjoinFailure >>());
215- },
216- timeout: const Timeout (Duration (seconds: 330 )),
217- );
172+ tearDownAll (() async {
173+ try {
174+ if (previousPayjoinEnabled case final enabled? ) {
175+ final restored = await policy.setEnabled (enabled);
176+ if (restored case Err (: final failure)) throw failure;
177+ }
178+ } finally {
179+ await locator <SetEnvironmentUsecase >().execute (Environment .mainnet);
180+ }
181+ });
218182
219- test (
220- 'a receiver expires when no sender submits a request' ,
221- skip: hasFixtures ? null : fixtureSkip,
222- () async {
223- final address = await addressRepository.generateNewReceiveAddress (
224- walletId: receiverWallet.id,
225- );
226- final receiverResult = await receiverRole.start (
227- StartPayjoinReceiver (
183+ setUp (() async {
184+ await walletRepository.getWallets (sync : true );
185+ });
186+
187+ test (
188+ 'funded testnet wallets complete a Payjoin' ,
189+ () async {
190+ receiverWallet = (await walletRepository.getWallet (receiverWallet.id))! ;
191+ senderWallet = (await walletRepository.getWallet (senderWallet.id))! ;
192+ expect (receiverWallet.balanceSat, greaterThan (BigInt .zero));
193+ expect (senderWallet.balanceSat, greaterThan (BigInt .zero));
194+
195+ await consolidateUtxos (receiverWallet.id);
196+ await consolidateUtxos (senderWallet.id);
197+
198+ final address = await addressRepository.generateNewReceiveAddress (
228199 walletId: receiverWallet.id,
229- network: BitcoinNetwork .testnet,
200+ );
201+ final receiverResult = await receiverRole.start (
202+ StartPayjoinReceiver (
203+ walletId: receiverWallet.id,
204+ network: BitcoinNetwork .testnet,
205+ address: address.address,
206+ amount: Sats .fromInt (10000 ),
207+ ),
208+ );
209+ final receiver = switch (receiverResult) {
210+ Ok (: final value) => value,
211+ Err (: final failure) => throw failure,
212+ };
213+ final uri = Uri .parse (receiver.pjUri);
214+ expect (uri.scheme, 'bitcoin' );
215+ expect (uri.path, address.address);
216+ expect (uri.queryParameters, contains ('pj' ));
217+
218+ const feeRate = 1000.0 ;
219+ final prepared = await prepareBitcoinSend.execute (
220+ walletId: senderWallet.id,
230221 address: address.address,
231- amount: Sats .fromInt (10000 ),
232- expiresAt: DateTime .now ().add (
233- PayjoinPolicy .minimumSessionLifetime + const Duration (seconds: 1 ),
222+ amountSat: 10000 ,
223+ networkFee: NetworkFee .relativeFromSatPerVbyte (feeRate),
224+ );
225+ final senderResult = await senderRole.start (
226+ StartPayjoinSender (
227+ walletId: senderWallet.id,
228+ network: BitcoinNetwork .testnet,
229+ bip21Uri: receiver.pjUri,
230+ unsignedOriginalPsbt: prepared.unsignedPsbt,
231+ amount: Sats .fromInt (10000 ),
232+ feeRate: FeeRate (feeRate),
234233 ),
235- ),
236- );
237- final receiver = switch (receiverResult) {
238- Ok (: final value) => value,
239- Err (: final failure) => throw failure,
240- };
234+ );
235+ final sender = switch (senderResult) {
236+ Ok (: final value) => value,
237+ Err (: final failure) => throw failure,
238+ };
241239
242- final expired = await sessions
243- .watch (sessionIds: {receiver.id})
244- .where (
245- (result) => switch (result) {
246- Ok (: final value) => value.status == PayjoinStatus .expired,
247- Err () => false ,
248- },
249- )
250- .first
251- .timeout (const Duration (seconds: 240 ));
240+ final completed = await sessions
241+ .watch (sessionIds: {sender.id})
242+ .where (
243+ (result) => switch (result) {
244+ Ok (: final value) => value.status == PayjoinStatus .completed,
245+ Err () => false ,
246+ },
247+ )
248+ .first
249+ .timeout (const Duration (seconds: 300 ));
250+ expect (completed, isA <Ok <PayjoinSession , PayjoinFailure >>());
251+ },
252+ timeout: const Timeout (Duration (seconds: 330 )),
253+ );
252254
253- expect (expired, isA <Ok <PayjoinSession , PayjoinFailure >>());
254- },
255- timeout: const Timeout (Duration (seconds: 270 )),
256- );
255+ test (
256+ 'a receiver expires when no sender submits a request' ,
257+ () async {
258+ final address = await addressRepository.generateNewReceiveAddress (
259+ walletId: receiverWallet.id,
260+ );
261+ final receiverResult = await receiverRole.start (
262+ StartPayjoinReceiver (
263+ walletId: receiverWallet.id,
264+ network: BitcoinNetwork .testnet,
265+ address: address.address,
266+ amount: Sats .fromInt (10000 ),
267+ expiresAt: DateTime .now ().add (
268+ PayjoinPolicy .minimumSessionLifetime + const Duration (seconds: 1 ),
269+ ),
270+ ),
271+ );
272+ final receiver = switch (receiverResult) {
273+ Ok (: final value) => value,
274+ Err (: final failure) => throw failure,
275+ };
276+
277+ final expired = await sessions
278+ .watch (sessionIds: {receiver.id})
279+ .where (
280+ (result) => switch (result) {
281+ Ok (: final value) => value.status == PayjoinStatus .expired,
282+ Err () => false ,
283+ },
284+ )
285+ .first
286+ .timeout (const Duration (seconds: 240 ));
287+
288+ expect (expired, isA <Ok <PayjoinSession , PayjoinFailure >>());
289+ },
290+ timeout: const Timeout (Duration (seconds: 270 )),
291+ );
292+ }, skip: hasFixtures ? null : fixtureSkip);
257293}
0 commit comments