Files

165 lines
5.4 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:sxchnge_auth/sxchnge_auth.dart';
import 'package:sxchnge_kyc/sxchnge_kyc.dart';
import 'package:sxchnge_wallet/sxchnge_wallet.dart';
void main() {
for (final kind in TransferKind.values) {
test(
'${kind.name} requires matching approval before funds change',
() async {
final factor = DummyTwoFactorService(
enabled: true,
delay: Duration.zero,
);
final wallet = DummyWalletService(
DummyIdentityService(delay: Duration.zero),
factors: factor,
delay: Duration.zero,
);
final q = await wallet.quote(
TransferRequest(
kind: kind,
source: 'eur',
amount: 1000,
destination: kind == TransferKind.exchange ? 'usd' : null,
recipient: 'Sample recipient',
payoutDestination: OneTimeDestination(
CryptoDestination(
assetId: 'demo:ethereum:native',
networkId: 'eip155:1',
address: '0x1234567890123456789012345678901234567890',
assetLabel: 'ETH',
networkLabel: 'Ethereum',
),
),
),
);
await expectLater(
wallet.submit(q.id, 'request'),
throwsA(isA<FactorException>()),
);
expect((await wallet.fetch()).accounts.first.available, 7500);
expect((await wallet.fetch()).operations, isEmpty);
final c = (await factor.begin(
FactorPurpose.values.byName(kind.name),
q.id,
))!;
final g = await factor.verify(c.id, '123456');
final result = await wallet.submit(q.id, 'request', approval: g.token);
expect((await wallet.submit(q.id, 'request')).id, result.id);
expect((await wallet.fetch()).operations.length, 1);
},
);
}
test('concurrent retries consume one approval and debit once', () async {
final factor = DummyTwoFactorService(enabled: true, delay: Duration.zero);
final wallet = DummyWalletService(
DummyIdentityService(delay: Duration.zero),
factors: factor,
delay: Duration.zero,
);
final q = await wallet.quote(
const TransferRequest(
kind: TransferKind.payout,
source: 'eur',
amount: 1000,
recipient: 'Sample recipient',
payoutDestination: OneTimeDestination(
CryptoDestination(
assetId: 'demo:ethereum:native',
networkId: 'eip155:1',
address: '0x1234567890123456789012345678901234567890',
assetLabel: 'ETH',
networkLabel: 'Ethereum',
),
),
),
);
final c = (await factor.begin(FactorPurpose.payout, q.id))!;
final grant = await factor.verify(c.id, '123456');
final results = await Future.wait([
wallet.submit(q.id, 'same-request', approval: grant.token),
wallet.submit(q.id, 'same-request', approval: grant.token),
]);
expect(results[0].id, results[1].id);
final snapshot = await wallet.fetch();
expect(snapshot.operations.length, 1);
expect(snapshot.accounts.first.available, 6400);
});
test('proof for another quote cannot authorize payout', () async {
final factor = DummyTwoFactorService(enabled: true, delay: Duration.zero);
final wallet = DummyWalletService(
DummyIdentityService(delay: Duration.zero),
factors: factor,
delay: Duration.zero,
);
final q = await wallet.quote(
const TransferRequest(
kind: TransferKind.payout,
source: 'eur',
amount: 1000,
recipient: 'Sample recipient',
payoutDestination: OneTimeDestination(
CryptoDestination(
assetId: 'demo:ethereum:native',
networkId: 'eip155:1',
address: '0x1234567890123456789012345678901234567890',
assetLabel: 'ETH',
networkLabel: 'Ethereum',
),
),
),
);
final c = (await factor.begin(FactorPurpose.payout, 'different-quote'))!;
final g = await factor.verify(c.id, '123456');
await expectLater(
wallet.submit(q.id, 'request', approval: g.token),
throwsA(isA<FactorException>()),
);
expect((await wallet.fetch()).accounts.first.available, 7500);
});
test(
'quote expired during factor verification does not move money',
() async {
var now = DateTime(2026, 9, 12);
final factor = DummyTwoFactorService(
enabled: true,
delay: Duration.zero,
now: () => now,
);
final wallet = DummyWalletService(
DummyIdentityService(delay: Duration.zero),
factors: factor,
delay: Duration.zero,
now: () => now,
);
final q = await wallet.quote(
const TransferRequest(
kind: TransferKind.exchange,
source: 'eur',
destination: 'usd',
amount: 1000,
),
);
final c = (await factor.begin(FactorPurpose.exchange, q.id))!;
final g = await factor.verify(c.id, '123456');
now = now.add(const Duration(seconds: 61));
await expectLater(
wallet.submit(q.id, 'request', approval: g.token),
throwsA(
isA<WalletException>().having(
(e) => e.code,
'code',
WalletError.expiredQuote,
),
),
);
expect((await wallet.fetch()).accounts.first.available, 7500);
},
);
}