import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:drift/native.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:motorcycle_dash/database/database.dart'; import 'package:motorcycle_dash/features/settings/account_screen.dart'; import 'package:motorcycle_dash/services/auth/auth_providers.dart'; import 'package:motorcycle_dash/services/auth/auth_service.dart'; import 'package:motorcycle_dash/services/auth/auth_state.dart'; import 'package:motorcycle_dash/services/auth/auth_user.dart'; import 'package:motorcycle_dash/services/auth/account_lifecycle_service.dart'; import 'package:motorcycle_dash/services/cloud/account_cloud_cleanup_service.dart'; import 'package:motorcycle_dash/services/sync/sync_providers.dart'; import 'package:motorcycle_dash/features/records/records_service.dart'; import 'package:shared_preferences/shared_preferences.dart'; class _FakeAuth implements AuthService { @override Future ensureBackendIdentity() async {} final _states = StreamController.broadcast(); AuthUser? user = const AuthUser(uid: 'delete-test', email: 'delete@test.com', providerId: 'password', emailVerified: true); bool reauthenticated = false; bool deleted = false; Object? reauthError; @override Stream get authStateChanges => _states.stream; @override AuthState get currentState => user == null ? const AuthState.unauthenticated(isFirebaseConfigured: true) : AuthState.authenticated(user!, isFirebaseConfigured: true); @override AuthUser? get currentUser => user; @override bool get isConfigured => true; @override bool get isEmailVerified => true; @override Future reauthenticate({String? password}) async { if (reauthError != null) throw reauthError!; reauthenticated = true; } @override Future deleteCurrentUser() async { deleted = true; user = null; _states.add(currentState); } @override Future getIdToken([bool forceRefresh = false]) async => 'test-token'; @override Future signInWithEmailAndPassword(String email, String password) async {} @override Future createUserWithEmailAndPassword(String email, String password, {String? displayName}) async {} @override Future signInWithGoogle() async {} @override Future signInAnonymously() async {} @override Future sendPasswordResetEmail(String email) async {} @override Future sendEmailVerification() async {} @override Future reloadCurrentUser() async {} @override Future signOut() async {} @override void dispose() => _states.close(); } class _FakeCloud implements CloudAccountCleanup { bool called = false; Object? error; @override Future deleteCloudData() async { called = true; if (error != null) throw error!; } } Future _pumpAccountScreen( WidgetTester tester, _FakeAuth auth, _FakeCloud cloud, ) async { SharedPreferences.setMockInitialValues({}); final prefs = await SharedPreferences.getInstance(); final db = AppDatabase.forTesting(NativeDatabase.memory()); await tester.pumpWidget( ProviderScope( overrides: [ sharedPreferencesProvider.overrideWithValue(prefs), databaseProvider.overrideWithValue(db), authServiceProvider.overrideWithValue(auth), accountLifecycleServiceProvider.overrideWithValue( AccountLifecycleService(auth: auth, cloud: cloud, db: db), ), ], child: const MaterialApp(home: AccountScreen()), ), ); await tester.pumpAndSettle(); return db; } void main() { testWidgets( 'delete account uses one password dialog without lifecycle assertion', (tester) async { final auth = _FakeAuth(); final cloud = _FakeCloud(); final db = await _pumpAccountScreen(tester, auth, cloud); addTearDown(() async { auth.dispose(); await db.close(); }); expect(find.text('DELETE ACCOUNT'), findsOneWidget); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pump(); expect(find.text('Delete Account'), findsOneWidget); expect(find.byType(AlertDialog), findsOneWidget); await tester.enterText(find.byType(TextField), 'test-password'); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pump(); await tester.pump(); await tester.pumpAndSettle(); expect(auth.reauthenticated, isTrue); expect(cloud.called, isTrue); expect(auth.deleted, isTrue); expect(find.text('Account & Cloud Identity'), findsOneWidget); expect(find.text('SIGN IN TO MOTORCLOUD'), findsOneWidget); expect(find.text('Account deleted. Local rides remain on this device.'), findsNothing); expect(find.text('Confirm Your Password'), findsNothing); expect(tester.takeException(), isNull); }, ); testWidgets('canceling account deletion performs no lifecycle work', (tester) async { final auth = _FakeAuth(); final cloud = _FakeCloud(); final db = await _pumpAccountScreen(tester, auth, cloud); addTearDown(() async { auth.dispose(); await db.close(); }); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pump(); await tester.tap(find.text('CANCEL').last); await tester.pumpAndSettle(); expect(auth.reauthenticated, isFalse); expect(auth.deleted, isFalse); expect(cloud.called, isFalse); expect(find.byType(AlertDialog), findsNothing); expect(tester.takeException(), isNull); }); testWidgets('failed password reauthentication stays lifecycle safe', (tester) async { final auth = _FakeAuth()..reauthError = StateError('invalid password'); final cloud = _FakeCloud(); final db = await _pumpAccountScreen(tester, auth, cloud); addTearDown(() async { auth.dispose(); await db.close(); }); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pump(); await tester.enterText(find.byType(TextField), 'wrong-password'); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pumpAndSettle(); expect(auth.reauthenticated, isFalse); expect(auth.deleted, isFalse); expect(cloud.called, isFalse); expect( find.text('Account deletion could not be completed. Your account and local rides are unchanged.'), findsOneWidget, ); expect(tester.takeException(), isNull); }); testWidgets('cloud cleanup failure stays controlled and preserves session', (tester) async { final auth = _FakeAuth(); final cloud = _FakeCloud() ..error = FirebaseException( plugin: 'cloud_firestore', code: 'permission-denied', message: 'cleanup unavailable', ); final db = await _pumpAccountScreen(tester, auth, cloud); addTearDown(() async { auth.dispose(); await db.close(); }); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pump(); await tester.enterText(find.byType(TextField), 'test-password'); await tester.tap(find.text('DELETE ACCOUNT').last); await tester.pumpAndSettle(); expect(auth.reauthenticated, isTrue); expect(auth.deleted, isFalse); expect(cloud.called, isTrue); expect(auth.currentUser, isNotNull); expect( find.text('Account deletion could not be completed. Your account and local rides are unchanged.'), findsOneWidget, ); expect(tester.takeException(), isNull); }); }