diff --git a/.gitattributes b/.gitattributes index 1d7dc2a24..b6757eeff 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ UITests/Sources/__Snapshots__/** filter=lfs diff=lfs merge=lfs -text +UnitTests/Resources/** filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 8295daba4..eb953e1ca 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -23,6 +23,7 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} + lfs: true - uses: actions/cache@v3 with: diff --git a/UnitTests/Resources/Media/landscape_test_image.jpg b/UnitTests/Resources/Media/landscape_test_image.jpg new file mode 100644 index 000000000..268d6c5a0 --- /dev/null +++ b/UnitTests/Resources/Media/landscape_test_image.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:79da65d0e46d2d7520db60a9e39513484cab5f37b797d1745a921d8a7014aebb +size 2298757 diff --git a/UnitTests/Resources/Media/portrait_test_image.jpg b/UnitTests/Resources/Media/portrait_test_image.jpg new file mode 100644 index 000000000..1ed41e11e --- /dev/null +++ b/UnitTests/Resources/Media/portrait_test_image.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b618d6055c7d4fb53e5edb487a5c1a6fdee0fb966c7910df95e01b612c3b2ad +size 3059441 diff --git a/UnitTests/Resources/Media/test_audio.mp3 b/UnitTests/Resources/Media/test_audio.mp3 new file mode 100644 index 000000000..788335478 --- /dev/null +++ b/UnitTests/Resources/Media/test_audio.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa2820256f2cd3f2df03fa247d7b01e79d3fe794344aadcea08cee06bcce3c94 +size 764176 diff --git a/UnitTests/Resources/Media/test_image.png b/UnitTests/Resources/Media/test_image.png new file mode 100644 index 000000000..d29308618 --- /dev/null +++ b/UnitTests/Resources/Media/test_image.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c3ff2e57850649f588516a1a2688be18df8cf8a0bb8850f8d943cf28659beca5 +size 6163 diff --git a/UnitTests/Resources/Media/test_pdf.pdf b/UnitTests/Resources/Media/test_pdf.pdf new file mode 100644 index 000000000..17bad3b57 --- /dev/null +++ b/UnitTests/Resources/Media/test_pdf.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60bdd13ea4827b8de375c79dc3ff847f83b55bd73b6461523fdf8f843b5a0d5b +size 7945 diff --git a/UnitTests/Resources/Media/test_video.mov b/UnitTests/Resources/Media/test_video.mov new file mode 100644 index 000000000..b75ddf156 --- /dev/null +++ b/UnitTests/Resources/Media/test_video.mov @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ff020aa426b7e49606dda50ef48812925f265e570456a09261932fff0d54c21 +size 1448367 diff --git a/UnitTests/Resources/test_image.png b/UnitTests/Resources/test_image.png deleted file mode 100644 index 85d7a6a01..000000000 Binary files a/UnitTests/Resources/test_image.png and /dev/null differ diff --git a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift index 1c561c839..8c481c601 100644 --- a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift +++ b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift @@ -166,8 +166,7 @@ final class MediaProviderTests: XCTestCase { } private func loadTestImage() throws -> UIImage { - let bundle = Bundle(for: classForCoder) - guard let path = bundle.path(forResource: "test_image", ofType: "png"), + guard let path = Bundle(for: Self.self).path(forResource: "test_image", ofType: "png"), let image = UIImage(contentsOfFile: path) else { throw MediaProviderTestsError.screenshotNotFound } diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift new file mode 100644 index 000000000..4bb9c2c97 --- /dev/null +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -0,0 +1,191 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +@testable import ElementX + +final class MediaUploadingPreprocessorTests: XCTestCase { + let mediaUploadingPreprocessor = MediaUploadingPreprocessor() + + func testAudioFileProcessing() async { + guard let url = Bundle(for: Self.self).url(forResource: "test_audio.mp3", withExtension: nil) else { + XCTFail("Failed retrieving test asset") + return + } + + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + case let .audio(audioURL, audioInfo) = result else { + XCTFail("Failed processing asset") + return + } + + // Check that the file name is preserved + XCTAssertEqual(audioURL.lastPathComponent, "test_audio.mp3") + + XCTAssertEqual(audioInfo.mimetype, "audio/mpeg") + XCTAssertEqual(audioInfo.duration, 27252) + XCTAssertEqual(audioInfo.size, 764_176) + } + + func testVideoProcessing() async { + guard let url = Bundle(for: Self.self).url(forResource: "test_video.mov", withExtension: nil) else { + XCTFail("Failed retrieving test asset") + return + } + + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + case let .video(videoURL, thumbnailURL, videoInfo) = result else { + XCTFail("Failed processing asset") + return + } + + // Check that the file name is preserved + XCTAssertEqual(videoURL.lastPathComponent, "test_video.mp4") + + // Check that the thumbnail is generated correctly + guard let thumbnailData = try? Data(contentsOf: thumbnailURL), + let thumbnail = UIImage(data: thumbnailData) else { + XCTFail("Invalid thumbnail") + return + } + + XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width) + XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height) + + // Check resulting video info + XCTAssertEqual(videoInfo.mimetype, "video/mp4") + XCTAssertEqual(videoInfo.blurhash, "K22PJZx^DgadWAbbMuRio$") + XCTAssertEqual(videoInfo.size, 1_431_959) + XCTAssertEqual(videoInfo.width, 1280) + XCTAssertEqual(videoInfo.height, 720) + XCTAssertEqual(videoInfo.duration, 30483) + + XCTAssertNotNil(videoInfo.thumbnailInfo) + XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg") + XCTAssertEqual(videoInfo.thumbnailInfo?.size, 33949) + XCTAssertEqual(videoInfo.thumbnailInfo?.width, 800) + XCTAssertEqual(videoInfo.thumbnailInfo?.height, 450) + } + + func testLandscapeImageProcessing() async { + guard let url = Bundle(for: Self.self).url(forResource: "landscape_test_image.jpg", withExtension: nil) else { + XCTFail("Failed retrieving test asset") + return + } + + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { + XCTFail("Failed processing asset") + return + } + + compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL) + + // Check resulting image info + XCTAssertEqual(imageInfo.mimetype, "image/jpeg") + XCTAssertEqual(imageInfo.blurhash, "K%I#.NofkC_4ayayxujsWB") + XCTAssertEqual(imageInfo.size, 3_305_795) + XCTAssertEqual(imageInfo.width, 6103) + XCTAssertEqual(imageInfo.height, 2621) + + XCTAssertNotNil(imageInfo.thumbnailInfo) + XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg") + XCTAssertEqual(imageInfo.thumbnailInfo?.size, 52159) + XCTAssertEqual(imageInfo.thumbnailInfo?.width, 600) + XCTAssertEqual(imageInfo.thumbnailInfo?.height, 257) + } + + func testPortraitImageProcessing() async { + guard let url = Bundle(for: Self.self).url(forResource: "portrait_test_image.jpg", withExtension: nil) else { + XCTFail("Failed retrieving test asset") + return + } + + guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url), + case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else { + XCTFail("Failed processing asset") + return + } + + compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL) + + // Check resulting image info + XCTAssertEqual(imageInfo.mimetype, "image/jpeg") + XCTAssertEqual(imageInfo.blurhash, "KdE:ess+RP^-n*RP%hWAV@") + XCTAssertEqual(imageInfo.size, 4_414_666) + XCTAssertEqual(imageInfo.width, 3024) + XCTAssertEqual(imageInfo.height, 4032) + + XCTAssertNotNil(imageInfo.thumbnailInfo) + XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg") + XCTAssertEqual(imageInfo.thumbnailInfo?.size, 156_948) + XCTAssertEqual(imageInfo.thumbnailInfo?.width, 450) + XCTAssertEqual(imageInfo.thumbnailInfo?.height, 600) + } + + // MARK: - Private + + private func compare(originalImageAt originalImageURL: URL, toConvertedImageAt convertedImageURL: URL, withThumbnailAt thumbnailURL: URL) { + 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() + } + + // Check that the file name is preserved + XCTAssertEqual(originalImageURL.lastPathComponent, convertedImageURL.lastPathComponent) + + // Check that new image is the same size as the original one + XCTAssertEqual(originalImage.size, convertedImage.size) + + // Check that the GPS data has been stripped + guard let imageSource = CGImageSourceCreateWithData(originalImageData as NSData, nil) else { + XCTFail("Invalid test asset") + return + } + + guard let originalMetadata: NSDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) else { + XCTFail("Test asset is expected to contain metadata") + return + } + + XCTAssertNotNil(originalMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)")) + + guard let convertedImageSource = CGImageSourceCreateWithData(convertedImageData as NSData, nil) else { + XCTFail("Invalid converted asset") + return + } + + guard let convertedMetadata: NSDictionary = CGImageSourceCopyPropertiesAtIndex(convertedImageSource, 0, nil) else { + XCTFail("Test asset is expected to contain metadata") + return + } + + XCTAssertNil(convertedMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)")) + + // Check that the thumbnail is generated correctly + guard let thumbnailData = try? Data(contentsOf: thumbnailURL), + let thumbnail = UIImage(data: thumbnailData) else { + XCTFail("Invalid thumbnail") + return + } + + XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width) + XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height) + } +}