feat(auth): transition from email to phone-based OTP authentication
- Replace Email+Password login with Phone+OTP flow - Remove RegisterCubit and registration screens (email verification) - Remove ResetPasswordCubit and reset password screens - Add phone normalization and international dial code support - Update LoginCubit with sendCode/resend cooldown logic - Add new widgets: phone prefix selector, confirm sheet - Update all auth API endpoints: /otp/send, /phone-session - Update form inputs: Email -> Phone with E.164 validation - Update tests for new auth flow
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:fake_async/fake_async.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:formz/formz.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
@@ -26,17 +27,68 @@ void main() {
|
||||
});
|
||||
|
||||
blocTest<LoginCubit, LoginState>(
|
||||
'emailChanged updates email',
|
||||
'phoneChanged updates phone',
|
||||
build: () => cubit,
|
||||
act: (c) => c.emailChanged('test@example.com'),
|
||||
act: (c) => c.phoneChanged('+8613812345678'),
|
||||
expect: () => [isA<LoginState>()],
|
||||
);
|
||||
|
||||
blocTest<LoginCubit, LoginState>(
|
||||
'passwordChanged updates password',
|
||||
'codeChanged updates code',
|
||||
build: () => cubit,
|
||||
act: (c) => c.passwordChanged('password123'),
|
||||
act: (c) => c.codeChanged('123456'),
|
||||
expect: () => [isA<LoginState>()],
|
||||
);
|
||||
|
||||
test('sendCode success starts 60s cooldown', () {
|
||||
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
||||
|
||||
fakeAsync((async) {
|
||||
cubit.phoneChanged('13812345678');
|
||||
|
||||
cubit.sendCode();
|
||||
async.flushMicrotasks();
|
||||
|
||||
expect(cubit.state.resendCooldownSeconds, 60);
|
||||
|
||||
async.elapse(const Duration(seconds: 1));
|
||||
expect(cubit.state.resendCooldownSeconds, 59);
|
||||
|
||||
async.elapse(const Duration(seconds: 59));
|
||||
expect(cubit.state.resendCooldownSeconds, 0);
|
||||
});
|
||||
});
|
||||
|
||||
test('sendCode is blocked during cooldown', () async {
|
||||
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
||||
cubit.phoneChanged('13812345678');
|
||||
|
||||
final first = await cubit.sendCode();
|
||||
final second = await cubit.sendCode();
|
||||
|
||||
expect(first, isTrue);
|
||||
expect(second, isFalse);
|
||||
verify(() => mockRepository.sendOtp(any())).called(1);
|
||||
});
|
||||
|
||||
test('phone change resets cooldown and code state', () {
|
||||
when(() => mockRepository.sendOtp(any())).thenAnswer((_) async {});
|
||||
|
||||
fakeAsync((async) {
|
||||
cubit.phoneChanged('13812345678');
|
||||
cubit.codeChanged('123456');
|
||||
cubit.sendCode();
|
||||
async.flushMicrotasks();
|
||||
|
||||
expect(cubit.state.resendCooldownSeconds, 60);
|
||||
expect(cubit.state.codeSent, isTrue);
|
||||
|
||||
cubit.phoneChanged('14155552671');
|
||||
|
||||
expect(cubit.state.resendCooldownSeconds, 0);
|
||||
expect(cubit.state.codeSent, isFalse);
|
||||
expect(cubit.state.code.value, '');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user