已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
@@ -2,6 +2,17 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:user_app/domain/models/client_models.dart';
|
||||
|
||||
void main() {
|
||||
test('用户资料保留服务端实名认证事实', () {
|
||||
final profile = UserProfile.fromJson(const {
|
||||
'identity': 'user',
|
||||
'name': '张先生',
|
||||
'phone': '13800005678',
|
||||
'avatar': '',
|
||||
'real_name': '张实名',
|
||||
});
|
||||
expect(profile.realName, '张实名');
|
||||
});
|
||||
|
||||
group('moneyText', () {
|
||||
test('formats minor currency units', () {
|
||||
expect(moneyText(12345), '¥123.45');
|
||||
|
||||
39
apps/user_app/test/domain/recharge_draft_test.dart
Normal file
39
apps/user_app/test/domain/recharge_draft_test.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
// 功能描述:充值待确认请求的存储隔离和损坏保护;版本:1.0.0。
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:user_app/data/services/recharge_draft_store.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
const draft = PendingRecharge(
|
||||
request: 'request-1',
|
||||
amount: 29,
|
||||
channel: 'alipay',
|
||||
payType: 'wap',
|
||||
);
|
||||
test('重开存储恢复同一请求,环境和账号分别隔离', () async {
|
||||
FlutterSecureStorage.setMockInitialValues({});
|
||||
final store = SecureRechargeDraftStore();
|
||||
await store.write('api-a#alice', draft);
|
||||
expect((await SecureRechargeDraftStore().read('api-a#alice'))?.request, 'request-1');
|
||||
expect((await store.read('api-a#alice'))?.amount, 29);
|
||||
expect(await store.read('api-a#bob'), isNull);
|
||||
expect(await store.read('api-b#alice'), isNull);
|
||||
await store.delete('api-a#bob');
|
||||
expect(await store.read('api-a#alice'), isNotNull);
|
||||
await store.delete('api-a#alice');
|
||||
expect(await store.read('api-a#alice'), isNull);
|
||||
});
|
||||
test('损坏请求不能静默当作未创建,禁止Mock渠道和无效金额', () {
|
||||
for (final patch in <Map<String, Object?>>[
|
||||
{'schema': 2},
|
||||
{'request': ''},
|
||||
{'amount': 0},
|
||||
{'amount': 1.5},
|
||||
{'channel': 'mock'},
|
||||
{'channel': 'wechat', 'pay_type': 'wap'},
|
||||
]) {
|
||||
expect(() => PendingRecharge.fromJson({...draft.toJson(), ...patch}), throwsFormatException);
|
||||
}
|
||||
});
|
||||
}
|
||||
121
apps/user_app/test/domain/recharge_flow_test.dart
Normal file
121
apps/user_app/test/domain/recharge_flow_test.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
// 功能描述:充值先存后发和未确认订单保留;版本:1.0.0。
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:user_app/data/services/recharge_draft_store.dart';
|
||||
import 'package:user_app/data/services/recharge_flow.dart';
|
||||
import 'package:user_app/data/services/api_client.dart';
|
||||
import 'package:user_app/domain/models/recharge.dart';
|
||||
import '../support/a1_fixture.dart';
|
||||
|
||||
class _Store implements RechargeDraftStore {
|
||||
PendingRecharge? value;
|
||||
bool fail = false;
|
||||
@override
|
||||
Future<PendingRecharge?> read(String owner) async => value;
|
||||
@override
|
||||
Future<void> write(String owner, PendingRecharge draft) async {
|
||||
if (fail) throw StateError('存储失败');
|
||||
value = draft;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> delete(String owner) async {
|
||||
value = null;
|
||||
}
|
||||
}
|
||||
|
||||
class _Repo extends A1FixtureRepository {
|
||||
_Repo(this.store);
|
||||
final _Store store;
|
||||
int calls = 0, status = 10;
|
||||
int? queryError;
|
||||
String owner = 'api#alice';
|
||||
bool changeOwnerOnQuery = false;
|
||||
@override
|
||||
Future<String> rechargeDraftOwner() async => owner;
|
||||
@override
|
||||
Future<Map<String, Object?>> createRecharge({
|
||||
required String request,
|
||||
required int amount,
|
||||
required String channel,
|
||||
required String payType,
|
||||
}) async {
|
||||
expect(store.value?.request, request);
|
||||
calls++;
|
||||
throw StateError('响应丢失');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<RechargeRecord> rechargeResult(String request) async {
|
||||
if (changeOwnerOnQuery) owner = 'api#bob';
|
||||
if (queryError != null) throw ApiException(queryError!, '查询失败');
|
||||
return RechargeRecord(
|
||||
identity: 'order',
|
||||
number: request,
|
||||
amount: 100,
|
||||
status: status,
|
||||
channel: 'alipay',
|
||||
createdAt: DateTime(2026),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('明确不存在可原号重发,网络错误和账号切换不能发单', () async {
|
||||
const draft = PendingRecharge(
|
||||
request: 'request-1',
|
||||
amount: 100,
|
||||
channel: 'alipay',
|
||||
payType: 'wap',
|
||||
);
|
||||
for (final code in [1112, 500]) {
|
||||
final store = _Store()..value = draft;
|
||||
final repo = _Repo(store)..queryError = code;
|
||||
final flow = RechargeFlow(repo, store);
|
||||
await expectLater(
|
||||
flow.create(draft),
|
||||
code == 1112
|
||||
? throwsStateError
|
||||
: throwsA(isA<ApiException>().having((e) => e.code, '错误码', 500)),
|
||||
);
|
||||
expect(repo.calls, code == 1112 ? 1 : 0);
|
||||
expect(store.value?.request, draft.request);
|
||||
}
|
||||
final store = _Store()..value = draft;
|
||||
final repo = _Repo(store)..changeOwnerOnQuery = true;
|
||||
await expectLater(RechargeFlow(repo, store).create(draft), throwsStateError);
|
||||
expect(repo.calls, 0);
|
||||
expect(store.value, isNotNull);
|
||||
});
|
||||
test('存储失败不发单,响应丢失保留原请求,到账后才清除', () async {
|
||||
final store = _Store()..fail = true;
|
||||
final repo = _Repo(store);
|
||||
final flow = RechargeFlow(repo, store);
|
||||
const draft = PendingRecharge(
|
||||
request: 'request-1',
|
||||
amount: 100,
|
||||
channel: 'alipay',
|
||||
payType: 'wap',
|
||||
);
|
||||
await expectLater(flow.create(draft), throwsStateError);
|
||||
expect(repo.calls, 0);
|
||||
store.fail = false;
|
||||
await expectLater(flow.create(draft), throwsStateError);
|
||||
expect(repo.calls, 1);
|
||||
expect(store.value?.request, 'request-1');
|
||||
await expectLater(
|
||||
flow.create(
|
||||
const PendingRecharge(request: 'request-2', amount: 100, channel: 'alipay', payType: 'wap'),
|
||||
),
|
||||
throwsStateError,
|
||||
);
|
||||
expect(repo.calls, 1);
|
||||
expect((await flow.recover())?.credited, isFalse);
|
||||
expect(store.value, isNotNull);
|
||||
repo.status = 23;
|
||||
expect(await flow.create(draft), {'recharge_status': 23});
|
||||
expect(repo.calls, 1);
|
||||
expect(store.value, isNotNull);
|
||||
expect((await flow.recover())?.credited, isTrue);
|
||||
expect(store.value, isNull);
|
||||
});
|
||||
}
|
||||
49
apps/user_app/test/domain/recharge_test.dart
Normal file
49
apps/user_app/test/domain/recharge_test.dart
Normal file
@@ -0,0 +1,49 @@
|
||||
// 功能描述:充值金额精度、协议完整性和到账事实校验;版本:1.0.0。
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:user_app/domain/models/recharge.dart';
|
||||
|
||||
void main() {
|
||||
test('金额按十进制精确转分,拒绝超精度和指数输入', () {
|
||||
expect(parseRechargeAmount('0.29'), 29);
|
||||
expect(parseRechargeAmount(' 100.1 '), 10010);
|
||||
expect(parseRechargeAmount('999999999.99'), 99999999999);
|
||||
for (final value in ['-1', '1e2', '1.001', '.5', '1.', 'NaN', '']) {
|
||||
expect(parseRechargeAmount(value), isNull, reason: value);
|
||||
}
|
||||
});
|
||||
|
||||
test('只有钱包充值完成状态才表示到账', () {
|
||||
RechargeRecord record(int status, int? paymentStatus) => RechargeRecord.fromJson({
|
||||
'identity': 'recharge-1',
|
||||
'amount': 10000,
|
||||
'recharge_status': status,
|
||||
'payment_status': paymentStatus,
|
||||
'created_at': '2026-09-11T00:00:00Z',
|
||||
});
|
||||
expect(record(10, 23).credited, isFalse);
|
||||
expect(record(10, 30).statusText, '支付已关闭,未入账');
|
||||
expect(record(23, null).credited, isTrue);
|
||||
expect(record(23, 30).statusText, '已到账');
|
||||
});
|
||||
|
||||
test('缺少协议允许读取配置,残缺协议必须拒绝', () {
|
||||
final json = <String, Object?>{'min_amount': 1, 'max_amount': 500000, 'channels': <Object>[]};
|
||||
expect(RechargeOptions.fromJson(json).agreement, isNull);
|
||||
for (final identity in ['', ' ']) {
|
||||
expect(
|
||||
() => RechargeOptions.fromJson({
|
||||
...json,
|
||||
'agreement': {
|
||||
'identity': identity,
|
||||
'version': 1,
|
||||
'body': '协议正文',
|
||||
},
|
||||
}),
|
||||
throwsFormatException,
|
||||
);
|
||||
}
|
||||
expect(() => RechargeOptions.fromJson({...json, 'min_amount': 0}), throwsFormatException);
|
||||
expect(() => RechargeOptions.fromJson({...json, 'max_amount': 0}), throwsFormatException);
|
||||
expect(() => rechargeCents(1.5), throwsFormatException);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user