Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Modules/Sources/Fakes/NetworkingCore.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ extension NetworkingCore.OrderItem {
totalTax: .fake(),
attributes: .fake(),
addOns: .fake(),
image: .fake(),
parent: .fake(),
bundleConfiguration: .fake()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Codegen
import Foundation
import WooFoundation
import struct Alamofire.JSONEncoding
import struct NetworkingCore.JetpackSite
Copy link
Contributor

Choose a reason for hiding this comment

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

👀 I'm guessing this was automatically generated on rake generate, but I'm not sure why. Perhaps some other PR missed it? Since builds without the import, we could remove it from this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it was added by rake generate.

Since builds without the import, we could remove it from this PR.

Well.. it would re-appear with another rake generate so probably not much value in removing it here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just noticed that the test target won't run locally unless we add this import via rake generate

Undefined symbol: NetworkingCore.AlamofireNetwork.__allocating_init(credentials: NetworkingCore.Credentials?, selectedSite: Combine.AnyPublisher<NetworkingCore.JetpackSite?, Swift.Never>?, sessionManager: Alamofire.Session?) -> NetworkingCore.AlamofireNetwork

Must have sneaked through some other changes, still odd that CI has been passing.



extension Networking.AIProduct {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,7 @@ extension NetworkingCore.OrderItem {
totalTax: CopiableProp<String> = .copy,
attributes: CopiableProp<[OrderItemAttribute]> = .copy,
addOns: CopiableProp<[OrderItemProductAddOn]> = .copy,
image: NullableCopiableProp<OrderItemProductImage> = .copy,
parent: NullableCopiableProp<Int64> = .copy,
bundleConfiguration: CopiableProp<[OrderItemBundleItem]> = .copy
) -> NetworkingCore.OrderItem {
Expand All @@ -462,6 +463,7 @@ extension NetworkingCore.OrderItem {
let totalTax = totalTax ?? self.totalTax
let attributes = attributes ?? self.attributes
let addOns = addOns ?? self.addOns
let image = image ?? self.image
let parent = parent ?? self.parent
let bundleConfiguration = bundleConfiguration ?? self.bundleConfiguration

Expand All @@ -481,6 +483,7 @@ extension NetworkingCore.OrderItem {
totalTax: totalTax,
attributes: attributes,
addOns: addOns,
image: image,
parent: parent,
bundleConfiguration: bundleConfiguration
)
Expand Down
34 changes: 34 additions & 0 deletions Modules/Sources/NetworkingCore/Model/OrderItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab

public let addOns: [OrderItemProductAddOn]

public let image: OrderItemProductImage?

/// Item ID of parent `OrderItem`, if any.
///
/// An `OrderItem` can have a parent if, for example, it is a bundled item within a product bundle.
Expand Down Expand Up @@ -58,6 +60,7 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab
totalTax: String,
attributes: [OrderItemAttribute],
addOns: [OrderItemProductAddOn],
image: OrderItemProductImage?,
parent: Int64?,
bundleConfiguration: [OrderItemBundleItem]) {
self.itemID = itemID
Expand All @@ -75,6 +78,7 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab
self.totalTax = totalTax
self.attributes = attributes
self.addOns = addOns
self.image = image
self.parent = parent
self.bundleConfiguration = bundleConfiguration
}
Expand Down Expand Up @@ -123,6 +127,9 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab
forKey: .attributes)
.first(where: { $0.key == "_pao_ids" })?.value ?? []

// Order item product image
let image = try container.decodeIfPresent(OrderItemProductImage.self, forKey: .image)

// Product Bundle extension properties:
// If the order item is part of a product bundle, `bundledBy` is the parent order item (product bundle).
// If it's not a bundled item, the API returns an empty string for `bundledBy` and the value will be `nil`.
Expand All @@ -149,6 +156,7 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab
totalTax: totalTax,
attributes: attributes,
addOns: productAddOns,
image: image,
parent: bundledBy ?? compositeParent,
bundleConfiguration: [])
}
Expand All @@ -174,6 +182,10 @@ public struct OrderItem: Codable, Equatable, Hashable, Sendable, GeneratedFakeab
try container.encode(total, forKey: .total)
}

if let image = image {
try container.encode(image, forKey: .image)
}

if !bundleConfiguration.isEmpty {
try container.encode(bundleConfiguration, forKey: .bundleConfiguration)
}
Expand Down Expand Up @@ -204,6 +216,7 @@ extension OrderItem {
case bundledBy = "bundled_by"
case compositeParent = "composite_parent"
case bundleConfiguration = "bundle_configuration"
case image
}
}

Expand All @@ -222,3 +235,24 @@ private struct OrderItemProductAddOnContainer: Decodable {
let key: String
let value: [OrderItemProductAddOn]
}


// MARK: - Order Item Product Image
//
public struct OrderItemProductImage: Codable, Equatable, Hashable, Sendable {
public let src: String?

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.src = try? container.decodeIfPresent(String.self, forKey: .src)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(src, forKey: .src)
}

private enum CodingKeys: String, CodingKey {
case src
}
}
1 change: 1 addition & 0 deletions Modules/Sources/Yosemite/Model/Mocks/MockObjectGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ extension MockObjectGraph {
totalTax: "0",
attributes: [],
addOns: [],
image: nil,
parent: nil,
bundleConfiguration: []
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extension Storage.OrderItem: ReadOnlyConvertible {
totalTax: totalTax ?? "",
attributes: attributes,
addOns: addOns,
image: nil,
parent: parent?.int64Value,
bundleConfiguration: [])
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,25 @@ import Foundation
public struct POSOrderItem: Equatable, Hashable {
public let itemID: Int64
public let name: String
// periphery:ignore - Will be used for images
public let productID: Int64
// periphery:ignore - Will be used for images
public let variationID: Int64
public let quantity: Decimal
public let formattedPrice: String
public let formattedTotal: String
public let imageSrc: String?
public let attributes: [OrderItemAttribute]

public init(itemID: Int64,
name: String,
productID: Int64,
variationID: Int64,
quantity: Decimal,
formattedPrice: String,
formattedTotal: String,
imageSrc: String?,
attributes: [OrderItemAttribute]) {
self.itemID = itemID
self.name = name
self.productID = productID
self.variationID = variationID
self.quantity = quantity
self.formattedPrice = formattedPrice
self.formattedTotal = formattedTotal
self.imageSrc = imageSrc
self.attributes = attributes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,10 @@ struct POSOrderMapper {
return POSOrderItem(
itemID: orderItem.itemID,
name: orderItem.name,
productID: orderItem.productID,
variationID: orderItem.variationID,
quantity: orderItem.quantity,
formattedPrice: currencyFormatter.formatAmount(orderItem.price, with: currency) ?? "",
formattedTotal: currencyFormatter.formatAmount(orderItem.total, with: currency) ?? "",
imageSrc: orderItem.image?.src,
attributes: orderItem.attributes
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ private extension ProductInputTransformer {
totalTax: "",
attributes: [],
addOns: [],
image: nil,
parent: nil,
bundleConfiguration: input.bundleConfiguration.map {
switch $0.productOrVariation {
Expand Down
29 changes: 28 additions & 1 deletion Modules/Tests/NetworkingTests/Mapper/OrderListMapperTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,35 @@ class OrderListMapperTests: XCTestCase {
}
}
}
}

/// Verifies that OrderItem decoding is robust for various image field scenarios from WooCommerce API
///
func test_order_item_image_decoding_robustness() {
let orders = mapOrders(from: "order-item-image")
XCTAssertEqual(orders.count, 1)

let order = orders[0]
XCTAssertEqual(order.items.count, 4)

// Item 1: Product with valid image src
let item1 = order.items[0]
XCTAssertNotNil(item1.image)
XCTAssertEqual(item1.image?.src, "https://example.com/image.jpg")

// Item 2: Product with no image field
let item2 = order.items[1]
XCTAssertNil(item2.image)

// Item 3: Product with image but no src field
let item3 = order.items[2]
XCTAssertNotNil(item3.image)
XCTAssertNil(item3.image?.src)

// Item 4: Product with wrong src type
let item4 = order.items[3]
XCTAssertNil(item4.image?.src)
}
}

/// Private Methods.
///
Expand Down
138 changes: 138 additions & 0 deletions Modules/Tests/NetworkingTests/Responses/order-item-image.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{
"data": [
{
"id": 963,
"parent_id": 0,
"is_editable": true,
"needs_payment": true,
"needs_processing": true,
"number": "963",
"status": "processing",
"order_key": "abc123",
"currency": "USD",
"currency_symbol": "$",
"date_created_gmt": "2018-04-03T23:05:12",
"date_modified_gmt": "2018-04-03T23:05:14",
"discount_total": "30.00",
"discount_tax": "1.20",
"shipping_total": "0.00",
"shipping_tax": "0.00",
"cart_tax": "1.20",
"total": "31.20",
"total_tax": "1.20",
"customer_id": 11,
"customer_note": "",
"billing": {
"first_name": "Johnny",
"last_name": "Appleseed",
"company": "",
"address_1": "234 70th Street",
"address_2": "",
"city": "Niagara Falls",
"state": "NY",
"postcode": "14304",
"country": "US",
"email": "[email protected]",
"phone": "333-333-3333"
},
"shipping": {
"first_name": "Johnny",
"last_name": "Appleseed",
"company": "",
"address_1": "234 70th Street",
"address_2": "",
"city": "Niagara Falls",
"state": "NY",
"postcode": "14304",
"country": "US",
"email": "[email protected]",
"phone": "333-333-3333"
},
"shipping_lines": [],
"fee_lines": [],
"tax_lines": [],
"payment_method": "stripe",
"payment_method_title": "Credit Card (Stripe)",
"payment_url": "http://www.automattic.com",
"date_paid_gmt": "2018-04-03T23:05:14",
"date_completed_gmt": null,
"line_items": [
{
"id": 1,
"name": "Product with valid image src",
"product_id": 123,
"variation_id": 0,
"quantity": 1,
"price": 10.0,
"sku": "",
"subtotal": "10.00",
"subtotal_tax": "0.00",
"tax_class": "",
"taxes": [],
"total": "10.00",
"total_tax": "0.00",
"meta_data": [],
"image": {
"src": "https://example.com/image.jpg"
}
},
{
"id": 2,
"name": "Product with no image field",
"product_id": 124,
"variation_id": 0,
"quantity": 1,
"price": 10.0,
"sku": "",
"subtotal": "10.00",
"subtotal_tax": "0.00",
"tax_class": "",
"taxes": [],
"total": "10.00",
"total_tax": "0.00",
"meta_data": []
},
{
"id": 3,
"name": "Product with image but no src",
"product_id": 125,
"variation_id": 0,
"quantity": 1,
"price": 10.0,
"sku": "",
"subtotal": "10.00",
"subtotal_tax": "0.00",
"tax_class": "",
"taxes": [],
"total": "10.00",
"total_tax": "0.00",
"meta_data": [],
"image": {
"alt": "Product image alt text"
}
},
{
"id": 4,
"name": "Product with wrong src type",
"product_id": 126,
"variation_id": 0,
"quantity": 1,
"price": 10.0,
"sku": "",
"subtotal": "10.00",
"subtotal_tax": "0.00",
"tax_class": "",
"taxes": [],
"total": "10.00",
"total_tax": "0.00",
"meta_data": [],
"image": {
"src": 12345
}
}
],
"coupon_lines": [],
"refunds": []
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,8 @@ private extension POSOrderMapperTests {
quantity: quantity,
price: price,
subtotal: subtotal,
total: total
total: total,
image: nil
)
}

Expand Down
Loading