feat: 切换邮箱认证并重构前后端启动与门禁

This commit is contained in:
qzl
2026-04-02 18:39:35 +08:00
parent 92cdfd9fca
commit 31594558eb
116 changed files with 5608 additions and 628 deletions
@@ -0,0 +1,87 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:meeyao_qianwen/features/auth/data/models/auth_user.dart';
import 'package:meeyao_qianwen/features/auth/data/repositories/auth_repository.dart';
import 'package:meeyao_qianwen/features/auth/presentation/bloc/auth_bloc.dart';
import 'package:meeyao_qianwen/features/auth/presentation/bloc/auth_state.dart';
class _FakeAuthRepository implements AuthRepository {
_FakeAuthRepository({this.recoveredUser, this.throwOnLogout = false});
AuthUser? recoveredUser;
bool clearCalled = false;
bool logoutCalled = false;
bool throwOnLogout;
@override
Future<void> clearLocalSession() async {
clearCalled = true;
}
@override
Future<AuthUser> loginWithEmailOtp({
required String email,
required String otp,
}) async {
return AuthUser(id: 'u1', email: email);
}
@override
Future<void> logout() async {
logoutCalled = true;
if (throwOnLogout) {
throw Exception('logout failed');
}
}
@override
Future<AuthUser?> recoverSession() async {
return recoveredUser;
}
@override
Future<void> sendOtp(String email) async {}
}
void main() {
test('start should become authenticated when recover success', () async {
final repo = _FakeAuthRepository(
recoveredUser: const AuthUser(id: 'u1', email: 'a@b.com'),
);
final bloc = AuthBloc(repository: repo);
await bloc.start();
expect(bloc.state.status, AuthStatus.authenticated);
expect(bloc.state.user?.email, 'a@b.com');
});
test('handleUnauthorized401 should clear local session and unauth', () async {
final repo = _FakeAuthRepository(
recoveredUser: const AuthUser(id: 'u1', email: 'a@b.com'),
);
final bloc = AuthBloc(repository: repo);
await bloc.start();
await bloc.handleUnauthorized401();
expect(repo.clearCalled, isTrue);
expect(bloc.state.status, AuthStatus.unauthenticated);
});
test(
'logout should set unauthenticated even when repository throws',
() async {
final repo = _FakeAuthRepository(
recoveredUser: const AuthUser(id: 'u1', email: 'a@b.com'),
throwOnLogout: true,
);
final bloc = AuthBloc(repository: repo);
await bloc.start();
await expectLater(bloc.logout(), throwsA(isA<Exception>()));
expect(repo.logoutCalled, isTrue);
expect(bloc.state.status, AuthStatus.unauthenticated);
},
);
}
@@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:meeyao_qianwen/core/auth/session_store.dart';
import 'package:meeyao_qianwen/app/app_theme.dart';
import 'package:meeyao_qianwen/data/storage/local_kv_store.dart';
import 'package:meeyao_qianwen/features/home/presentation/screens/home_screen.dart';
import 'package:meeyao_qianwen/l10n/app_localizations.dart';
class _FakeSessionStore extends SessionStore {
_FakeSessionStore({required this.hasReadWelcomeValue})
: super(LocalKvStore());
bool hasReadWelcomeValue;
bool setWelcomeReadCalled = false;
@override
Future<bool> hasReadWelcome() async {
return hasReadWelcomeValue;
}
@override
Future<void> setWelcomeRead(bool value) async {
setWelcomeReadCalled = value;
}
}
void main() {
testWidgets('history cards should use full available width', (tester) async {
final sessionStore = _FakeSessionStore(hasReadWelcomeValue: true);
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: AppLocalizations.supportedLocales,
home: HomeScreen(
account: 'user@example.com',
sessionStore: sessionStore,
onLogout: () async {},
),
),
);
await tester.pumpAndSettle();
final historyCard = find.byType(Card).first;
final cardWidth = tester.getSize(historyCard).width;
final viewportWidth =
tester.view.physicalSize.width / tester.view.devicePixelRatio;
expect(cardWidth, viewportWidth);
});
}