157 lines
4.7 KiB
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')))
|
|
);
|
|
});
|
|
});
|
|
}
|