182 lines
7.8 KiB
Swift
182 lines
7.8 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 Testing
|
|
|
|
final class ClassicAppMediaLoaderTests {
|
|
let account: ClassicAppAccount
|
|
let mediaLoader: ClassicAppMediaLoader
|
|
let urlSession: URLSession
|
|
|
|
init() throws {
|
|
account = ClassicAppAccount(userID: "@alice:matrix.org",
|
|
displayName: nil,
|
|
avatarURL: nil,
|
|
serverName: "matrix.org",
|
|
homeserverURL: "https://matrix-client.matrix.org",
|
|
cryptoStoreURL: .temporaryDirectory,
|
|
cryptoStorePassphrase: "",
|
|
accessToken: MockURLProtocol.accessToken)
|
|
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
configuration.protocolClasses = [MockURLProtocol.self]
|
|
urlSession = URLSession(configuration: configuration)
|
|
|
|
mediaLoader = ClassicAppMediaLoader(classicAppAccount: account, urlSession: urlSession)
|
|
}
|
|
|
|
// MARK: - Images
|
|
|
|
@Test
|
|
func loadMediaContent() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
let data = try await mediaLoader.loadMediaContentForSource(source)
|
|
#expect(data == MockURLProtocol.downloadData)
|
|
}
|
|
|
|
@Test
|
|
func loadMediaContentWithInvalidToken() async throws {
|
|
let accountWithoutToken = ClassicAppAccount(userID: "@bob:matrix.org",
|
|
displayName: nil,
|
|
avatarURL: nil,
|
|
serverName: "matrix.org",
|
|
homeserverURL: "https://matrix-client.matrix.org",
|
|
cryptoStoreURL: .temporaryDirectory,
|
|
cryptoStorePassphrase: "",
|
|
accessToken: "wrongToken")
|
|
let loaderWithoutToken = ClassicAppMediaLoader(classicAppAccount: accountWithoutToken, urlSession: urlSession)
|
|
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
|
try await loaderWithoutToken.loadMediaContentForSource(source)
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func loadMediaContentWith404() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.notFoundMXCURL, mimeType: nil)
|
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
|
try await mediaLoader.loadMediaContentForSource(source)
|
|
}
|
|
}
|
|
|
|
// MARK: - Thumbnails
|
|
|
|
@Test
|
|
func loadMediaThumbnail() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
let data = try await mediaLoader.loadMediaThumbnailForSource(source, width: 100, height: 100)
|
|
#expect(data == MockURLProtocol.thumbnailData)
|
|
}
|
|
|
|
@Test
|
|
func loadMediaThumbnailWith404() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.notFoundMXCURL, mimeType: nil)
|
|
await #expect(throws: MediaLoaderError.unexpectedResponse) {
|
|
try await mediaLoader.loadMediaThumbnailForSource(source, width: 100, height: 100)
|
|
}
|
|
}
|
|
|
|
// MARK: - Files (unsupported)
|
|
|
|
@Test
|
|
func loadMediaFileIsNotSupported() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
await #expect(throws: MediaLoaderError.notSupported) {
|
|
try await mediaLoader.loadMediaFileForSource(source, filename: nil)
|
|
}
|
|
}
|
|
|
|
// MARK: - URL construction
|
|
|
|
@Test
|
|
func loadMediaContentBuildsCorrectDownloadURL() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
let capturedRequest = try await mediaLoader.loadMediaContentForSource(source)
|
|
|
|
// The download path must use the download endpoint with no query string.
|
|
#expect(MockURLProtocol.lastRequest?.url?.path() == "/_matrix/client/v1/media/download/matrix.org/testmediaid")
|
|
#expect(MockURLProtocol.lastRequest?.url?.query() == nil)
|
|
#expect(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)")
|
|
_ = capturedRequest
|
|
}
|
|
|
|
@Test
|
|
func loadMediaThumbnailBuildsCorrectThumbnailURL() async throws {
|
|
let source = try MediaSourceProxy(url: MockURLProtocol.mxcURL, mimeType: nil)
|
|
_ = try await mediaLoader.loadMediaThumbnailForSource(source, width: 320, height: 240)
|
|
|
|
// The thumbnail path must use the thumbnail endpoint with width/height query items.
|
|
let url = try #require(MockURLProtocol.lastRequest?.url)
|
|
#expect(url.path() == "/_matrix/client/v1/media/thumbnail/matrix.org/testmediaid")
|
|
let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
|
|
#expect(queryItems?.contains(URLQueryItem(name: "width", value: "320")) == true)
|
|
#expect(queryItems?.contains(URLQueryItem(name: "height", value: "240")) == true)
|
|
#expect(MockURLProtocol.lastRequest?.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)")
|
|
}
|
|
}
|
|
|
|
// MARK: - MockURLProtocol
|
|
|
|
private class MockURLProtocol: URLProtocol {
|
|
/// The MXC URL whose media requests will be served successfully.
|
|
static let mxcURL: URL = "mxc://matrix.org/testmediaid"
|
|
/// The MXC URL whose media requests will return a 404.
|
|
static let notFoundMXCURL: URL = "mxc://matrix.org/notfound"
|
|
|
|
/// The access token expected on all authenticated requests.
|
|
static let accessToken = "testAccessToken"
|
|
|
|
/// The data returned for a full-size download of `mxcURL`.
|
|
static let downloadData = Data("download data".utf8)
|
|
/// The data returned for a thumbnail download of `mxcURL`.
|
|
static let thumbnailData = Data("thumbnail data".utf8)
|
|
|
|
/// The last request handled, for URL/header inspection in tests.
|
|
static var lastRequest: URLRequest?
|
|
|
|
/// Maps a URL path to a fixed `(statusCode, Data)` response.
|
|
private static let responses: [String: (Int, Data)] = [
|
|
"/_matrix/client/v1/media/download/matrix.org/testmediaid": (200, downloadData),
|
|
"/_matrix/client/v1/media/thumbnail/matrix.org/testmediaid": (200, thumbnailData)
|
|
]
|
|
|
|
override func startLoading() {
|
|
MockURLProtocol.lastRequest = request
|
|
|
|
guard let url = request.url else {
|
|
client?.urlProtocol(self, didFailWithError: URLError(.badURL))
|
|
return
|
|
}
|
|
|
|
let isAuthenticated = request.value(forHTTPHeaderField: "Authorization") == "Bearer \(MockURLProtocol.accessToken)"
|
|
let path = url.path()
|
|
let (statusCode, data) = isAuthenticated ? MockURLProtocol.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
|
|
}
|
|
}
|