504 lines
25 KiB
Swift
504 lines
25 KiB
Swift
//
|
||
// 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.resetAllSettings()
|
||
appSettings = AppSettings()
|
||
appSettings.optimizeMediaUploads = false
|
||
ServiceLocator.shared.register(appSettings: appSettings)
|
||
mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings)
|
||
}
|
||
|
||
deinit {
|
||
AppSettings.resetAllSettings()
|
||
}
|
||
|
||
@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
|
||
}
|
||
}
|