TF-3826 Try guessing webfinger when dns look up fail
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/model/oidc_guessing_origin.dart';
|
||||
|
||||
void main() {
|
||||
group('OidcGuessingOrigin enum', () {
|
||||
test('url() returns correct URL for empty origin', () {
|
||||
const email = 'user@example.com';
|
||||
const origin = OidcGuessingOrigin.empty;
|
||||
expect(origin.url(email), 'https://example.com');
|
||||
});
|
||||
|
||||
test('url() returns correct URL for autoDiscover origin', () {
|
||||
const email = 'user@example.com';
|
||||
const origin = OidcGuessingOrigin.autoDiscover;
|
||||
expect(origin.url(email), 'https://autodiscover.example.com');
|
||||
});
|
||||
|
||||
test('url() returns correct URL for jmap origin', () {
|
||||
const email = 'user@example.com';
|
||||
const origin = OidcGuessingOrigin.jmap;
|
||||
expect(origin.url(email), 'https://jmap.example.com');
|
||||
});
|
||||
|
||||
test('url() throws error for invalid email', () {
|
||||
const email = 'invalid_email@@example.com';
|
||||
const origin = OidcGuessingOrigin.empty;
|
||||
expect(() => origin.url(email), throwsArgumentError);
|
||||
});
|
||||
|
||||
test('url() returns correct URL for jmap origin with quoted local part', () {
|
||||
const email = '"user"@example.com';
|
||||
const origin = OidcGuessingOrigin.jmap;
|
||||
expect(origin.url(email), 'https://jmap.example.com');
|
||||
});
|
||||
|
||||
test('url() returns correct URL for jmap origin with quoted local part containing @', () {
|
||||
const email = '"user@local"@example.com';
|
||||
const origin = OidcGuessingOrigin.jmap;
|
||||
expect(origin.url(email), 'https://jmap.example.com');
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/annotations.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/try_guessing_web_finger_interactor.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/oidc/request/oidc_request.dart';
|
||||
import 'package:model/oidc/response/oidc_response.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/state/try_guessing_web_finger_state.dart';
|
||||
|
||||
@GenerateMocks([AuthenticationOIDCRepository])
|
||||
import 'try_guessing_web_finger_interactor_test.mocks.dart';
|
||||
|
||||
void main() {
|
||||
late MockAuthenticationOIDCRepository mockRepository;
|
||||
late TryGuessingWebFingerInteractor interactor;
|
||||
|
||||
final oidcRequest1 = OIDCRequest(
|
||||
baseUrl: 'https://example1.com',
|
||||
resourceUrl: 'https://example1.com/.well-known/openid-configuration'
|
||||
);
|
||||
final oidcRequest2 = OIDCRequest(
|
||||
baseUrl: 'https://example2.com',
|
||||
resourceUrl: 'https://example2.com/.well-known/openid-configuration'
|
||||
);
|
||||
final successResponse = OIDCResponse('subject', []);
|
||||
|
||||
setUp(() {
|
||||
mockRepository = MockAuthenticationOIDCRepository();
|
||||
interactor = TryGuessingWebFingerInteractor(mockRepository);
|
||||
});
|
||||
|
||||
test('should return first successful response immediately', () async {
|
||||
// Arrange
|
||||
final completer1 = Completer<OIDCResponse>();
|
||||
final completer2 = Completer<OIDCResponse>();
|
||||
|
||||
when(mockRepository.checkOIDCIsAvailable(oidcRequest1))
|
||||
.thenAnswer((_) => completer1.future);
|
||||
when(mockRepository.checkOIDCIsAvailable(oidcRequest2))
|
||||
.thenAnswer((_) => completer2.future);
|
||||
|
||||
// Act
|
||||
final stream = interactor.execute([oidcRequest1, oidcRequest2]);
|
||||
final futureResults = stream.toList();
|
||||
|
||||
completer1.complete(successResponse);
|
||||
await Future.delayed(Duration.zero); // Allow stream to process
|
||||
|
||||
// Assert
|
||||
final results = await futureResults;
|
||||
expect(results, [
|
||||
Right(TryingGuessingWebFinger()),
|
||||
Right(TryGuessingWebFingerSuccess(successResponse)),
|
||||
]);
|
||||
|
||||
verifyInOrder([
|
||||
mockRepository.checkOIDCIsAvailable(oidcRequest1),
|
||||
mockRepository.checkOIDCIsAvailable(oidcRequest2),
|
||||
]);
|
||||
|
||||
// Verify second request was made but not completed
|
||||
expect(completer2.isCompleted, isFalse);
|
||||
});
|
||||
|
||||
test('should return failure when all responses are null', () async {
|
||||
// Arrange
|
||||
when(mockRepository.checkOIDCIsAvailable(any))
|
||||
.thenAnswer((_) async => throw Exception());
|
||||
|
||||
// Act
|
||||
final results = await interactor.execute([oidcRequest1, oidcRequest2]).toList();
|
||||
|
||||
// Assert
|
||||
expect(results[0], Right(TryingGuessingWebFinger()));
|
||||
expect(results[1], isA<Left<Failure, Success>>());
|
||||
expect((results[1] as Left<Failure, Success>).value, isA<TryGuessingWebFingerFailure>());
|
||||
});
|
||||
|
||||
test('should handle async response order correctly', () async {
|
||||
// Arrange
|
||||
final completer1 = Completer<OIDCResponse>();
|
||||
final completer2 = Completer<OIDCResponse>();
|
||||
|
||||
when(mockRepository.checkOIDCIsAvailable(oidcRequest1))
|
||||
.thenAnswer((_) => completer1.future);
|
||||
when(mockRepository.checkOIDCIsAvailable(oidcRequest2))
|
||||
.thenAnswer((_) => completer2.future);
|
||||
|
||||
// Act
|
||||
final stream = interactor.execute([oidcRequest1, oidcRequest2]);
|
||||
final futureResults = stream.toList();
|
||||
|
||||
completer2.complete(successResponse);
|
||||
await Future.delayed(Duration.zero);
|
||||
completer1.completeError(Exception('Timeout'));
|
||||
|
||||
final results = await futureResults;
|
||||
|
||||
// Assert
|
||||
expect(results, [
|
||||
Right(TryingGuessingWebFinger()),
|
||||
Right(TryGuessingWebFingerSuccess(successResponse)),
|
||||
]);
|
||||
});
|
||||
|
||||
test('should handle exceptions properly', () async {
|
||||
// Arrange
|
||||
when(mockRepository.checkOIDCIsAvailable(any))
|
||||
.thenAnswer((_) => Future<OIDCResponse>.error(Exception('Test error')));
|
||||
|
||||
// Act
|
||||
final results = await interactor.execute([oidcRequest1]).toList();
|
||||
|
||||
// Assert
|
||||
expect(results[0], Right(TryingGuessingWebFinger()));
|
||||
expect(results[1], isA<Left<Failure, Success>>());
|
||||
expect((results[1] as Left<Failure, Success>).value, isA<TryGuessingWebFingerFailure>());
|
||||
});
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_stored_oidc_con
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/try_guessing_web_finger_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/presentation/extensions/handle_openid_configuration.dart';
|
||||
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
|
||||
@@ -71,6 +72,7 @@ import 'login_controller_test.mocks.dart';
|
||||
MockSpec<GetAllRecentLoginUsernameOnMobileInteractor>(),
|
||||
MockSpec<DNSLookupToGetJmapUrlInteractor>(),
|
||||
MockSpec<SignInTwakeWorkplaceInteractor>(),
|
||||
MockSpec<TryGuessingWebFingerInteractor>(),
|
||||
MockSpec<GetSessionInteractor>(),
|
||||
MockSpec<GetAuthenticatedAccountInteractor>(),
|
||||
MockSpec<UpdateAccountCacheInteractor>(),
|
||||
@@ -94,6 +96,7 @@ void main() {
|
||||
late MockGetAllRecentLoginUsernameOnMobileInteractor mockGetAllRecentLoginUsernameOnMobileInteractor;
|
||||
late MockDNSLookupToGetJmapUrlInteractor mockDNSLookupToGetJmapUrlInteractor;
|
||||
late MockSignInTwakeWorkplaceInteractor mockSignInTwakeWorkplaceInteractor;
|
||||
late MockTryGuessingWebFingerInteractor mockTryGuessingWebFingerInteractor;
|
||||
late MockGetSessionInteractor mockGetSessionInteractor;
|
||||
late MockGetAuthenticatedAccountInteractor mockGetAuthenticatedAccountInteractor;
|
||||
late MockUpdateAccountCacheInteractor mockUpdateAccountCacheInteractor;
|
||||
@@ -128,6 +131,7 @@ void main() {
|
||||
mockGetAllRecentLoginUsernameOnMobileInteractor = MockGetAllRecentLoginUsernameOnMobileInteractor();
|
||||
mockDNSLookupToGetJmapUrlInteractor = MockDNSLookupToGetJmapUrlInteractor();
|
||||
mockSignInTwakeWorkplaceInteractor = MockSignInTwakeWorkplaceInteractor();
|
||||
mockTryGuessingWebFingerInteractor = MockTryGuessingWebFingerInteractor();
|
||||
|
||||
// mock reloadable controller
|
||||
mockGetSessionInteractor = MockGetSessionInteractor();
|
||||
@@ -191,6 +195,7 @@ void main() {
|
||||
mockGetAllRecentLoginUsernameOnMobileInteractor,
|
||||
mockDNSLookupToGetJmapUrlInteractor,
|
||||
mockSignInTwakeWorkplaceInteractor,
|
||||
mockTryGuessingWebFingerInteractor,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user