Files
element-x-ios/UnitTests/Sources/MediaUploadingPreprocessorTests.swift
T
Michael 22d14ff8e4 AppSettings are Mockable (#5589)
* create DependenciesProtocol and replace ServiceLocator registration with it

replace mocking calls for ServiceLocator to Dependencies

replace ServiceLocator with instance level calls

replace ServiceLocator configs with TestDependencies config in Tests

replace servicelocator usage in analytics service

replace remaining ServiceLocator calls in unit tests

that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances

fix swiftlint warning

remove remaining references to ServiceLocator

make dependencies properties consistent in tests

delete Dependencies and DependenciesProtocol

fix whitespace modifications

updates from PR feedback

a pass over converting AppSettings and AnalyticsService test instance properties to per test instantiation

fix a missed PR feedback

Sync up after rebase

create DependenciesProtocol and replace ServiceLocator registration with it

replace mocking calls for ServiceLocator to Dependencies

replace ServiceLocator with instance level calls

replace ServiceLocator configs with TestDependencies config in Tests

replace servicelocator usage in analytics service

replace remaining ServiceLocator calls in unit tests

that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances

fix swiftlint warning

remove remaining references to ServiceLocator

make dependencies properties consistent in tests

delete Dependencies and DependenciesProtocol

* AppSettings is instance level mockable now

* replace mocked and preview usages of reset + AppSettings global init with

AppSettings(store: UserDefaultsMock())

* AppSettings.UserDefaultsKeys is now a RawRep struct, making it extensible

* update tests with new AppSettings so it builds

* update remaining AppSettings test/mock inits

* AppSettings.UserDefaultsKey conformed to ExpressibleByStringLiteral

* addressed issues found in first pass ai review

* update AppSettings instantiations after rebase

* fix accessibility tests coordinator after rebase

* Rename to VolatileUserDefaults

* AppSettings init uses mandatory parameter

* Revert UserDefaultsKey to enum

* Add TrackedUserDefaults to be able to reset the defaults object

* Added volatile static func to AppSettings

* Use .volatile() static func instead of full init

* review fixups

* Resolve build issues after rebase

* updated enterprise module

* remove the static reset settings call from the preview tests

---------

Co-authored-by: Mauro Romito <mauro.romito@element.io>
2026-05-22 15:14:08 +02:00

498 lines
25 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 New Vector 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 SwiftUI
import Testing
import UniformTypeIdentifiers
final class MediaUploadingPreprocessorTests {
let maxUploadSize: UInt = 100 * 1024 * 1024
var appSettings: AppSettings!
var mediaUploadingPreprocessor: MediaUploadingPreprocessor!
init() {
appSettings = AppSettings.volatile()
appSettings.optimizeMediaUploads = false
mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings)
}
@Test
func audioFileProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "test_audio.mp3", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .audio(audioURL, audioInfo) = result else {
Issue.record("Failed processing asset")
return
}
// Check that the file name is preserved
#expect(audioURL.lastPathComponent == "test_audio.mp3")
#expect(audioInfo.mimetype == "audio/mpeg")
#expect(isEqual(audioInfo.duration ?? 0, 27, within: 100))
#expect(isEqual(audioInfo.size ?? 0, 194_811, within: 100))
}
@Test
func landscapeMovVideoProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(videoURL, thumbnailURL, videoInfo) = result else {
Issue.record("Failed processing asset")
return
}
// Check that the file name is preserved
#expect(videoURL.lastPathComponent == "landscape_test_video.mp4")
#expect(videoURL.pathExtension == "mp4", "The file extension should match the container we use.")
// Check that the thumbnail is generated correctly
let thumbnailData = try Data(contentsOf: thumbnailURL)
let thumbnail = try #require(UIImage(data: thumbnailData), "Invalid thumbnail")
#expect(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
#expect(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
// Check resulting video info
#expect(videoInfo.mimetype == "video/mp4")
#expect(videoInfo.blurhash == "K9F$LJZ9,+8yA9-:yT,@%1")
#expect(isEqual(videoInfo.size ?? 0, 4_016_620, within: 100))
#expect(videoInfo.width == 1280)
#expect(videoInfo.height == 720)
#expect(isEqual(videoInfo.duration ?? 0, 30, within: 100))
#expect(videoInfo.thumbnailInfo != nil)
#expect(videoInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(videoInfo.thumbnailInfo?.size ?? 0, 183_093, within: 100))
#expect(videoInfo.thumbnailInfo?.width == 800)
#expect(videoInfo.thumbnailInfo?.height == 450)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
#expect(optimizedVideoURL.pathExtension == "mp4", "The file extension should match the container we use.")
// Check optimised video info
#expect(optimizedVideoInfo.mimetype == "video/mp4")
#expect(optimizedVideoInfo.blurhash == "K9F$LJZ9,+8yA9-:yT,@%1")
#expect(isEqual(optimizedVideoInfo.size ?? 0, 4_016_620, within: 100)) // Note: The video is already 720p so it doesn't change size.
#expect(optimizedVideoInfo.width == 1280)
#expect(optimizedVideoInfo.height == 720)
#expect(isEqual(optimizedVideoInfo.duration ?? 0, 30, within: 100))
}
@Test
func portraitMp4VideoProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(videoURL, thumbnailURL, videoInfo) = result else {
Issue.record("Failed processing asset")
return
}
// Check that the file name is preserved
#expect(videoURL.lastPathComponent == "portrait_test_video.mp4")
#expect(videoURL.pathExtension == "mp4", "The file extension should match the container we use.")
// Check that the thumbnail is generated correctly
let thumbnailData = try Data(contentsOf: thumbnailURL)
let thumbnail = try #require(UIImage(data: thumbnailData), "Invalid thumbnail")
#expect(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
#expect(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
// Check resulting video info
#expect(videoInfo.mimetype == "video/mp4")
#expect(videoInfo.blurhash == "KSB{R8O]MuwQS4oJvcaIt8")
#expect(isEqual(videoInfo.size ?? 0, 5_824_946, within: 100))
#expect(videoInfo.width == 1080)
#expect(videoInfo.height == 1920)
#expect(isEqual(videoInfo.duration ?? 0, 21, within: 100))
#expect(videoInfo.thumbnailInfo != nil)
#expect(videoInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(videoInfo.thumbnailInfo?.size ?? 0, 40976, within: 100))
#expect(videoInfo.thumbnailInfo?.width == 337)
#expect(videoInfo.thumbnailInfo?.height == 600)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
#expect(optimizedVideoURL.pathExtension == "mp4", "The file extension should match the container we use.")
// Check optimised video info
#expect(optimizedVideoInfo.mimetype == "video/mp4")
#expect(optimizedVideoInfo.blurhash == "KSC5.vO]MuwQS4oJvcaIt8")
#expect(isEqual(optimizedVideoInfo.size ?? 0, 12_169_117, within: 100)) // Note: This is slightly stupid because it is larger now 🤦‍♂️
#expect(optimizedVideoInfo.width == 720)
#expect(optimizedVideoInfo.height == 1280)
#expect(isEqual(optimizedVideoInfo.duration ?? 0, 30, within: 100))
}
@Test
func landscapeImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "landscape_test_image.jpg", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
// Check resulting image info
#expect(imageInfo.mimetype == "image/jpeg")
#expect(imageInfo.blurhash == "K%I#.NofkC_4ayayxujsWB")
#expect(isEqual(imageInfo.size ?? 0, 3_305_795, within: 100))
#expect(imageInfo.width == 6103)
#expect(imageInfo.height == 2621)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(imageInfo.thumbnailInfo?.size ?? 0, 87733, within: 100))
#expect(imageInfo.thumbnailInfo?.width == 800)
#expect(imageInfo.thumbnailInfo?.height == 344)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
// Check optimised image info
#expect(optimizedImageInfo.mimetype == "image/jpeg")
#expect(optimizedImageInfo.blurhash == "K%I#.NofkC_4ayaxxujsWB")
#expect(isEqual(optimizedImageInfo.size ?? 0, 524_226, within: 100))
#expect(optimizedImageInfo.width == 2048)
#expect(optimizedImageInfo.height == 879)
}
@Test
func portraitImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "portrait_test_image.jpg", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
// Check resulting image info
#expect(imageInfo.mimetype == "image/jpeg")
#expect(imageInfo.blurhash == "KdE|0Ls+RP^-n*RP%OWAV@")
#expect(isEqual(imageInfo.size ?? 0, 4_414_666, within: 100))
#expect(imageInfo.width == 3024)
#expect(imageInfo.height == 4032)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(imageInfo.thumbnailInfo?.size ?? 0, 258_914, within: 100))
#expect(imageInfo.thumbnailInfo?.width == 600)
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
// Check optimised image info
#expect(optimizedImageInfo.mimetype == "image/jpeg")
#expect(optimizedImageInfo.blurhash == "KdE|0Ls+RP^-n*RP%OWAV@")
#expect(isEqual(optimizedImageInfo.size ?? 0, 1_462_937, within: 100))
#expect(optimizedImageInfo.width == 1536)
#expect(optimizedImageInfo.height == 2048)
}
@Test
func pngImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "test_image.png", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, _, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
// Make sure the output file matches the image info.
#expect(mimeType(from: convertedImageURL) == "image/png", "PNGs should always be sent as PNG to preserve the alpha channel.")
#expect(convertedImageURL.pathExtension == "png", "The file extension should match the MIME type.")
// Check resulting image info
#expect(imageInfo.mimetype == "image/png")
#expect(imageInfo.blurhash == "K0TSUA~qfQ~qj[fQfQfQfQ")
#expect(isEqual(imageInfo.size ?? 0, 4868, within: 100))
#expect(imageInfo.width == 240)
#expect(imageInfo.height == 240)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(imageInfo.thumbnailInfo?.size ?? 0, 1725, within: 100))
#expect(imageInfo.thumbnailInfo?.width == 240)
#expect(imageInfo.thumbnailInfo?.height == 240)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
// Make sure the output file matches the image info.
#expect(mimeType(from: optimizedImageURL) == "image/png", "PNGs should always be sent as PNG to preserve the alpha channel.")
#expect(optimizedImageURL.pathExtension == "png", "The file extension should match the MIME type.")
// Check optimised image info
#expect(optimizedImageInfo.mimetype == "image/png")
#expect(optimizedImageInfo.blurhash == "K0TSUA~qfQ~qj[fQfQfQfQ")
#expect(isEqual(optimizedImageInfo.size ?? 0, 8199, within: 100))
// Assert that resizing didn't upscale to the maxPixelSize.
#expect(optimizedImageInfo.width == 240)
#expect(optimizedImageInfo.height == 240)
}
@Test
func heicImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "test_apple_image.heic", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
// Make sure the output file matches the image info.
#expect(mimeType(from: convertedImageURL) == "image/heic", "Unoptimised HEICs should always be sent as is.")
#expect(convertedImageURL.pathExtension == "heic", "The file extension should match the MIME type.")
// Check resulting image info
#expect(imageInfo.mimetype == "image/heic")
#expect(imageInfo.blurhash == "KGD]3ns:T00$kWxFXmt6xv")
#expect(isEqual(imageInfo.size ?? 0, 1_848_525, within: 100))
#expect(imageInfo.width == 3024)
#expect(imageInfo.height == 4032)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(imageInfo.thumbnailInfo?.size ?? 0, 218_108, within: 100))
#expect(imageInfo.thumbnailInfo?.width == 600)
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
// Make sure the output file matches the image info.
#expect(mimeType(from: optimizedImageURL) == "image/jpeg", "Optimised HEICs should always be converted to JPEG for compatibility.")
#expect(optimizedImageURL.pathExtension == "jpeg", "The file extension should match the MIME type.")
// Check optimised image info
#expect(optimizedImageInfo.mimetype == "image/jpeg")
#expect(optimizedImageInfo.blurhash == "KGD]3ns:T00#kWxFb^s:xv")
#expect(isEqual(optimizedImageInfo.size ?? 0, 1_049_393, within: 100))
#expect(optimizedImageInfo.width == 1536)
#expect(optimizedImageInfo.height == 2048)
}
@Test
func gifImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "test_animated_image.gif", withExtension: nil), "Failed retrieving test asset")
let originalSizeValue = try UInt64(FileManager.default.sizeForItem(at: url))
let originalSize = try #require(originalSizeValue > 0 ? originalSizeValue : nil, "File size must be greater than zero")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, _, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
// Make sure the output file matches the image info.
#expect(mimeType(from: convertedImageURL) == "image/gif", "GIFs should always be sent as GIF to preserve the animation.")
#expect(convertedImageURL.pathExtension == "gif", "The file extension should match the MIME type.")
// Check resulting image info
#expect(imageInfo.mimetype == "image/gif")
#expect(imageInfo.blurhash == "KpRMPTj[_NxuaeRj%MofMx")
#expect(isEqual(imageInfo.size ?? 0, originalSize, within: 100))
#expect(imageInfo.width == 331)
#expect(imageInfo.height == 472)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.mimetype == "image/jpeg")
#expect(isEqual(imageInfo.thumbnailInfo?.size ?? 0, 34215, within: 100))
#expect(imageInfo.thumbnailInfo?.width == 331)
#expect(imageInfo.thumbnailInfo?.height == 472)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
// Make sure the output file matches the image info.
#expect(mimeType(from: optimizedImageURL) == "image/gif", "GIFs should always be sent as GIF to preserve the animation.")
#expect(optimizedImageURL.pathExtension == "gif", "The file extension should match the MIME type.")
// Ensure optimised image is still the same as the original image.
#expect(optimizedImageInfo.mimetype == "image/gif")
#expect(optimizedImageInfo.blurhash == "KpRMPTj[_NxuaeRj%MofMx")
#expect(isEqual(optimizedImageInfo.size ?? 0, originalSize, within: 100))
#expect(optimizedImageInfo.width == 331)
#expect(optimizedImageInfo.height == 472)
}
@Test
func rotatedImageProcessing() async throws {
let url = try #require(Bundle(for: Self.self).url(forResource: "test_rotated_image.jpg", withExtension: nil), "Failed retrieving test asset")
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
// Check resulting image info
#expect(imageInfo.mimetype == "image/jpeg")
#expect(imageInfo.width == 2848)
#expect(imageInfo.height == 4272)
#expect(imageInfo.thumbnailInfo != nil)
#expect(imageInfo.thumbnailInfo?.width == 533)
#expect(imageInfo.thumbnailInfo?.height == 800)
// Repeat with optimised media setting
appSettings.optimizeMediaUploads = true
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url, maxUploadSize: maxUploadSize),
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
Issue.record("Failed processing asset")
return
}
try compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
// Check optimised image info
#expect(optimizedImageInfo.mimetype == "image/jpeg")
#expect(optimizedImageInfo.width == 1365)
#expect(optimizedImageInfo.height == 2048)
}
// MARK: - Private
private func isEqual<N: UnsignedInteger>(_ lhs: N, _ rhs: N, within tolerance: N) -> Bool {
isEqual(Double(lhs), Double(rhs), within: Double(tolerance))
}
private func isEqual<N: SignedNumeric & Comparable>(_ lhs: N, _ rhs: N, within tolerance: N) -> Bool {
abs(lhs - rhs) <= tolerance
}
private func compare(originalImageAt originalImageURL: URL, toConvertedImageAt convertedImageURL: URL, withThumbnailAt thumbnailURL: URL) throws {
guard let originalImageData = try? Data(contentsOf: originalImageURL),
let originalImage = UIImage(data: originalImageData),
let convertedImageData = try? Data(contentsOf: convertedImageURL),
let convertedImage = UIImage(data: convertedImageData) else {
fatalError()
}
if appSettings.optimizeMediaUploads {
// Check that new image has been scaled within the requirements for an optimised image
#expect(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
#expect(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
} else {
// Check that the file name is preserved
#expect(originalImageURL.lastPathComponent == convertedImageURL.lastPathComponent)
// Check that new image is the same size as the original one
#expect(originalImage.size == convertedImage.size)
}
// Check that the GPS data has been stripped
let originalMetadata = try metadata(from: originalImageData)
#expect(originalMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)") != nil)
let convertedMetadata = try metadata(from: convertedImageData)
#expect(convertedMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)") == nil)
// Check that the thumbnail is generated correctly
let thumbnailData = try Data(contentsOf: thumbnailURL)
let thumbnail = try #require(UIImage(data: thumbnailData), "Invalid thumbnail")
if thumbnail.size.width > thumbnail.size.height {
#expect(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
#expect(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
} else {
#expect(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
#expect(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
}
let thumbnailMetadata = try metadata(from: thumbnailData)
#expect(thumbnailMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)") == nil)
}
private func metadata(from imageData: Data) throws -> NSDictionary {
let imageSource = try #require(CGImageSourceCreateWithData(imageData as NSData, nil), "Invalid asset")
return try #require(CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as NSDictionary?, "Test asset is expected to contain metadata")
}
private func mimeType(from url: URL) -> String? {
guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
let typeIdentifier = CGImageSourceGetType(imageSource),
let type = UTType(typeIdentifier as String),
let mimeType = type.preferredMIMEType else {
Issue.record("Failed to get mimetype from URL.")
return nil
}
return mimeType
}
}