Add transfer tracking and secure admin access
This commit is contained in:
@@ -65,6 +65,32 @@ void main() {
|
||||
expect(ledger.transactionCount, 9);
|
||||
});
|
||||
|
||||
test('transfer DTO uses direction for effective income and expense', () {
|
||||
TxItem transfer(String direction) => TxItem.fromJson({
|
||||
'id': direction == 'in' ? 8 : 9,
|
||||
'ledgerId': 3,
|
||||
'categoryId': direction == 'in' ? 101 : 1,
|
||||
'categoryName': direction == 'in' ? '工资' : '餐饮',
|
||||
'categoryIcon': direction == 'in' ? 'money' : 'food',
|
||||
'type': 'transfer',
|
||||
'transferDirection': direction,
|
||||
'counterparty': '张三',
|
||||
'amount': 20,
|
||||
'source': 'accessibility',
|
||||
'occurredAt': '2026-07-25T03:00:00Z',
|
||||
});
|
||||
|
||||
final transferIn = transfer('in');
|
||||
final transferOut = transfer('out');
|
||||
expect(transferIn.isIncome, isTrue);
|
||||
expect(transferIn.isExpense, isFalse);
|
||||
expect(transferIn.typeLabel, '转入');
|
||||
expect(transferOut.isExpense, isTrue);
|
||||
expect(transferOut.isIncome, isFalse);
|
||||
expect(transferOut.typeLabel, '转出');
|
||||
expect(transferOut.counterparty, '张三');
|
||||
});
|
||||
|
||||
test('budget refinement DTO keeps conversation summary and constraints', () {
|
||||
final recommendation = BudgetRecommendations.fromJson({
|
||||
'year': 2026,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user