Files
letro-ios/UnitTests/Sources/ClassicAppMediaLoaderTests.swift
2026-04-08 21:18:03 +01:00

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
}
}