Skip to content

Commit 442d467

Browse files
committed
test(payjoin): isolate funded fixture environment
1 parent 3a50795 commit 442d467

1 file changed

Lines changed: 158 additions & 122 deletions

File tree

integration_test/payjoin_test.dart

Lines changed: 158 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)