111 lines
3.4 KiB
Dart
111 lines
3.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:miaoji_zhang/shared/services/local_database.dart';
|
|
|
|
void main() {
|
|
test('本地转账按方向统计并按稳定身份幂等', () {
|
|
final database = LocalDatabase.inMemoryForTesting();
|
|
addTearDown(database.close);
|
|
database.upsertBudget(2026, 7, 1, null, 100, false);
|
|
database.upsertBudget(2026, 7, 1, 1, 80, false);
|
|
|
|
final transferOut = <String, dynamic>{
|
|
'ledgerId': 1,
|
|
'categoryId': 1,
|
|
'type': 'transfer',
|
|
'transferDirection': 'out',
|
|
'counterparty': '张三',
|
|
'amount': 40,
|
|
'occurredAt': '2026-07-25T03:00:00.000Z',
|
|
'source': 'accessibility',
|
|
'clientRequestId': 'wx-event-out-a',
|
|
'provider': 'wechat',
|
|
'providerTransactionId': 'wx-order-out-001',
|
|
'recognitionOccurrenceId': 'wx-occurrence-out-a',
|
|
};
|
|
final createdOut = database.createTransaction(transferOut);
|
|
final providerRetry = database.createTransaction({
|
|
...transferOut,
|
|
'clientRequestId': 'wx-event-out-b',
|
|
'recognitionOccurrenceId': 'wx-occurrence-out-b',
|
|
});
|
|
expect(providerRetry['id'], createdOut['id']);
|
|
|
|
final transferIn = <String, dynamic>{
|
|
'ledgerId': 1,
|
|
'categoryId': 101,
|
|
'type': 'transfer',
|
|
'transferDirection': 'in',
|
|
'counterparty': '张三',
|
|
'amount': 75,
|
|
'occurredAt': '2026-07-25T03:05:00.000Z',
|
|
'source': 'accessibility',
|
|
'clientRequestId': 'wx-event-in-a',
|
|
'recognitionOccurrenceId': 'wx-occurrence-in-001',
|
|
};
|
|
final createdIn = database.createTransaction(transferIn);
|
|
final occurrenceRetry = database.createTransaction({
|
|
...transferIn,
|
|
'clientRequestId': 'wx-event-in-b',
|
|
});
|
|
expect(occurrenceRetry['id'], createdIn['id']);
|
|
|
|
final month = database.monthSummary(2026, 7, 1);
|
|
expect(month['count'], 2);
|
|
expect(month['expense'], 40);
|
|
expect(month['income'], 75);
|
|
expect(month['balance'], 35);
|
|
|
|
final stats = database.periodStats('month', DateTime(2026, 7, 25), 1);
|
|
expect(stats['totalExpense'], 40);
|
|
expect(stats['totalIncome'], 75);
|
|
|
|
final budgets = database.budgets(2026, 7, 1);
|
|
expect((budgets['total'] as Map<String, dynamic>)['spent'], 40);
|
|
expect(
|
|
((budgets['categories'] as List).single as Map<String, dynamic>)['spent'],
|
|
40,
|
|
);
|
|
});
|
|
|
|
test('本地数据库拒绝缺少方向的转账', () {
|
|
final database = LocalDatabase.inMemoryForTesting();
|
|
addTearDown(database.close);
|
|
|
|
expect(
|
|
() => database.createTransaction({
|
|
'ledgerId': 1,
|
|
'categoryId': 1,
|
|
'type': 'transfer',
|
|
'amount': 20,
|
|
'occurredAt': '2026-07-25T03:00:00.000Z',
|
|
}),
|
|
throwsStateError,
|
|
);
|
|
});
|
|
|
|
test('云端回写失败时保留待同步的本地账单', () {
|
|
final database = LocalDatabase.inMemoryForTesting();
|
|
addTearDown(database.close);
|
|
final local = database.createTransaction({
|
|
'ledgerId': 1,
|
|
'categoryId': 1,
|
|
'type': 'expense',
|
|
'amount': 28,
|
|
'occurredAt': '2026-07-25T03:00:00.000Z',
|
|
'source': 'manual',
|
|
});
|
|
final localId = local['id'] as int;
|
|
|
|
expect(
|
|
() => database.replaceLocalTransaction(localId, {
|
|
...local,
|
|
'id': 42,
|
|
'occurredAt': 'invalid-time',
|
|
}),
|
|
throwsFormatException,
|
|
);
|
|
expect(database.transaction(localId), isNotNull);
|
|
expect(database.transaction(42), isNull);
|
|
});
|
|
}
|