178 lines
5.6 KiB
Dart
178 lines
5.6 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:sxchnge_auth/sxchnge_auth.dart';
|
|
|
|
class ControlledService implements LoginService, SignupService {
|
|
final calls = <Completer<AuthResult>>[];
|
|
LoginRequest? loginInput;
|
|
SignupRequest? signupInput;
|
|
Future<AuthResult> next() {
|
|
final call = Completer<AuthResult>();
|
|
calls.add(call);
|
|
return call.future;
|
|
}
|
|
|
|
@override
|
|
Future<AuthResult> login(LoginRequest request) {
|
|
loginInput = request;
|
|
return next();
|
|
}
|
|
|
|
@override
|
|
Future<AuthResult> signup(SignupRequest request) {
|
|
signupInput = request;
|
|
return next();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
const result = AuthResult(userId: 'demo', isSimulated: true);
|
|
for (final signup in [false, true]) {
|
|
group(signup ? 'signup' : 'login', () {
|
|
late ControlledService service;
|
|
late AuthProvider provider;
|
|
Future<void> submit() => signup
|
|
? (provider as SignupProvider).signup(
|
|
name: ' Alex ',
|
|
email: ' a@example.com ',
|
|
password: ' secret ',
|
|
locale: 'ru',
|
|
)
|
|
: (provider as LoginProvider).login(
|
|
email: ' a@example.com ',
|
|
password: ' secret ',
|
|
locale: 'ru',
|
|
);
|
|
setUp(() {
|
|
service = ControlledService();
|
|
provider = signup ? SignupProvider(service) : LoginProvider(service);
|
|
});
|
|
test(
|
|
'loading, duplicate suppression, result and normalized input',
|
|
() async {
|
|
final states = <AuthStatus>[];
|
|
provider.addListener(() => states.add(provider.state.status));
|
|
final first = submit();
|
|
await submit();
|
|
expect(service.calls, hasLength(1));
|
|
expect(provider.state.isLoading, isTrue);
|
|
if (signup) {
|
|
expect(service.signupInput!.name, 'Alex');
|
|
expect(service.signupInput!.email, 'a@example.com');
|
|
expect(service.signupInput!.password, ' secret ');
|
|
expect(service.signupInput!.locale, 'ru');
|
|
} else {
|
|
expect(service.loginInput!.email, 'a@example.com');
|
|
expect(service.loginInput!.password, ' secret ');
|
|
expect(service.loginInput!.locale, 'ru');
|
|
}
|
|
service.calls.single.complete(result);
|
|
await first;
|
|
expect(states, [AuthStatus.loading, AuthStatus.success]);
|
|
expect(provider.state.result, same(result));
|
|
provider.dispose();
|
|
},
|
|
);
|
|
test('typed failure and retry clear previous error', () async {
|
|
final first = submit();
|
|
service.calls.last.completeError(
|
|
const AuthException(AuthError.unavailable),
|
|
);
|
|
await first;
|
|
expect(provider.state.error, AuthError.unavailable);
|
|
final retry = submit();
|
|
expect(provider.state.error, isNull);
|
|
service.calls.last.complete(result);
|
|
await retry;
|
|
expect(provider.state.status, AuthStatus.success);
|
|
provider.dispose();
|
|
});
|
|
test('unknown exceptions are sanitized', () async {
|
|
final first = submit();
|
|
service.calls.last.completeError(StateError('secret password'));
|
|
await first;
|
|
expect(provider.state.error, AuthError.unexpected);
|
|
expect(provider.state.result, isNull);
|
|
provider.dispose();
|
|
});
|
|
test('reset rejects stale completion during a new operation', () async {
|
|
final first = submit();
|
|
provider.reset();
|
|
expect(provider.state.status, AuthStatus.idle);
|
|
final second = submit();
|
|
service.calls.first.complete(result);
|
|
await first;
|
|
expect(provider.state.status, AuthStatus.loading);
|
|
service.calls.last.complete(result);
|
|
await second;
|
|
expect(provider.state.status, AuthStatus.success);
|
|
provider.dispose();
|
|
});
|
|
test('dispose ignores late failures and further submissions', () async {
|
|
var notifications = 0;
|
|
provider.addListener(() => notifications++);
|
|
final pending = submit();
|
|
provider.dispose();
|
|
service.calls.last.completeError(
|
|
const AuthException(AuthError.unavailable),
|
|
);
|
|
await pending;
|
|
await submit();
|
|
expect(notifications, 1);
|
|
expect(service.calls, hasLength(1));
|
|
});
|
|
});
|
|
}
|
|
test('dummy services simulate success and configured errors', () async {
|
|
const login = LoginRequest(
|
|
email: 'a@example.com',
|
|
password: 'secret',
|
|
locale: 'en',
|
|
);
|
|
const signup = SignupRequest(
|
|
name: 'Alex',
|
|
email: 'a@example.com',
|
|
password: 'secret',
|
|
locale: 'ru',
|
|
);
|
|
expect(
|
|
(await const DummyLoginService(delay: Duration.zero).login(login))
|
|
.isSimulated,
|
|
isTrue,
|
|
);
|
|
final accepted = await const DummySignupService(delay: Duration.zero)
|
|
.signup(signup);
|
|
expect(accepted.isSimulated, isTrue);
|
|
expect(accepted.delivery?.email, 'a@example.com');
|
|
expect(accepted.delivery?.isSimulated, isTrue);
|
|
await expectLater(
|
|
const DummyLoginService(
|
|
delay: Duration.zero,
|
|
failure: AuthError.invalidCredentials,
|
|
).login(login),
|
|
throwsA(
|
|
isA<AuthException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
AuthError.invalidCredentials,
|
|
),
|
|
),
|
|
);
|
|
await expectLater(
|
|
const DummySignupService(
|
|
delay: Duration.zero,
|
|
failure: AuthError.emailInUse,
|
|
).signup(signup),
|
|
throwsA(
|
|
isA<AuthException>().having(
|
|
(e) => e.code,
|
|
'code',
|
|
AuthError.emailInUse,
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|