Add shared authentication and email verification services
Provide reusable models, injectable service contracts, lifecycle-safe providers and explicit dummy implementations for login, signup and verification. Add focused error and async lifecycle tests. Related to #4 and #5.
This commit is contained in:
1 parent
d9318053e8
commit
27424ed886
15 files changed
+708
No files matched your search
@@ -0,0 +1,11 @@
|
||||
import '../models/auth.dart';
|
||||
|
||||
/// Production adapters own transport, DTO mapping, and authentication effects.
|
||||
/// Providers and widgets only consume domain results and typed failures.
|
||||
abstract interface class LoginService {
|
||||
Future<AuthResult> login(LoginRequest request);
|
||||
}
|
||||
|
||||
abstract interface class SignupService {
|
||||
Future<AuthResult> signup(SignupRequest request);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import '../models/auth.dart';
|
||||
import 'contracts.dart';
|
||||
|
||||
/// Deterministic local simulation. No network, storage, credentials or sessions
|
||||
/// are retained. Configure failures explicitly rather than using magic emails.
|
||||
class DummyLoginService implements LoginService {
|
||||
const DummyLoginService({
|
||||
this.delay = const Duration(milliseconds: 600),
|
||||
this.failure,
|
||||
});
|
||||
final Duration delay;
|
||||
final AuthError? failure;
|
||||
|
||||
@override
|
||||
Future<AuthResult> login(LoginRequest request) async {
|
||||
await Future<void>.delayed(delay);
|
||||
if (failure case final error?) throw AuthException(error);
|
||||
return const AuthResult(userId: 'demo-user', isSimulated: true);
|
||||
}
|
||||
}
|
||||
|
||||
class DummySignupService implements SignupService {
|
||||
const DummySignupService({
|
||||
this.delay = const Duration(milliseconds: 600),
|
||||
this.failure,
|
||||
});
|
||||
final Duration delay;
|
||||
final AuthError? failure;
|
||||
|
||||
@override
|
||||
Future<AuthResult> signup(SignupRequest request) async {
|
||||
await Future<void>.delayed(delay);
|
||||
if (failure case final error?) throw AuthException(error);
|
||||
return const AuthResult(userId: 'demo-user', isSimulated: true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import '../models/verification.dart';
|
||||
|
||||
abstract interface class VerificationService {
|
||||
Future<VerificationResult> send({
|
||||
required String email,
|
||||
required String locale,
|
||||
});
|
||||
Future<VerificationResult> confirm({required String token});
|
||||
}
|
||||
|
||||
/// Explicit preview tokens only. Never use this implementation in production.
|
||||
class DummyVerificationService implements VerificationService {
|
||||
const DummyVerificationService({
|
||||
this.delay = const Duration(milliseconds: 600),
|
||||
this.sendFailure,
|
||||
});
|
||||
final Duration delay;
|
||||
final VerificationError? sendFailure;
|
||||
|
||||
@override
|
||||
Future<VerificationResult> send({
|
||||
required String email,
|
||||
required String locale,
|
||||
}) async {
|
||||
await Future<void>.delayed(delay);
|
||||
if (sendFailure case final error?) throw VerificationException(error);
|
||||
return const VerificationResult(isSimulated: true);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<VerificationResult> confirm({required String token}) async {
|
||||
await Future<void>.delayed(delay);
|
||||
switch (token) {
|
||||
case 'demo-valid':
|
||||
return const VerificationResult(isSimulated: true);
|
||||
case 'demo-expired':
|
||||
throw const VerificationException(VerificationError.expired);
|
||||
case 'demo-internal':
|
||||
throw const VerificationException(VerificationError.internal);
|
||||
default:
|
||||
throw const VerificationException(VerificationError.invalid);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user