mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
e5bfc3a26d
Expose OpenID tokens and homeserver credentials from the client proxy, and add fork-only services for the HealthChat member directory backend (api.healthchat.ch) plus a service that reads MSC4133 extended profile fields directly from the homeserver. Groundwork for the members tab and the profile screens. Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
118 lines
4.3 KiB
Swift
118 lines
4.3 KiB
Swift
//
|
|
// Copyright 2026 Element Creations Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
@testable import ElementX
|
|
import Foundation
|
|
import Synchronization
|
|
import Testing
|
|
|
|
final class ExtendedProfileServiceTests {
|
|
private let clientProxy = ClientProxyMock(.init())
|
|
private let service: ExtendedProfileService
|
|
|
|
init() {
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.protocolClasses = [MockProfileURLProtocol.self]
|
|
service = ExtendedProfileService(clientProxy: clientProxy,
|
|
urlSession: URLSession(configuration: configuration))
|
|
|
|
clientProxy.homeserverCredentialsReturnValue = .success(.init(homeserverURL: "https://matrix.example.com",
|
|
accessToken: MockProfileURLProtocol.accessToken))
|
|
}
|
|
|
|
@Test
|
|
func fetchesProfileWithBearerToken() async throws {
|
|
let result = await service.profile(for: "@alice:healthchat.ch")
|
|
|
|
let profile = try result.get()
|
|
#expect(profile.displayName == "Dr. Alice Alpha")
|
|
#expect(profile.businessTel == "+41 44 123 45 67")
|
|
|
|
let request = try #require(MockProfileURLProtocol.lastRequest)
|
|
#expect(request.url?.path(percentEncoded: false) == "/_matrix/client/v3/profile/@alice:healthchat.ch")
|
|
#expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockProfileURLProtocol.accessToken)")
|
|
}
|
|
|
|
@Test
|
|
func missingCredentialsFailWithoutRequest() async {
|
|
clientProxy.homeserverCredentialsReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
|
|
|
|
let result = await service.profile(for: "@alice:healthchat.ch")
|
|
|
|
#expect(result == .failure(.missingCredentials))
|
|
}
|
|
|
|
@Test
|
|
func unknownUserFailsWithHTTPStatus() async {
|
|
let result = await service.profile(for: "@unknown:healthchat.ch")
|
|
|
|
#expect(result == .failure(.httpStatus(404)))
|
|
}
|
|
|
|
@Test
|
|
func credentialsDescriptionRedactsAccessToken() {
|
|
let credentials = HomeserverCredentials(homeserverURL: "https://matrix.example.com",
|
|
accessToken: "supersecret")
|
|
|
|
#expect(!String(describing: credentials).contains("supersecret"))
|
|
}
|
|
}
|
|
|
|
// MARK: - MockProfileURLProtocol
|
|
|
|
private nonisolated class MockProfileURLProtocol: URLProtocol {
|
|
static let accessToken = "testAccessToken"
|
|
|
|
private static let _lastRequest: Mutex<URLRequest?> = .init(nil)
|
|
static var lastRequest: URLRequest? {
|
|
_lastRequest.withLock { $0 }
|
|
}
|
|
|
|
private static let profileJSON = """
|
|
{
|
|
"displayname": "Dr. Alice Alpha",
|
|
"io.element.profile.business_tel": "+41 44 123 45 67"
|
|
}
|
|
"""
|
|
|
|
private static let responses: [String: (Int, Data)] = [
|
|
"/_matrix/client/v3/profile/@alice:healthchat.ch": (200, Data(profileJSON.utf8))
|
|
]
|
|
|
|
override func startLoading() {
|
|
MockProfileURLProtocol._lastRequest.withLock { $0 = request }
|
|
|
|
guard let url = request.url else {
|
|
client?.urlProtocol(self, didFailWithError: URLError(.badURL))
|
|
return
|
|
}
|
|
|
|
let isAuthenticated = request.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockProfileURLProtocol.accessToken)"
|
|
let path = url.path(percentEncoded: false)
|
|
let (statusCode, data) = isAuthenticated ? MockProfileURLProtocol.responses[path] ?? (404, Data()) : (401, Data())
|
|
|
|
guard let response = HTTPURLResponse(url: url, statusCode: statusCode, httpVersion: nil, headerFields: nil) else {
|
|
client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))
|
|
return
|
|
}
|
|
|
|
client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
|
|
client?.urlProtocol(self, didLoad: data)
|
|
client?.urlProtocolDidFinishLoading(self)
|
|
}
|
|
|
|
override func stopLoading() { }
|
|
|
|
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
|
|
request
|
|
}
|
|
|
|
override class func canInit(with request: URLRequest) -> Bool {
|
|
true
|
|
}
|
|
}
|