mobile: normalize login account input
This commit is contained in:
@@ -114,6 +114,9 @@ Created on 2026-04-18.
|
||||
- artifact bundle: `mobile/qa-artifacts/real-device-smoke/2026-05-25T21-26-28-369/`
|
||||
- current wireless-debug serial rotated again to `192.168.3.93:38491`; the first rerun hit `adb` `offline`, so the reconnect/smoke helpers were hardened to wait for the serial to reach `device` before continuing
|
||||
- the latest `documents-workspace` retry on `192.168.3.93:38491` now exits cleanly with `SMOKE_BLOCKER=wireless_adb_unavailable` instead of an app-surface misfire, which makes the remaining issue explicit
|
||||
- the next wireless-debug serial was `192.168.3.93:45167`; the app relaunched into the login form and surfaced that the Chinese IME entered the email domain separator as `。`, so mobile login now normalizes full-width account punctuation before calling the API
|
||||
- after installing the rebuilt APK, `tools/real-device-smoke.ps1 -DeviceSerial 192.168.3.93:45167 -ConfigureDevice -SmokeDepth documents-workspace -LoginAccount <member-account> -LoginPassword <member-password>` returned `SMOKE_OK=True` with `DOCUMENTS_WORKSPACE_READY=True`, `DOCUMENTS_TYPE_LIST_OK=True`, and `DOCUMENTS_BUILDER_OK=True`
|
||||
- artifact bundle: `mobile/qa-artifacts/real-device-smoke/2026-05-25T23-44-09-374/`
|
||||
|
||||
## Phase 0/1 mobile structure progress
|
||||
|
||||
|
||||
@@ -15,11 +15,14 @@ class AuthController {
|
||||
|
||||
Future<void> login(
|
||||
{required String account, required String password}) async {
|
||||
final normalizedAccount = _normalizeLoginAccount(account);
|
||||
isLoading.value = true;
|
||||
errorMessage.value = null;
|
||||
try {
|
||||
final session =
|
||||
await _apiClient.login(account: account, password: password);
|
||||
final session = await _apiClient.login(
|
||||
account: normalizedAccount,
|
||||
password: password,
|
||||
);
|
||||
final currentUser =
|
||||
await _apiClient.fetchCurrentUser(session.accessToken);
|
||||
await SessionStore.save(_mergeSession(session, currentUser));
|
||||
@@ -50,3 +53,17 @@ class AuthController {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _normalizeLoginAccount(String value) {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return trimmed
|
||||
.replaceAll('\uFF20', '@')
|
||||
.replaceAll('\uFE6B', '@')
|
||||
.replaceAll('\u3002', '.')
|
||||
.replaceAll('\uFF0E', '.')
|
||||
.replaceAll('\uFF61', '.');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:ai_contract_mobile/features/auth/application/auth_controller.dart';
|
||||
import 'package:ai_contract_mobile/features/auth/domain/auth_session.dart';
|
||||
import 'package:ai_contract_mobile/shared/network/api_client.dart';
|
||||
import 'package:ai_contract_mobile/shared/storage/session_store.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
setUp(() {
|
||||
SessionStore.useStorageDriverForTesting(InMemorySessionStorageDriver());
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await SessionStore.clear();
|
||||
SessionStore.resetStorageDriverForTesting();
|
||||
});
|
||||
|
||||
test('normalizes full-width email punctuation before login request',
|
||||
() async {
|
||||
final apiClient = _CapturingLoginApiClient();
|
||||
final controller = AuthController(apiClient: apiClient);
|
||||
|
||||
await controller.login(
|
||||
account: ' cyuqei@gmail。com ',
|
||||
password: '816301',
|
||||
);
|
||||
|
||||
expect(apiClient.account, 'cyuqei@gmail.com');
|
||||
expect(apiClient.password, '816301');
|
||||
expect(SessionStore.current?.username, 'cyuqei@gmail.com');
|
||||
});
|
||||
}
|
||||
|
||||
class _CapturingLoginApiClient extends ApiClient {
|
||||
String? account;
|
||||
String? password;
|
||||
|
||||
@override
|
||||
Future<AuthSession> login({
|
||||
required String account,
|
||||
required String password,
|
||||
}) async {
|
||||
this.account = account;
|
||||
this.password = password;
|
||||
return AuthSession(
|
||||
accessToken: 'token',
|
||||
userId: 'user-1',
|
||||
tenantId: 'tenant-1',
|
||||
username: account,
|
||||
userDisplayName: 'Member',
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthSession> fetchCurrentUser(String accessToken) async {
|
||||
return const AuthSession(
|
||||
accessToken: 'token',
|
||||
userId: 'user-1',
|
||||
tenantId: 'tenant-1',
|
||||
username: 'cyuqei@gmail.com',
|
||||
userDisplayName: 'Member',
|
||||
role: 'member',
|
||||
status: 'active',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ function Invoke-AdbTextEntry {
|
||||
& $script:adb -s $Serial shell input text (ConvertTo-AdbInputText -Value $domainParts[$i]) | Out-Null
|
||||
}
|
||||
if ($i -lt ($domainParts.Length - 1)) {
|
||||
& $script:adb -s $Serial shell input keyevent KEYCODE_PERIOD | Out-Null
|
||||
& $script:adb -s $Serial shell input text . | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user