diff --git a/ElementX/Sources/Services/Users/UserDiscoveryService.swift b/ElementX/Sources/Services/Users/UserDiscoveryService.swift index 224d1bf09..662346707 100644 --- a/ElementX/Sources/Services/Users/UserDiscoveryService.swift +++ b/ElementX/Sources/Services/Users/UserDiscoveryService.swift @@ -28,36 +28,45 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol { } func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> { + async let queriedProfile = profileIfPossible(with: searchQuery) + do { - async let queriedProfile = try? profileIfPossible(with: searchQuery).get() - async let searchedUsers = clientProxy.searchUsers(searchTerm: searchQuery, limit: 10) - let users = try await merge(searchQuery: searchQuery, queriedProfile: queriedProfile, searchResults: searchedUsers.get()) + async let searchedUsers = clientProxy.searchUsers(searchTerm: searchQuery, limit: 10).get() + let users = try await merge(queriedProfile: queriedProfile, searchResults: searchedUsers) return .success(users) } catch { - return .failure(.failedSearchingUsers) + // we want to show the profile (if any) even if the search fails + if let queriedProfile = await queriedProfile { + return .success([queriedProfile]) + } else { + return .failure(.failedSearchingUsers) + } } } - private func merge(searchQuery: String, queriedProfile: UserProfileProxy?, searchResults: SearchUsersResultsProxy) -> [UserProfileProxy] { - let localProfile = queriedProfile ?? UserProfileProxy(searchQuery: searchQuery) + private func merge(queriedProfile: UserProfileProxy?, searchResults: SearchUsersResultsProxy) -> [UserProfileProxy] { let searchResults = searchResults.results - guard let localProfile else { + + guard let queriedProfile else { return searchResults } let filteredSearchResult = searchResults.filter { - $0.userID != localProfile.userID + $0.userID != queriedProfile.userID } - return [localProfile] + filteredSearchResult + return [queriedProfile] + filteredSearchResult } - private func profileIfPossible(with searchQuery: String) async -> Result { + private func profileIfPossible(with searchQuery: String) async -> UserProfileProxy? { guard searchQuery.isMatrixIdentifier else { - return .failure(.failedGettingUserProfile) + return nil } - return await clientProxy.profile(for: searchQuery) + let getProfileResult = try? await clientProxy.profile(for: searchQuery).get() + + // fallback to a "local profile" if the profile api fails + return getProfileResult ?? .init(userID: searchQuery) } } @@ -66,12 +75,3 @@ private extension String { MatrixEntityRegex.isMatrixUserIdentifier(self) } } - -private extension UserProfileProxy { - init?(searchQuery: String) { - guard searchQuery.isMatrixIdentifier else { - return nil - } - self.init(userID: searchQuery) - } -} diff --git a/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift b/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift index 832382190..28a5fc788 100644 --- a/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift +++ b/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift @@ -54,6 +54,30 @@ class UserDiscoveryServiceTest: XCTestCase { XCTAssertTrue(clientProxy.getProfileCalled) } + func testLocalResultShowsOnSearchError() async { + clientProxy.searchUsersResult = .failure(.failedSearchingUsers) + clientProxy.getProfileResult = .success(.init(userID: "@some:matrix.org")) + + let results = await (try? search(query: "@a:b.com").get()) ?? [] + + assertSearchResults(results, toBe: 1) + XCTAssertTrue(clientProxy.getProfileCalled) + } + + func testSearchErrorTriggers() async { + clientProxy.searchUsersResult = .failure(.failedSearchingUsers) + clientProxy.getProfileResult = .success(.init(userID: "@some:matrix.org")) + + switch await search(query: "some query") { + case .success: + XCTFail("Search users must fail") + case .failure(let error): + XCTAssertEqual(error, UserDiscoveryErrorType.failedSearchingUsers) + } + + XCTAssertFalse(clientProxy.getProfileCalled) + } + func testLocalResultWithDuplicates() async { clientProxy.searchUsersResult = .success(.init(results: searchResults, limited: true)) clientProxy.getProfileResult = .success(.init(userID: "@bob:matrix.org"))