import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:firebase_auth/firebase_auth.dart' as fb; import 'package:motorcycle_dash/services/auth/auth_service.dart'; class MockFirebaseAuth implements fb.FirebaseAuth { fb.User? _currentUser; final StreamController _authStateChangesController = StreamController.broadcast(); int signInAnonymouslyCallCount = 0; bool shouldThrowOnSignIn = false; MockFirebaseAuth(this._currentUser); @override fb.User? get currentUser => _currentUser; @override Stream authStateChanges() async* { yield _currentUser; yield* _authStateChangesController.stream; } @override Future signInAnonymously() async { signInAnonymouslyCallCount++; if (shouldThrowOnSignIn) { throw fb.FirebaseAuthException(code: 'network-error', message: 'Network disconnected'); } await Future.delayed(const Duration(milliseconds: 50)); final user = MockUser('new_anon', true, null); _currentUser = user; _authStateChangesController.add(user); return MockUserCredential(user); } @override dynamic noSuchMethod(Invocation invocation) { if (invocation.isGetter) return null; return super.noSuchMethod(invocation); } } class MockUser implements fb.User { @override final String uid; @override final bool isAnonymous; @override final String? email; @override final bool emailVerified = false; @override final fb.UserMetadata metadata; @override final List providerData = const []; MockUser(this.uid, this.isAnonymous, this.email) : metadata = MockUserMetadata(); @override dynamic noSuchMethod(Invocation invocation) { if (invocation.isGetter) return null; return super.noSuchMethod(invocation); } } class MockUserMetadata implements fb.UserMetadata { @override DateTime? get creationTime => DateTime.now(); @override DateTime? get lastSignInTime => DateTime.now(); @override dynamic noSuchMethod(Invocation invocation) { if (invocation.isGetter) return null; return super.noSuchMethod(invocation); } } class MockUserCredential implements fb.UserCredential { @override final fb.User? user; MockUserCredential(this.user); @override dynamic noSuchMethod(Invocation invocation) { if (invocation.isGetter) return null; return super.noSuchMethod(invocation); } } void main() { group('AuthService ensureBackendIdentity Tests', () { test('current registered user -> reused, no anonymous sign-in', () async { final fakeUser = MockUser('reg_123', false, 'test@example.com'); final fakeAuth = MockFirebaseAuth(fakeUser); final service = FirebaseAuthService(firebaseAuth: fakeAuth); await Future.delayed(Duration.zero); // allow init to run await service.ensureBackendIdentity(); expect(fakeAuth.signInAnonymouslyCallCount, 0); expect(service.currentUser?.isAnonymous, false); expect(service.currentUser?.uid, 'reg_123'); }); test('current anonymous user -> reused', () async { final fakeUser = MockUser('anon_123', true, null); final fakeAuth = MockFirebaseAuth(fakeUser); final service = FirebaseAuthService(firebaseAuth: fakeAuth); await Future.delayed(Duration.zero); await service.ensureBackendIdentity(); expect(fakeAuth.signInAnonymouslyCallCount, 0); expect(service.currentUser?.isAnonymous, true); }); test('null user -> one anonymous sign-in', () async { final fakeAuth = MockFirebaseAuth(null); final service = FirebaseAuthService(firebaseAuth: fakeAuth); await Future.delayed(Duration.zero); await service.ensureBackendIdentity(); expect(fakeAuth.signInAnonymouslyCallCount, 1); expect(service.currentState.user?.uid, 'new_anon'); }); test('two simultaneous requests -> only one anonymous sign-in', () async { final fakeAuth = MockFirebaseAuth(null); final service = FirebaseAuthService(firebaseAuth: fakeAuth); await Future.delayed(Duration.zero); final future1 = service.ensureBackendIdentity(); final future2 = service.ensureBackendIdentity(); await Future.wait([future1, future2]); expect(fakeAuth.signInAnonymouslyCallCount, 1); expect(service.currentState.user?.uid, 'new_anon'); }); test('anonymous sign-in failure -> explicit provider/auth error', () async { final fakeAuth = MockFirebaseAuth(null); fakeAuth.shouldThrowOnSignIn = true; final service = FirebaseAuthService(firebaseAuth: fakeAuth); await Future.delayed(Duration.zero); expect( () => service.ensureBackendIdentity(), throwsA(isA().having((e) => e.toString(), 'msg', contains('Network disconnected'))) ); }); }); }