Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Codable Unit Tests for ProductImageStatus, ProductImageAssetType and ProductOrVariationID #15253

Open
wants to merge 8 commits into
base: feat/save-product-image-upload-statuses-in-user-defaults
Choose a base branch
from
16 changes: 16 additions & 0 deletions Networking/Networking.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,9 @@
450106852399A7CB00E24722 /* TaxClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450106842399A7CB00E24722 /* TaxClass.swift */; };
4501068F2399B19500E24722 /* TaxRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4501068E2399B19500E24722 /* TaxRemote.swift */; };
450106912399B2C800E24722 /* TaxClassListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450106902399B2C800E24722 /* TaxClassListMapper.swift */; };
4507DDF42D6E74E400D2F3BC /* ProductImageAssetTypeCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4507DDF32D6E74E400D2F3BC /* ProductImageAssetTypeCodableTests.swift */; };
4507DDF62D6E770500D2F3BC /* PHAssetMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4507DDF52D6E770500D2F3BC /* PHAssetMock.swift */; };
4507DDF82D6E79F600D2F3BC /* ProductOrVariationIDCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4507DDF72D6E79F600D2F3BC /* ProductOrVariationIDCodableTests.swift */; };
450BFA6B2D52777500C2D1D7 /* WordPressMediaMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 450BFA6A2D52777500C2D1D7 /* WordPressMediaMapperTests.swift */; };
450BFA6D2D52790E00C2D1D7 /* media-library-with-empty-sizes.json in Resources */ = {isa = PBXBuildFile; fileRef = 450BFA6C2D52790200C2D1D7 /* media-library-with-empty-sizes.json */; };
451274A625276C82009911FF /* product-variation.json in Resources */ = {isa = PBXBuildFile; fileRef = 451274A525276C82009911FF /* product-variation.json */; };
Expand Down Expand Up @@ -483,6 +486,7 @@
4587D11F2D64D887001971E4 /* ProductOrVariationID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4587D11E2D64D886001971E4 /* ProductOrVariationID.swift */; };
458C6DE425AC72A1009B300D /* StoredProductSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 458C6DE325AC72A1009B300D /* StoredProductSettings.swift */; };
4595827C264D5B3F000A4413 /* ShippingLabelPackageSelected.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4595827B264D5B3F000A4413 /* ShippingLabelPackageSelected.swift */; };
459824A32D6E5094003D8263 /* ProductImageStatusCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 459824A22D6E5094003D8263 /* ProductImageStatusCodableTests.swift */; };
4599FC5824A624BD0056157A /* ProductTagListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4599FC5724A624BD0056157A /* ProductTagListMapper.swift */; };
4599FC5A24A626B70056157A /* ProductTagListMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4599FC5924A626B70056157A /* ProductTagListMapperTests.swift */; };
4599FC5C24A6276F0056157A /* product-tags-all.json in Resources */ = {isa = PBXBuildFile; fileRef = 4599FC5B24A6276F0056157A /* product-tags-all.json */; };
Expand Down Expand Up @@ -1636,6 +1640,9 @@
450106842399A7CB00E24722 /* TaxClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxClass.swift; sourceTree = "<group>"; };
4501068E2399B19500E24722 /* TaxRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxRemote.swift; sourceTree = "<group>"; };
450106902399B2C800E24722 /* TaxClassListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaxClassListMapper.swift; sourceTree = "<group>"; };
4507DDF32D6E74E400D2F3BC /* ProductImageAssetTypeCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageAssetTypeCodableTests.swift; sourceTree = "<group>"; };
4507DDF52D6E770500D2F3BC /* PHAssetMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHAssetMock.swift; sourceTree = "<group>"; };
4507DDF72D6E79F600D2F3BC /* ProductOrVariationIDCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductOrVariationIDCodableTests.swift; sourceTree = "<group>"; };
450BFA6A2D52777500C2D1D7 /* WordPressMediaMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WordPressMediaMapperTests.swift; sourceTree = "<group>"; };
450BFA6C2D52790200C2D1D7 /* media-library-with-empty-sizes.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "media-library-with-empty-sizes.json"; sourceTree = "<group>"; };
451274A525276C82009911FF /* product-variation.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-variation.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1696,6 +1703,7 @@
4587D11E2D64D886001971E4 /* ProductOrVariationID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductOrVariationID.swift; sourceTree = "<group>"; };
458C6DE325AC72A1009B300D /* StoredProductSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredProductSettings.swift; sourceTree = "<group>"; };
4595827B264D5B3F000A4413 /* ShippingLabelPackageSelected.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPackageSelected.swift; sourceTree = "<group>"; };
459824A22D6E5094003D8263 /* ProductImageStatusCodableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductImageStatusCodableTests.swift; sourceTree = "<group>"; };
4599FC5724A624BD0056157A /* ProductTagListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductTagListMapper.swift; sourceTree = "<group>"; };
4599FC5924A626B70056157A /* ProductTagListMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductTagListMapperTests.swift; sourceTree = "<group>"; };
4599FC5B24A6276F0056157A /* product-tags-all.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "product-tags-all.json"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2579,6 +2587,7 @@
isa = PBXGroup;
children = (
025F9A242B020F1900B91F1D /* MockURLProtocol.swift */,
4507DDF52D6E770500D2F3BC /* PHAssetMock.swift */,
);
path = Mocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -4055,6 +4064,9 @@
028F3F932B0DF9A800F8E227 /* OrderEncoderTests.swift */,
8646A9B62B4522E7001F606C /* BlazeForecastedImpressionsInputEncoderTests.swift */,
953477AA2CD678A500038AED /* MetaDataEncoderTests.swift */,
459824A22D6E5094003D8263 /* ProductImageStatusCodableTests.swift */,
4507DDF32D6E74E400D2F3BC /* ProductImageAssetTypeCodableTests.swift */,
4507DDF72D6E79F600D2F3BC /* ProductOrVariationIDCodableTests.swift */,
);
path = Encoder;
sourceTree = "<group>";
Expand Down Expand Up @@ -5560,12 +5572,14 @@
45551F142523E7FF007EF104 /* UserAgentTests.swift in Sources */,
CE21FB202C2ADAC500303832 /* GoogleAdsCampaignStatsMapperTests.swift in Sources */,
03EB998A2906AB0C00F06A39 /* JustInTimeMessagesRemoteTests.swift in Sources */,
459824A32D6E5094003D8263 /* ProductImageStatusCodableTests.swift in Sources */,
CCE5F39129F000AC00087332 /* SubscriptionListMapperTests.swift in Sources */,
DEDA8D9B2B05BEF80076BF0F /* CreateProductVariationTests.swift in Sources */,
DE34051D28BDF1C900CF0D97 /* JetpackConnectionURLMapperTests.swift in Sources */,
451A9836260B9DF90059D135 /* ShippingLabelPackagesMapperTests.swift in Sources */,
CE71E2392A4C594600DB5376 /* ProductsReportsRemoteTests.swift in Sources */,
453954CE2C90D66B00A3E64A /* MetaDataMapperTests.swift in Sources */,
4507DDF62D6E770500D2F3BC /* PHAssetMock.swift in Sources */,
EE338A0E294AF9BD00183934 /* ApplicationPasswordMapperTests.swift in Sources */,
02BDB83723EA9C4D00BCC63E /* String+HTMLTests.swift in Sources */,
74CF5E8421402C04000CED0A /* TopEarnerStatsRemoteTests.swift in Sources */,
Expand All @@ -5587,6 +5601,7 @@
45D685FC23D0C739005F87D0 /* ProductSkuMapperTests.swift in Sources */,
CEC4BF95234E7F34008D9195 /* RefundListMapperTests.swift in Sources */,
74A7B4BC217A807400E85A8B /* SiteSettingsMapperTests.swift in Sources */,
4507DDF82D6E79F600D2F3BC /* ProductOrVariationIDCodableTests.swift in Sources */,
311976E02602BD4B006AC56C /* SitePluginsMapperTests.swift in Sources */,
DE02ABB72B563F3A008E0AC4 /* BlazePaymentInfoMapperTests.swift in Sources */,
2661547B242DAC1C00A31661 /* ProductCategoriesRemoteTests.swift in Sources */,
Expand Down Expand Up @@ -5615,6 +5630,7 @@
B5C6FCC820A32E4800A4F8E4 /* DateFormatterWooTests.swift in Sources */,
74C8F06A20EEBC8C00B6EDC9 /* OrderMapperTests.swift in Sources */,
45152835257A8F490076B03C /* ProductAttributeListMapperTests.swift in Sources */,
4507DDF42D6E74E400D2F3BC /* ProductImageAssetTypeCodableTests.swift in Sources */,
077F39DA26A58ED700ABEADC /* SystemStatusRemoteTests.swift in Sources */,
0359EA1F27AAE4680048DE2D /* WCPayChargeMapperTests.swift in Sources */,
31D27C952602B737002EDB1D /* SitePluginsRemoteTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import XCTest
import Photos
@testable import Networking

final class ProductImageAssetTypeCodableTests: XCTestCase {

func test_uiImage_encodes_and_decodes_correctly() throws {
// Given
let assetType = ProductImageAssetType.uiImage(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR: We should move the key string values phAsset and uiImage from ProductImageAssetType into a constant and reuse it while encoding and decoding. This will help avoid mistakingly editing these key values.

The same comment applies for ProductImageStatus and ProductOrVariationID as well.

image: UIImage.checkmark,
filename: "test_image.png",
altText: "Test Alt Text"
)

// When
let data = try JSONEncoder().encode(assetType)
let decodedAssetType = try JSONDecoder().decode(ProductImageAssetType.self, from: data)

// Then
if case let .uiImage(decodedImage, decodedFilename, decodedAltText) = decodedAssetType {
XCTAssertNotNil(decodedImage.pngData(), "Decoded image data should not be nil")
XCTAssertEqual(decodedFilename, "test_image.png")
XCTAssertEqual(decodedAltText, "Test Alt Text")
} else {
XCTFail("Decoded asset type should be .uiImage")
}
}

func test_phAsset_decoding_fails_with_expected_error() throws {
// Given
let fakeLocalIdentifier = "this_is_a_fake_local_identifier"
let mockAsset = PHAssetMock(localIdentifier: fakeLocalIdentifier)
let assetType = ProductImageAssetType.phAsset(asset: mockAsset)

// Then
XCTAssertThrowsError(try {
let data = try JSONEncoder().encode(assetType)
_ = try JSONDecoder().decode(ProductImageAssetType.self, from: data)
}()) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertEqual(context.debugDescription, "No PHAsset found with localIdentifier \(fakeLocalIdentifier)")
} else {
XCTFail("Expected dataCorrupted error, got \(error)")
}
}
}

func test_decoding_unknown_asset_type_throwsError() throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func test_decoding_unknown_asset_type_throwsError() throws {
func test_decoding_unknown_asset_type_throws_error() throws {

// Given
let jsonData = """
{
"type": "unknownType"
}
""".data(using: .utf8)!

// Then
XCTAssertThrowsError(try JSONDecoder().decode(ProductImageAssetType.self, from: jsonData)) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertTrue(context.debugDescription.contains("Unknown type unknownType"))
} else {
XCTFail("Expected dataCorrupted error, but got: \(error)")
}
}
}

func test_invalid_Base64Data_for_UIImage_throwsError() throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func test_invalid_Base64Data_for_UIImage_throwsError() throws {
func test_invalid_base64Data_for_UIImage_throws_error() throws {

// Given
let jsonData = """
{
"type": "uiImage",
"imageData": "INVALID_BASE64_DATA",
"filename": "test.png",
"altText": "Test Alt"
}
""".data(using: .utf8)!

// Then
XCTAssertThrowsError(try JSONDecoder().decode(ProductImageAssetType.self, from: jsonData)) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertTrue(context.debugDescription.contains("Invalid image data"))
} else {
XCTFail("Expected dataCorrupted error, got \(error)")
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// Start of Selection
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be deleted.

import XCTest
import Photos
@testable import Networking

final class ProductImageStatusCodableTests: XCTestCase {

func test_uploading_encodes_and_decodes_correctly() throws {
// Given
let status = ProductImageStatus.uploading(
asset: .uiImage(image: UIImage.checkmark, filename: "test.png", altText: "Test Alt"),
siteID: 12345,
productID: .product(id: 999)
)

// When
let encodedData = try JSONEncoder().encode(status)
let decodedStatus = try JSONDecoder().decode(ProductImageStatus.self, from: encodedData)

// Then
guard case let .uploading(.uiImage(decodedImage, decodedFilename, decodedAltText), siteID, productID) = decodedStatus else {
return XCTFail("Decoded status should be .uploading with .uiImage asset")
}

XCTAssertNotNil(decodedImage.pngData(), "Decoded image data should not be nil")
XCTAssertEqual(decodedFilename, "test.png")
XCTAssertEqual(decodedAltText, "Test Alt")
XCTAssertEqual(siteID, 12345)
XCTAssertEqual(productID, .product(id: 999))
}

func test_uploading_phAsset_decoding_fails_with_expected_error() throws {
// Given
let fakeLocalIdentifier = "4321"
let mockAsset = PHAssetMock(localIdentifier: fakeLocalIdentifier)
let status = ProductImageStatus.uploading(
asset: .phAsset(asset: mockAsset),
siteID: 11111,
productID: .variation(productID: 43, variationID: 55)
)

// When
let encodedData = try JSONEncoder().encode(status)

// Then
XCTAssertThrowsError(try JSONDecoder().decode(ProductImageStatus.self, from: encodedData)) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertEqual(context.debugDescription, "No PHAsset found with localIdentifier \(fakeLocalIdentifier)")
} else {
XCTFail("Expected dataCorrupted error, but got: \(error)")
}
}
}

func test_remote_encodes_and_decodes_correctly() throws {
// Given
let testImage = ProductImage(
imageID: 111,
dateCreated: Date(),
dateModified: nil,
src: "https://example.com/image.png",
name: "Example Image",
alt: "Example Alt"
)
let status = ProductImageStatus.remote(
image: testImage,
siteID: 98765,
productID: .variation(productID: 1, variationID: 2)
)

// When
let encodedData = try JSONEncoder().encode(status)
let decodedStatus = try JSONDecoder().decode(ProductImageStatus.self, from: encodedData)

// Then
guard case let .remote(image, siteID, productID) = decodedStatus else {
return XCTFail("Decoded status should be .remote")
}

XCTAssertEqual(image.imageID, 111)
XCTAssertEqual(image.src, "https://example.com/image.png")
XCTAssertEqual(image.name, "Example Image")
XCTAssertEqual(image.alt, "Example Alt")
XCTAssertEqual(siteID, 98765)
XCTAssertEqual(productID, .variation(productID: 1, variationID: 2))
}

func test_uploadFailure_encodes_and_decodes_correctly() throws {
// Given
let error = NSError(domain: "TestErrorDomain", code: 404, userInfo: ["info": "not found"])
let status = ProductImageStatus.uploadFailure(
asset: .uiImage(image: UIImage.checkmark, filename: "failure.png", altText: nil),
error: error,
siteID: 55555,
productID: .product(id: 123)
)

// When
let encodedData = try JSONEncoder().encode(status)
let decodedStatus = try JSONDecoder().decode(ProductImageStatus.self, from: encodedData)

// Then
guard case let .uploadFailure(.uiImage(_, decodedFilename, decodedAltText), decodedError as NSError, siteID, productID) = decodedStatus else {
return XCTFail("Decoded status should be .uploadFailure with .uiImage asset")
}

XCTAssertEqual(decodedFilename, "failure.png")
XCTAssertNil(decodedAltText)
XCTAssertEqual(decodedError.domain, "TestErrorDomain")
XCTAssertEqual(decodedError.code, 404)
XCTAssertEqual(decodedError.userInfo["info"] as? String, "not found")
XCTAssertEqual(siteID, 55555)
XCTAssertEqual(productID, .product(id: 123))
}

func test_decoding_unknown_status_type_throwsError() throws {
// Given
let jsonString = """
{
"type": "unknownType",
"siteID": 1234,
"productID": {
"type": "product",
"id": 999
}
}
"""
let data = jsonString.data(using: .utf8)!

// Then
XCTAssertThrowsError(try JSONDecoder().decode(ProductImageStatus.self, from: data)) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertTrue(context.debugDescription.contains("Invalid type value"))
} else {
XCTFail("Expected dataCorrupted error, but got: \(error)")
}
}
}

func test_invalid_Base64Data_for_UIImage_throwsError() throws {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func test_invalid_Base64Data_for_UIImage_throwsError() throws {
func test_invalid_base64Data_for_UIImage_throws_error() throws {

// Given:
let jsonString = """
{
"type": "uploading",
"asset": {
"type": "uiImage",
"imageData": "INVALID_BASE64_DATA",
"filename": "base64",
"altText": "BASE64"
},
"siteID": 111,
"productID": {
"type": "product",
"id": 1
}
}
"""
let data = jsonString.data(using: .utf8)!

// When / Then
XCTAssertThrowsError(try JSONDecoder().decode(ProductImageStatus.self, from: data)) { error in
if case let DecodingError.dataCorrupted(context) = error {
XCTAssertTrue(context.debugDescription.contains("Invalid image data"))
} else {
XCTFail("Expected dataCorrupted error, got \(error)")
}
}
}
}
Loading