165 lines
5.4 KiB
Dart
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);
|
|
},
|
|
);
|
|
}
|