MotorSignal-App/test/auth_bootstrap_test.dart

157 lines
4.7 KiB
Dart

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<fb.User?> _authStateChangesController = StreamController<fb.User?>.broadcast();
int signInAnonymouslyCallCount = 0;
bool shouldThrowOnSignIn = false;
MockFirebaseAuth(this._currentUser);
@override
fb.User? get currentUser => _currentUser;
@override
Stream<fb.User?> authStateChanges() async* {
yield _currentUser;
yield* _authStateChangesController.stream;
}
@override
Future<fb.UserCredential> 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<fb.UserInfo> 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<Exception>().having((e) => e.toString(), 'msg', contains('Network disconnected')))
);
});
});
}