已完成用户APP首期功能开发

交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
czl231
2026-09-13 00:57:32 +08:00
parent a0a4bb1218
commit 3ef33b531d
793 changed files with 40959 additions and 1204 deletions

View File

@@ -0,0 +1,73 @@
// 功能描述账单金额严格解析、筛选请求和过期会话保护版本1.0.0。
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:user_app/data/repositories/client_repository.dart';
import 'package:user_app/data/services/api_client.dart';
import 'package:user_app/domain/models/wallet_bill.dart';
void main() {
test('钱包缺失或小数金额不能显示为零余额', () async {
for (final data in [
'{}',
'{"balance":1.5,"withdrawal_balance":0}',
'{"balance":0,"withdrawal_balance":1}',
]) {
final repo = ClientRepository(
ApiClient(
() => 't',
client: MockClient((_) async => http.Response('{"code":0,"details":$data}', 200)),
),
);
await expectLater(repo.wallet(), throwsA(isA<ApiException>()));
}
});
final valid = <String, Object?>{
'identity': 'r1',
'direction': 'expense',
'amount': 100,
'fee': 0,
'balance_after': 500,
'created_at': '2026-09-08T08:00:00Z',
};
test('金额和收支不推测、不截断为整数', () {
expect(WalletBill.fromJson(valid).signedAmount, '-¥1.00');
for (final patch in [
{'amount': 1.5},
{'amount': -1},
{'fee': '0'},
{'direction': 'unknown'},
{'created_at': ''},
]) {
expect(() => WalletBill.fromJson({...valid, ...patch}), throwsFormatException);
}
});
test('账单筛选和游标传递,不将错误响应当空列表', () async {
final repo = ClientRepository(
ApiClient(
() => 't',
client: MockClient((r) async {
expect(r.url.queryParameters, {'direction': 'income', 'cursor': '50'});
return http.Response('{"code":0,"details":{}}', 200);
}),
),
);
await expectLater(
repo.walletBills(direction: 'income', cursor: '50'),
throwsA(isA<ApiException>()),
);
});
test('切换账户后迟到的账单不可显示', () async {
var token = 'alice';
final repo = ClientRepository(
ApiClient(
() => token,
client: MockClient((r) async {
token = 'bob';
return http.Response('{"code":0,"details":{"items":[],"next_cursor":""}}', 200);
}),
),
);
await expectLater(repo.walletBills(), throwsA(isA<SessionExpiredException>()));
});
}