迁移 Flutter 端与训练脚本,模型/训练产物移出 git(遵循纯代码约定)
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:observer/auth/auth_screen.dart';
|
||||
import 'package:observer/auth/auth_view_model.dart';
|
||||
import 'package:observer/auth/session_store.dart';
|
||||
import 'package:observer/home/home_screen.dart';
|
||||
import 'package:observer/home/home_view_model.dart';
|
||||
import 'package:observer/payment/license_service.dart';
|
||||
import 'package:observer/payment/models.dart';
|
||||
import 'package:observer/payment/order_api.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
/// 测试环境无 secure storage 平台实现:mock 通道让读写立即返回空。
|
||||
void _mockSecureStorage() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(
|
||||
const MethodChannel('plugins.it_nomads.com/flutter_secure_storage'),
|
||||
(call) async => null,
|
||||
);
|
||||
}
|
||||
|
||||
/// 内存版 SessionStore:不触平台通道(flutter_secure_storage 在测试环境不可用)
|
||||
class FakeSession extends SessionStore {
|
||||
String? token;
|
||||
String? phone;
|
||||
bool cleared = false;
|
||||
|
||||
@override
|
||||
Future<String?> readToken() async => token;
|
||||
@override
|
||||
Future<String?> readPhone() async => phone;
|
||||
@override
|
||||
Future<void> save(String phone, String token) async {
|
||||
this.phone = phone;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
token = null;
|
||||
phone = null;
|
||||
cleared = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// 内存版 OrderApi:登录/注册/授权全部本地返回,不触网络
|
||||
class FakeOrderApi extends OrderApi {
|
||||
final LicenseStatus licenseResult;
|
||||
FakeOrderApi(SessionStore s, {this.licenseResult = const LicenseStatus(active: false)})
|
||||
: super(sessionStore: s);
|
||||
|
||||
@override
|
||||
Future<void> register({required String phone, required String password}) async {}
|
||||
|
||||
@override
|
||||
Future<String> login({required String phone, required String password}) async =>
|
||||
'fake-token';
|
||||
|
||||
@override
|
||||
Future<LicenseStatus> fetchLicense() async => licenseResult;
|
||||
}
|
||||
|
||||
Widget _app(FakeSession session, FakeOrderApi api,
|
||||
{String initialRoute = '/login'}) {
|
||||
final authVm = AuthViewModel(orderApi: api, sessionStore: session);
|
||||
final homeVm = HomeViewModel(
|
||||
licenseService: LicenseService(orderApi: api, sessionStore: session));
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: session),
|
||||
ChangeNotifierProvider.value(value: authVm),
|
||||
ChangeNotifierProvider.value(value: homeVm),
|
||||
],
|
||||
child: MaterialApp(
|
||||
initialRoute: initialRoute,
|
||||
routes: {
|
||||
'/login': (_) => const AuthScreen(),
|
||||
'/home': (_) => const HomeScreen(),
|
||||
'/camera': (_) => const Scaffold(body: Text('camera-stub')),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _enterCredentials(WidgetTester tester, {required bool register}) async {
|
||||
if (register) {
|
||||
await tester.tap(find.text('没有账号?去注册'));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
await tester.enterText(find.widgetWithText(TextField, '手机号'), '13912345678');
|
||||
await tester.enterText(find.widgetWithText(TextField, '密码'), 'pass123456');
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(_mockSecureStorage);
|
||||
|
||||
testWidgets('登录流程:输错密码提示错误,不跳转', (tester) async {
|
||||
final session = FakeSession();
|
||||
final api = FakeOrderApi(session);
|
||||
await tester.pumpWidget(_app(session, api));
|
||||
|
||||
await tester.tap(find.text('登录'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// 本地校验:空手机号提示
|
||||
expect(find.text('请输入手机号和至少 6 位密码'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('注册并登录成功 → 跳转主界面展示未充值', (tester) async {
|
||||
final session = FakeSession();
|
||||
final api = FakeOrderApi(session);
|
||||
await tester.pumpWidget(_app(session, api));
|
||||
|
||||
await _enterCredentials(tester, register: true);
|
||||
await tester.tap(find.text('注册并登录'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(HomeScreen), findsOneWidget);
|
||||
expect(session.token, 'fake-token');
|
||||
expect(session.phone, '13912345678');
|
||||
expect(find.textContaining('未充值'), findsOneWidget);
|
||||
expect(find.text('搜索'), findsOneWidget);
|
||||
expect(find.text('充值'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('搜索按钮强制服务端校验:未授权弹出拦截弹窗', (tester) async {
|
||||
final session = FakeSession()..token = 'fake-token';
|
||||
final api = FakeOrderApi(session); // active: false
|
||||
await tester.pumpWidget(_app(session, api, initialRoute: '/home'));
|
||||
|
||||
await tester.tap(find.text('搜索'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('授权不可用'), findsOneWidget);
|
||||
expect(find.text('去充值'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('搜索按钮校验通过 → 进入相机页', (tester) async {
|
||||
final session = FakeSession()..token = 'fake-token';
|
||||
final api = FakeOrderApi(
|
||||
session,
|
||||
licenseResult: LicenseStatus(
|
||||
active: true,
|
||||
expiresAt: DateTime.now().add(const Duration(days: 1))),
|
||||
);
|
||||
await tester.pumpWidget(_app(session, api, initialRoute: '/home'));
|
||||
|
||||
await tester.tap(find.text('搜索'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('camera-stub'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user