Files
jizhi/frontend/test/local_transfer_database_test.dart
T

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);
});
}