diff --git a/plugins/woocommerce-blocks/src/RestApi/Controllers/Products.php b/plugins/woocommerce-blocks/src/RestApi/Controllers/Products.php index ff78c4ff923..95e591b2a4d 100644 --- a/plugins/woocommerce-blocks/src/RestApi/Controllers/Products.php +++ b/plugins/woocommerce-blocks/src/RestApi/Controllers/Products.php @@ -219,7 +219,7 @@ class Products extends WC_REST_Products_Controller { 'has_options' => $product->has_options(), 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), - 'add_to_cart' => [ + 'add_to_cart' => (object) [ 'text' => $product->add_to_cart_text(), 'description' => $product->add_to_cart_description(), ], diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/ProductCollectionData.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/ProductCollectionData.php index abcef4ceb18..256680228c9 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/ProductCollectionData.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Controllers/ProductCollectionData.php @@ -140,7 +140,7 @@ class ProductCollectionData extends RestController { $counts = $filters->get_attribute_counts( $filter_request, [ $taxonomy ] ); foreach ( $counts as $key => $value ) { - $data['attribute_counts'][] = [ + $data['attribute_counts'][] = (object) [ 'term' => $key, 'count' => $value, ]; @@ -152,7 +152,7 @@ class ProductCollectionData extends RestController { $counts = $filters->get_attribute_counts( $request, $taxonomy__and_queries ); foreach ( $counts as $key => $value ) { - $data['attribute_counts'][] = [ + $data['attribute_counts'][] = (object) [ 'term' => $key, 'count' => $value, ]; @@ -166,7 +166,7 @@ class ProductCollectionData extends RestController { $data['rating_counts'] = []; foreach ( $counts as $key => $value ) { - $data['rating_counts'][] = [ + $data['rating_counts'][] = (object) [ 'rating' => $key, 'count' => $value, ]; diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartCouponSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartCouponSchema.php index 51e33246c88..5d2db72193d 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartCouponSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartCouponSchema.php @@ -90,7 +90,7 @@ class CartCouponSchema extends AbstractSchema { $total_discount_taxes = $cart->get_coupon_discount_tax_totals(); return [ 'code' => $coupon_code, - 'totals' => array_merge( + 'totals' => (object) array_merge( $this->get_store_currency_response(), [ 'total_discount' => $this->prepare_money_response( isset( $total_discounts[ $coupon_code ] ) ? $total_discounts[ $coupon_code ] : 0, wc_get_price_decimals() ), diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartItemSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartItemSchema.php index 23866fd9c30..055d584ac5f 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartItemSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartItemSchema.php @@ -89,30 +89,42 @@ class CartItemSchema extends AbstractSchema { 'items' => [ 'type' => 'object', 'properties' => [ - 'id' => [ + 'id' => [ 'description' => __( 'Image ID.', 'woo-gutenberg-products-block' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'src' => [ - 'description' => __( 'Image URL.', 'woo-gutenberg-products-block' ), + 'src' => [ + 'description' => __( 'Full size image URL.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'name' => [ + 'thumbnail' => [ + 'description' => __( 'Thumbnail URL.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => [ 'view', 'edit' ], + ], + 'srcset' => [ + 'description' => __( 'Thumbnail srcset for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'sizes' => [ + 'description' => __( 'Thumbnail sizes for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'name' => [ 'description' => __( 'Image name.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'alt' => [ + 'alt' => [ 'description' => __( 'Image alternative text.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], ], ], @@ -205,12 +217,12 @@ class CartItemSchema extends AbstractSchema { 'id' => $product->get_id(), 'quantity' => wc_stock_amount( $cart_item['quantity'] ), 'name' => $product->get_title(), - 'short_description' => $short_description, 'sku' => $product->get_sku(), + 'short_description' => $short_description, 'permalink' => $product->get_permalink(), 'images' => ( new ProductImages() )->images_to_array( $product ), 'variation' => $this->format_variation_data( $cart_item['variation'], $product ), - 'totals' => array_merge( + 'totals' => (object) array_merge( $this->get_store_currency_response(), [ 'line_subtotal' => $this->prepare_money_response( $cart_item['line_subtotal'], wc_get_price_decimals() ), @@ -248,7 +260,10 @@ class CartItemSchema extends AbstractSchema { $label = wc_attribute_label( str_replace( 'attribute_', '', $name ), $product ); } - $return[ $label ] = $value; + $return[] = [ + 'attribute' => $label, + 'value' => $value, + ]; } return $return; diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartSchema.php index 4c45392262d..e6695da4da8 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartSchema.php @@ -57,7 +57,7 @@ class CartSchema extends AbstractSchema { ], 'items_weight' => [ 'description' => __( 'Total weight (in grams) of all products in the cart.', 'woo-gutenberg-products-block' ), - 'type' => 'string', + 'type' => 'number', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], @@ -179,7 +179,7 @@ class CartSchema extends AbstractSchema { 'items_count' => $cart->get_cart_contents_count(), 'items_weight' => wc_get_weight( $cart->get_cart_contents_weight(), 'g' ), 'needs_shipping' => $cart->needs_shipping(), - 'totals' => array_merge( + 'totals' => (object) array_merge( $this->get_store_currency_response(), [ 'total_items' => $this->prepare_money_response( $cart->get_subtotal(), wc_get_price_decimals() ), diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartShippingRateSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartShippingRateSchema.php index 7b4b4b24556..f68cfadeeb1 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartShippingRateSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CartShippingRateSchema.php @@ -122,7 +122,7 @@ class CartShippingRateSchema extends AbstractSchema { ], 'price' => [ 'description' => __( 'Price of this shipping rate.', 'woo-gutenberg-products-block' ), - 'type' => 'boolean', + 'type' => 'string', 'context' => [ 'view', 'edit' ], 'readonly' => true, ], @@ -177,7 +177,7 @@ class CartShippingRateSchema extends AbstractSchema { */ public function get_item_response( $package ) { return [ - 'destination' => [ + 'destination' => (object) [ 'address_1' => $package['destination']['address_1'], 'address_2' => $package['destination']['address_2'], 'city' => $package['destination']['city'], diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CustomerSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CustomerSchema.php index 1b0bed56376..ecde37a4dc3 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CustomerSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/CustomerSchema.php @@ -166,7 +166,7 @@ class CustomerSchema extends AbstractSchema { public function get_item_response( $object ) { return [ 'id' => $object->get_id(), - 'billing_address' => [ + 'billing_address' => (object) [ 'first_name' => $object->get_billing_first_name(), 'last_name' => $object->get_billing_last_name(), 'company' => $object->get_billing_company(), @@ -179,7 +179,7 @@ class CustomerSchema extends AbstractSchema { 'email' => $object->get_billing_email(), 'phone' => $object->get_billing_phone(), ], - 'shipping_address' => [ + 'shipping_address' => (object) [ 'first_name' => $object->get_shipping_first_name(), 'last_name' => $object->get_shipping_last_name(), 'company' => $object->get_shipping_company(), diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderCouponSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderCouponSchema.php new file mode 100644 index 00000000000..f5330554ee6 --- /dev/null +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderCouponSchema.php @@ -0,0 +1,80 @@ + [ + 'description' => __( 'The coupons unique code.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'totals' => [ + 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), + 'type' => 'object', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + 'properties' => array_merge( + $this->get_store_currency_properties(), + [ + 'total_discount' => [ + 'description' => __( 'Total discount applied by this coupon.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'total_discount_tax' => [ + 'description' => __( 'Total tax removed due to discount applied by this coupon.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + ] + ), + ], + ]; + } + + /** + * Convert an order coupon to an object suitable for the response. + * + * @param \WC_Order_Item_Coupon $coupon Order coupon array. + * @return array + */ + public function get_item_response( \WC_Order_Item_Coupon $coupon ) { + return [ + 'code' => $coupon->get_code(), + 'totals' => (object) array_merge( + $this->get_store_currency_response(), + [ + 'total_discount' => $this->prepare_money_response( $coupon->get_discount(), wc_get_price_decimals() ), + 'total_discount_tax' => $this->prepare_money_response( $coupon->get_discount_tax(), wc_get_price_decimals(), PHP_ROUND_HALF_DOWN ), + ] + ), + ]; + } +} diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderItemSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderItemSchema.php index 3312316d007..e333ebc0dc7 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderItemSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderItemSchema.php @@ -50,19 +50,19 @@ class OrderItemSchema extends AbstractSchema { ], 'name' => [ 'description' => __( 'Product name.', 'woo-gutenberg-products-block' ), - 'type' => 'string', + 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'sku' => [ 'description' => __( 'Stock keeping unit, if applicable.', 'woo-gutenberg-products-block' ), - 'type' => 'string', + 'type' => [ 'string', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'permalink' => [ 'description' => __( 'Product URL.', 'woo-gutenberg-products-block' ), - 'type' => 'string', + 'type' => [ 'string', 'null' ], 'format' => 'uri', 'context' => [ 'view', 'edit' ], 'readonly' => true, @@ -75,30 +75,42 @@ class OrderItemSchema extends AbstractSchema { 'items' => [ 'type' => 'object', 'properties' => [ - 'id' => [ + 'id' => [ 'description' => __( 'Image ID.', 'woo-gutenberg-products-block' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'src' => [ - 'description' => __( 'Image URL.', 'woo-gutenberg-products-block' ), + 'src' => [ + 'description' => __( 'Full size image URL.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'name' => [ + 'thumbnail' => [ + 'description' => __( 'Thumbnail URL.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => [ 'view', 'edit' ], + ], + 'srcset' => [ + 'description' => __( 'Thumbnail srcset for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'sizes' => [ + 'description' => __( 'Thumbnail sizes for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'name' => [ 'description' => __( 'Image name.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], - 'alt' => [ + 'alt' => [ 'description' => __( 'Image alternative text.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], - 'readonly' => true, ], ], ], @@ -173,9 +185,7 @@ class OrderItemSchema extends AbstractSchema { } /** - * Convert a WooCommerce cart item to an object suitable for the response. - * - * @todo Variation is stored to meta - how can we gather for response? + * Convert an order item to an object suitable for the response. * * @param \WC_Order_Item_Product $line_item Order line item array. * @return array @@ -190,9 +200,9 @@ class OrderItemSchema extends AbstractSchema { 'name' => $has_product ? $product->get_title() : null, 'sku' => $has_product ? $product->get_sku() : null, 'permalink' => $has_product ? $product->get_permalink() : null, - 'images' => $has_product ? ( new ProductImages() )->images_to_array( $product ) : null, + 'images' => $has_product ? ( new ProductImages() )->images_to_array( $product ) : [], 'variation' => $has_product ? $this->format_variation_data( $line_item, $product ) : [], - 'totals' => array_merge( + 'totals' => (object) array_merge( $this->get_store_currency_response(), [ 'line_subtotal' => $this->prepare_money_response( $line_item->get_subtotal(), wc_get_price_decimals() ), @@ -239,7 +249,10 @@ class OrderItemSchema extends AbstractSchema { $label = wc_attribute_label( $name, $product ); } - $return[ $label ] = $value; + $return[] = [ + 'attribute' => $label, + 'value' => $value, + ]; } return $return; diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderSchema.php index 93eee3fe921..4bf681552b2 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/OrderSchema.php @@ -97,25 +97,25 @@ class OrderSchema extends AbstractSchema { ], 'date_paid' => [ 'description' => __( "The date the order was paid, in the site's timezone.", 'woo-gutenberg-products-block' ), - 'type' => 'date-time', + 'type' => [ 'date-time', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_paid_gmt' => [ 'description' => __( 'The date the order was paid, as GMT.', 'woo-gutenberg-products-block' ), - 'type' => 'date-time', + 'type' => [ 'date-time', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_completed' => [ 'description' => __( "The date the order was completed, in the site's timezone.", 'woo-gutenberg-products-block' ), - 'type' => 'date-time', + 'type' => [ 'date-time', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], 'date_completed_gmt' => [ 'description' => __( 'The date the order was completed, as GMT.', 'woo-gutenberg-products-block' ), - 'type' => 'date-time', + 'type' => [ 'date-time', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], @@ -268,7 +268,7 @@ class OrderSchema extends AbstractSchema { ], ], 'coupons' => [ - 'description' => __( 'List of applied cart coupons.', 'woo-gutenberg-products-block' ), + 'description' => __( 'List of applied coupons.', 'woo-gutenberg-products-block' ), 'type' => 'array', 'context' => [ 'view', 'edit' ], 'readonly' => true, @@ -287,76 +287,6 @@ class OrderSchema extends AbstractSchema { 'properties' => $this->force_schema_readonly( ( new OrderItemSchema() )->get_properties() ), ], ], - 'shipping_lines' => [ - 'description' => __( 'Shipping lines data.', 'woo-gutenberg-products-block' ), - 'type' => 'array', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => [ - 'description' => __( 'Item ID.', 'woo-gutenberg-products-block' ), - 'type' => 'integer', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'method_title' => [ - 'description' => __( 'Shipping method name.', 'woo-gutenberg-products-block' ), - 'type' => 'mixed', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'method_id' => [ - 'description' => __( 'Shipping method ID.', 'woo-gutenberg-products-block' ), - 'type' => 'mixed', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'instance_id' => [ - 'description' => __( 'Shipping instance ID.', 'woo-gutenberg-products-block' ), - 'type' => 'string', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'total' => [ - 'description' => __( 'Line total (after discounts).', 'woo-gutenberg-products-block' ), - 'type' => 'string', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'total_tax' => [ - 'description' => __( 'Line total tax (after discounts).', 'woo-gutenberg-products-block' ), - 'type' => 'string', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'taxes' => [ - 'description' => __( 'Line taxes.', 'woo-gutenberg-products-block' ), - 'type' => 'array', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - 'items' => [ - 'type' => 'object', - 'properties' => [ - 'id' => [ - 'description' => __( 'Tax rate ID.', 'woo-gutenberg-products-block' ), - 'type' => 'integer', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - 'total' => [ - 'description' => __( 'Tax total.', 'woo-gutenberg-products-block' ), - 'type' => 'string', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], - ], - ], - ], - ], - ], - ], 'totals' => [ 'description' => __( 'Total amounts provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), 'type' => 'object', @@ -461,8 +391,8 @@ class OrderSchema extends AbstractSchema { * @return array */ public function get_item_response( \WC_Order $order ) { - $order_item_schema = new OrderItemSchema(); - + $order_item_schema = new OrderItemSchema(); + $order_coupon_schema = new OrderCouponSchema(); return [ 'id' => $order->get_id(), 'number' => $order->get_order_number(), @@ -470,14 +400,14 @@ class OrderSchema extends AbstractSchema { 'order_key' => $order->get_order_key(), 'created_via' => $order->get_created_via(), 'prices_include_tax' => $order->get_prices_include_tax(), - 'events' => $this->get_events( $order ), - 'customer' => [ + 'events' => (object) $this->get_events( $order ), + 'customer' => (object) [ 'customer_id' => $order->get_customer_id(), 'customer_ip_address' => $order->get_customer_ip_address(), 'customer_user_agent' => $order->get_customer_user_agent(), ], 'customer_note' => $order->get_customer_note(), - 'billing_address' => [ + 'billing_address' => (object) [ 'first_name' => $order->get_billing_first_name(), 'last_name' => $order->get_billing_last_name(), 'company' => $order->get_billing_company(), @@ -490,7 +420,7 @@ class OrderSchema extends AbstractSchema { 'email' => $order->get_billing_email(), 'phone' => $order->get_billing_phone(), ], - 'shipping_address' => [ + 'shipping_address' => (object) [ 'first_name' => $order->get_shipping_first_name(), 'last_name' => $order->get_shipping_last_name(), 'company' => $order->get_shipping_company(), @@ -501,8 +431,9 @@ class OrderSchema extends AbstractSchema { 'postcode' => $order->get_shipping_postcode(), 'country' => $order->get_shipping_country(), ], + 'coupons' => array_values( array_map( [ $order_coupon_schema, 'get_item_response' ], $order->get_items( 'coupon' ) ) ), 'items' => array_values( array_map( [ $order_item_schema, 'get_item_response' ], $order->get_items( 'line_item' ) ) ), - 'totals' => array_merge( + 'totals' => (object) array_merge( $this->get_store_currency_response(), [ 'total_items' => $this->prepare_money_response( $order->get_subtotal(), wc_get_price_decimals() ), diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductCollectionDataSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductCollectionDataSchema.php index 3a8fef1ecab..eddc17137c3 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductCollectionDataSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductCollectionDataSchema.php @@ -31,7 +31,7 @@ class ProductCollectionDataSchema extends AbstractSchema { return [ 'price_range' => [ 'description' => __( 'Min and max prices found in collection of products, provided using the smallest unit of the currency.', 'woo-gutenberg-products-block' ), - 'type' => 'object', + 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => array_merge( @@ -54,7 +54,7 @@ class ProductCollectionDataSchema extends AbstractSchema { ], 'attribute_counts' => [ 'description' => __( 'Returns number of products within attribute terms.', 'woo-gutenberg-products-block' ), - 'type' => 'array', + 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ @@ -77,7 +77,7 @@ class ProductCollectionDataSchema extends AbstractSchema { ], 'rating_counts' => [ 'description' => __( 'Returns number of products with each average rating.', 'woo-gutenberg-products-block' ), - 'type' => 'array', + 'type' => [ 'array', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'items' => [ @@ -109,7 +109,7 @@ class ProductCollectionDataSchema extends AbstractSchema { */ public function get_item_response( $data ) { return [ - 'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? array_merge( + 'price_range' => ! is_null( $data['min_price'] ) && ! is_null( $data['max_price'] ) ? (object) array_merge( $this->get_store_currency_response(), [ 'min_price' => $this->prepare_money_response( $data['min_price'], wc_get_price_decimals() ), diff --git a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductSchema.php b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductSchema.php index 0f826a55e93..1f199a64e5f 100644 --- a/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductSchema.php +++ b/plugins/woocommerce-blocks/src/RestApi/StoreApi/Schemas/ProductSchema.php @@ -98,7 +98,7 @@ class ProductSchema extends AbstractSchema { ], 'price_range' => [ 'description' => __( 'Price range, if applicable.', 'woo-gutenberg-products-block' ), - 'type' => 'object', + 'type' => [ 'object', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, 'properties' => [ @@ -138,23 +138,39 @@ class ProductSchema extends AbstractSchema { 'items' => [ 'type' => 'object', 'properties' => [ - 'id' => [ + 'id' => [ 'description' => __( 'Image ID.', 'woo-gutenberg-products-block' ), 'type' => 'integer', 'context' => [ 'view', 'edit' ], ], - 'src' => [ - 'description' => __( 'Image URL.', 'woo-gutenberg-products-block' ), + 'src' => [ + 'description' => __( 'Full size image URL.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'format' => 'uri', 'context' => [ 'view', 'edit' ], ], - 'name' => [ + 'thumbnail' => [ + 'description' => __( 'Thumbnail URL.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'format' => 'uri', + 'context' => [ 'view', 'edit' ], + ], + 'srcset' => [ + 'description' => __( 'Thumbnail srcset for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'sizes' => [ + 'description' => __( 'Thumbnail sizes for responsive images.', 'woo-gutenberg-products-block' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'name' => [ 'description' => __( 'Image name.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], ], - 'alt' => [ + 'alt' => [ 'description' => __( 'Image alternative text.', 'woo-gutenberg-products-block' ), 'type' => 'string', 'context' => [ 'view', 'edit' ], @@ -182,7 +198,7 @@ class ProductSchema extends AbstractSchema { ], 'low_stock_remaining' => [ 'description' => __( 'Quantity left in stock if stock is low, or null if not applicable.', 'woo-gutenberg-products-block' ), - 'type' => 'integer', + 'type' => [ 'integer', 'null' ], 'context' => [ 'view', 'edit' ], 'readonly' => true, ], @@ -224,7 +240,7 @@ class ProductSchema extends AbstractSchema { 'sku' => $product->get_sku(), 'description' => apply_filters( 'woocommerce_short_description', $product->get_short_description() ? $product->get_short_description() : wc_trim_string( $product->get_description(), 400 ) ), 'on_sale' => $product->is_on_sale(), - 'prices' => $this->get_prices( $product ), + 'prices' => (object) $this->get_prices( $product ), 'average_rating' => $product->get_average_rating(), 'review_count' => $product->get_review_count(), 'images' => ( new ProductImages() )->images_to_array( $product ), @@ -232,7 +248,7 @@ class ProductSchema extends AbstractSchema { 'is_purchasable' => $product->is_purchasable(), 'is_in_stock' => $product->is_in_stock(), 'low_stock_remaining' => $this->get_low_stock_remaining( $product ), - 'add_to_cart' => [ + 'add_to_cart' => (object) [ 'text' => $product->add_to_cart_text(), 'description' => $product->add_to_cart_description(), ], @@ -274,7 +290,7 @@ class ProductSchema extends AbstractSchema { * Get price range from certain product types. * * @param \WC_Product $product Product instance. - * @return array|null + * @return object|null */ protected function get_price_range( \WC_Product $product ) { $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); @@ -283,7 +299,7 @@ class ProductSchema extends AbstractSchema { $prices = $product->get_variation_prices( true ); if ( min( $prices['price'] ) !== max( $prices['price'] ) ) { - return [ + return (object) [ 'min_amount' => $this->prepare_money_response( min( $prices['price'] ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $prices['price'] ), wc_get_price_decimals() ), ]; @@ -301,7 +317,7 @@ class ProductSchema extends AbstractSchema { } if ( ! empty( $child_prices ) ) { - return [ + return (object) [ 'min_amount' => $this->prepare_money_response( min( $child_prices ), wc_get_price_decimals() ), 'max_amount' => $this->prepare_money_response( max( $child_prices ), wc_get_price_decimals() ), ]; diff --git a/plugins/woocommerce-blocks/src/RestApi/Utilities/ProductImages.php b/plugins/woocommerce-blocks/src/RestApi/Utilities/ProductImages.php index d469a640a5c..64ab1ce416f 100644 --- a/plugins/woocommerce-blocks/src/RestApi/Utilities/ProductImages.php +++ b/plugins/woocommerce-blocks/src/RestApi/Utilities/ProductImages.php @@ -43,11 +43,11 @@ class ProductImages { $thumbnail = wp_get_attachment_image_src( $attachment_id, 'woocommerce_thumbnail' ); $images[] = array( - 'id' => $attachment_id, + 'id' => (int) $attachment_id, 'src' => current( $attachment ), 'thumbnail' => current( $thumbnail ), - 'srcset' => wp_get_attachment_image_srcset( $attachment_id, 'full' ), - 'sizes' => wp_get_attachment_image_sizes( $attachment_id, 'full' ), + 'srcset' => (string) wp_get_attachment_image_srcset( $attachment_id, 'full' ), + 'sizes' => (string) wp_get_attachment_image_sizes( $attachment_id, 'full' ), 'name' => get_the_title( $attachment_id ), 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), ); diff --git a/plugins/woocommerce-blocks/tests/php/Helpers/TestValidateSchema.php b/plugins/woocommerce-blocks/tests/php/Helpers/TestValidateSchema.php new file mode 100644 index 00000000000..fa7c91e26ad --- /dev/null +++ b/plugins/woocommerce-blocks/tests/php/Helpers/TestValidateSchema.php @@ -0,0 +1,132 @@ +validate = new ValidateSchema( + [ + 'properties' => [ + 'test_number' => [ + 'type' => 'number', + ], + 'test_string' => [ + 'type' => 'string', + ], + 'test_integer' => [ + 'type' => 'integer', + ], + 'test_object' => [ + 'type' => 'object', + 'properties' => [ + 'property_1' => [ + 'type' => 'string', + ], + 'property_2' => [ + 'type' => 'string', + ], + ], + ], + 'test_array' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'property_1' => [ + 'type' => 'string', + ], + 'property_2' => [ + 'type' => 'string', + ], + ], + ], + ], + 'test_integer_or_null' => [ + 'type' => [ 'null', 'integer' ], + ], + ], + ] + ); + } + + /** + * Validate an object. + */ + public function test_get_diff_from_valid_object() { + $test_object = (object) [ + 'test_number' => 1.2, + 'test_string' => 'Hello', + 'test_integer' => 1, + 'test_object' => (object) [ + 'property_1' => 'Prop 1', + 'property_2' => 'Prop 2', + ], + 'test_array' => [ + (object) [ + 'property_1' => 'Prop 1', + 'property_2' => 'Prop 2', + ], + ], + 'test_integer_or_null' => null, + ]; + + $diff = $this->validate->get_diff_from_object( $test_object ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + } + + /** + * Validate an object. + */ + public function test_get_diff_from_invalid_object() { + $test_object = (object) [ + 'test_number' => 'Invalid', + 'test_string' => 666, + 'test_integer' => 'Nope', + 'test_object' => (object) [ + 'property_1' => 1, + 'property_2' => 2, + ], + 'test_array' => [ + (object) [ + 'property_1' => 1, + 'invalid_key' => 2, + ], + ], + 'test_integer_or_null' => 'string', + ]; + + $diff = $this->validate->get_diff_from_object( $test_object ); + + $this->assertContains( 'test_array:property_2', $diff['missing'], print_r( $diff['missing'], true ) ); + $this->assertContains( 'test_array:invalid_key', $diff['no_schema'], print_r( $diff['no_schema'], true ) ); + + $this->assertContains( 'test_number (string, expected number)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + $this->assertContains( 'test_string (integer, expected string)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + $this->assertContains( 'test_integer (string, expected integer)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + $this->assertContains( 'test_number (string, expected number)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + $this->assertContains( 'test_array:property_1 (integer, expected string)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + $this->assertContains( 'test_integer_or_null (string, expected null, integer)', $diff['invalid_type'], print_r( $diff['invalid_type'], true ) ); + } +} diff --git a/plugins/woocommerce-blocks/tests/php/Helpers/ValidateSchema.php b/plugins/woocommerce-blocks/tests/php/Helpers/ValidateSchema.php new file mode 100644 index 00000000000..00e20a4855c --- /dev/null +++ b/plugins/woocommerce-blocks/tests/php/Helpers/ValidateSchema.php @@ -0,0 +1,115 @@ +schema = $schema; + } + + /** + * Validate properties and return diff. + * + * @param array|object $object Object to compare. + * @param array $schema Schema to find nested properties under. + * @param string $prefix Prefix to append to diff property names. + * @return array + */ + public function get_diff_from_object( $object, $schema = null, $prefix = '' ) { + $missing = []; + $invalid_type = []; + $no_schema = []; + + if ( is_null( $schema ) ) { + $schema = $this->schema['properties']; + } + + if ( ! is_array( $object ) && ! is_object( $object ) ) { + return []; + } + + $object = (array) $object; + $no_schema_diffs = array_diff( array_keys( (array) $object ), array_keys( $schema ) ); + foreach ( $no_schema_diffs as $no_schema_diff ) { + $no_schema[] = $prefix . $no_schema_diff; + } + + foreach ( $schema as $property_name => $property_schema ) { + // Validate property is set in object. Avoids isset in case value is NULL. + if ( ( is_array( $object ) && ! array_key_exists( $property_name, $object ) ) || ( is_object( $object ) && ! property_exists( $object, $property_name ) ) ) { + $missing[] = $prefix . $property_name; + continue; + } + + // Validate type. + $type = gettype( $object[ $property_name ] ); + if ( $type && ! $this->validate_type( $type, $property_schema['type'] ) ) { + $expected = is_array( $property_schema['type'] ) ? implode( ', ', $property_schema['type'] ) : $property_schema['type']; + $invalid_type[] = $prefix . $property_name . " ({$type}, expected {$expected})"; + continue; + } + + // Validate nested props. + if ( isset( $property_schema['items']['properties'] ) ) { + $nested_value = is_array( $object[ $property_name ] ) ? current( $object[ $property_name ] ) : $object[ $property_name ]; + + if ( $nested_value ) { + $diff = $this->get_diff_from_object( + $nested_value, + $property_schema['items']['properties'], + $prefix . $property_name . ':' + ); + $missing = isset( $diff['missing'] ) ? array_merge( $missing, $diff['missing'] ) : $missing; + $invalid_type = isset( $diff['invalid_type'] ) ? array_merge( $invalid_type, $diff['invalid_type'] ) : $invalid_type; + $no_schema = isset( $diff['no_schema'] ) ? array_merge( $no_schema, $diff['no_schema'] ) : $no_schema; + } + } + } + + return array_filter( + [ + 'missing' => array_values( array_filter( $missing ) ), + 'invalid_type' => array_values( array_filter( $invalid_type ) ), + 'no_schema' => array_values( array_filter( $no_schema ) ), + ] + ); + } + + /** + * Validate type and return if it matches. + * + * @param string $type Type to check. + * @param string|array $expected Expected types. + * @return boolean + */ + protected function validate_type( $type, $expected ) { + $type = strtolower( $type ); + $expected = is_array( $expected ) ? $expected : [ $expected ]; + + if ( in_array( 'number', $expected ) ) { + $expected = array_merge( $expected, [ 'double', 'float', 'integer' ] ); + } + + return in_array( $type, $expected, true ); + } +} diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributeTerms.php b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributeTerms.php index 8c23358dc76..d9af4fdcb38 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributeTerms.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributeTerms.php @@ -59,11 +59,11 @@ class ProductAttributeTerms extends WC_REST_Unit_Test_Case { wp_set_current_user( $this->user ); $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/attributes/' . $this->attr_color['attribute_id'] . '/terms' ); - $response = $this->server->dispatch( $request ); - $response_terms = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 3, count( $response_terms ) ); - $term = $response_terms[0]; + $this->assertEquals( 3, count( $data ) ); + $term = $data[0]; $this->assertArrayHasKey( 'attribute', $term ); $attribute = $term['attribute']; $this->assertArrayHasKey( 'id', $attribute ); @@ -105,9 +105,9 @@ class ProductAttributeTerms extends WC_REST_Unit_Test_Case { wp_set_current_user( $this->contributor ); $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/attributes/' . $this->attr_size['attribute_id'] . '/terms' ); - $response = $this->server->dispatch( $request ); - $response_terms = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 4, count( $response_terms ) ); + $this->assertEquals( 4, count( $data ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributes.php b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributes.php index 96b7fd5e1ba..9cbe688fa34 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributes.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductAttributes.php @@ -59,11 +59,11 @@ class ProductAttributes extends WC_REST_Unit_Test_Case { wp_set_current_user( $this->user ); $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/attributes' ); - $response = $this->server->dispatch( $request ); - $response_attributes = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 2, count( $response_attributes ) ); - $attribute = $response_attributes[0]; + $this->assertEquals( 2, count( $data ) ); + $attribute = $data[0]; $this->assertArrayHasKey( 'id', $attribute ); $this->assertArrayHasKey( 'name', $attribute ); $this->assertArrayHasKey( 'slug', $attribute ); @@ -104,10 +104,10 @@ class ProductAttributes extends WC_REST_Unit_Test_Case { wp_set_current_user( $this->contributor ); $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/attributes/' . $this->attr_size['attribute_id'] ); - $response = $this->server->dispatch( $request ); - $attribute = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( $this->attr_size['attribute_id'], $attribute['id'] ); - $this->assertEquals( $this->attr_size['attribute_name'], $attribute['name'] ); + $this->assertEquals( $this->attr_size['attribute_id'], $data['id'] ); + $this->assertEquals( $this->attr_size['attribute_name'], $data['name'] ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductCategories.php b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductCategories.php index 27c3c34ef6a..26ca7b51fac 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductCategories.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/ProductCategories.php @@ -79,10 +79,10 @@ class ProductCategories extends WC_REST_Unit_Test_Case { wp_set_current_user( $this->user ); $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/categories' ); - $response = $this->server->dispatch( $request ); - $categories = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 4, count( $categories ) ); // Three created and `uncategorized`. + $this->assertEquals( 4, count( $data ) ); // Three created and `uncategorized`. } /** @@ -120,10 +120,10 @@ class ProductCategories extends WC_REST_Unit_Test_Case { $request = new WP_REST_Request( 'GET', $this->endpoint . '/products/categories/' . $this->categories['parent']['term_id'] ); $response = $this->server->dispatch( $request ); - $category = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( $category['name'], 'Parent Category' ); - $this->assertEquals( $category['parent'], 0 ); - $this->assertEquals( $category['count'], 2 ); + $this->assertEquals( $data['name'], 'Parent Category' ); + $this->assertEquals( $data['parent'], 0 ); + $this->assertEquals( $data['count'], 2 ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/Products.php b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/Products.php index e092653ca02..10be7fb0a86 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/Products.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/Controllers/Products.php @@ -106,11 +106,10 @@ class Products extends WC_REST_Unit_Test_Case { sleep( 1 ); // So both products have different timestamps. $product = ProductHelper::create_simple_product(); $response = $this->server->dispatch( new WP_REST_Request( 'GET', '/wc/blocks/products' ) ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - - $this->assertEquals( 6, count( $products ) ); + $this->assertEquals( 6, count( $data ) ); } /** @@ -160,13 +159,13 @@ class Products extends WC_REST_Unit_Test_Case { $request->set_param( 'orderby', 'price' ); $request->set_param( 'order', 'asc' ); $response = $this->server->dispatch( $request ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 6, count( $products ) ); + $this->assertEquals( 6, count( $data ) ); - $this->assertEquals( 'Dummy Product', $products[1]['name'] ); - $this->assertEquals( '10', $products[1]['price'] ); + $this->assertEquals( 'Dummy Product', $data[1]['name'] ); + $this->assertEquals( '10', $data[1]['price'] ); } /** @@ -202,11 +201,11 @@ class Products extends WC_REST_Unit_Test_Case { $request = new WP_REST_REQUEST( 'GET', '/wc/blocks/products' ); $request->set_query_params( $query_params ); $response = $this->server->dispatch( $request ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 5, count( $products ) ); - $this->assertEquals( 'Visible Product', $products[0]['name'] ); + $this->assertEquals( 5, count( $data ) ); + $this->assertEquals( 'Visible Product', $data[0]['name'] ); $query_params = array( 'catalog_visibility' => 'catalog', @@ -216,11 +215,11 @@ class Products extends WC_REST_Unit_Test_Case { $request = new WP_REST_REQUEST( 'GET', '/wc/blocks/products' ); $request->set_query_params( $query_params ); $response = $this->server->dispatch( $request ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 6, count( $products ) ); - $this->assertEquals( 'Dummy Product', $products[0]['name'] ); + $this->assertEquals( 6, count( $data ) ); + $this->assertEquals( 'Dummy Product', $data[0]['name'] ); $query_params = array( 'catalog_visibility' => 'search', @@ -230,11 +229,11 @@ class Products extends WC_REST_Unit_Test_Case { $request = new WP_REST_REQUEST( 'GET', '/wc/blocks/products' ); $request->set_query_params( $query_params ); $response = $this->server->dispatch( $request ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 6, count( $products ) ); - $this->assertEquals( 'Dummy Product', $products[0]['name'] ); + $this->assertEquals( 6, count( $data ) ); + $this->assertEquals( 'Dummy Product', $data[0]['name'] ); $query_params = array( 'catalog_visibility' => 'hidden', @@ -242,11 +241,11 @@ class Products extends WC_REST_Unit_Test_Case { $request = new WP_REST_REQUEST( 'GET', '/wc/blocks/products' ); $request->set_query_params( $query_params ); $response = $this->server->dispatch( $request ); - $products = $response->get_data(); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 1, count( $products ) ); - $this->assertEquals( 'Hidden Product', $products[0]['name'] ); + $this->assertEquals( 1, count( $data ) ); + $this->assertEquals( 'Hidden Product', $data[0]['name'] ); } /** @@ -263,11 +262,11 @@ class Products extends WC_REST_Unit_Test_Case { $request->set_param( 'category', $cats ); $request->set_param( 'category_operator', 'in' ); - $response = $this->server->dispatch( $request ); - $response_products = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 3, count( $response_products ) ); + $this->assertEquals( 3, count( $data ) ); } /** @@ -284,11 +283,11 @@ class Products extends WC_REST_Unit_Test_Case { $request->set_param( 'category', $cats ); $request->set_param( 'category_operator', 'and' ); - $response = $this->server->dispatch( $request ); - $response_products = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 1, count( $response_products ) ); + $this->assertEquals( 1, count( $data ) ); } /** @@ -305,10 +304,10 @@ class Products extends WC_REST_Unit_Test_Case { $request->set_param( 'category', $cats ); $request->set_param( 'category_operator', 'and' ); - $response = $this->server->dispatch( $request ); - $response_products = $response->get_data(); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 1, count( $response_products ) ); + $this->assertEquals( 1, count( $data ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php index 30113f0f3f4..5a4dbfe8c37 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Cart.php @@ -10,6 +10,8 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use \WC_Helper_Coupon as CouponHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Cart Controller Tests. @@ -38,11 +40,14 @@ class Cart extends TestCase { $this->products[1]->set_regular_price( 10 ); $this->products[1]->save(); + $this->coupon = CouponHelper::create_coupon(); + wc_empty_cart(); - $this->keys = []; + $this->keys = []; $this->keys[] = wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); $this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + wc()->cart->apply_coupon( $this->coupon->get_code() ); } /** @@ -66,18 +71,18 @@ class Cart extends TestCase { $this->assertEquals( false, $data['needs_shipping'] ); $this->assertEquals( '30', $data['items_weight'] ); - $this->assertEquals( 'GBP', $data['totals']['currency_code'] ); - $this->assertEquals( 2, $data['totals']['currency_minor_unit'] ); - $this->assertEquals( '3000', $data['totals']['total_items'] ); - $this->assertEquals( '0', $data['totals']['total_items_tax'] ); - $this->assertEquals( '0', $data['totals']['total_fees'] ); - $this->assertEquals( '0', $data['totals']['total_fees_tax'] ); - $this->assertEquals( '0', $data['totals']['total_discount'] ); - $this->assertEquals( '0', $data['totals']['total_discount_tax'] ); - $this->assertEquals( '0', $data['totals']['total_shipping'] ); - $this->assertEquals( '0', $data['totals']['total_shipping_tax'] ); - $this->assertEquals( '0', $data['totals']['total_tax'] ); - $this->assertEquals( '3000', $data['totals']['total_price'] ); + $this->assertEquals( 'GBP', $data['totals']->currency_code ); + $this->assertEquals( 2, $data['totals']->currency_minor_unit ); + $this->assertEquals( '3000', $data['totals']->total_items ); + $this->assertEquals( '0', $data['totals']->total_items_tax ); + $this->assertEquals( '0', $data['totals']->total_fees ); + $this->assertEquals( '0', $data['totals']->total_fees_tax ); + $this->assertEquals( '100', $data['totals']->total_discount ); + $this->assertEquals( '0', $data['totals']->total_discount_tax ); + $this->assertEquals( '0', $data['totals']->total_shipping ); + $this->assertEquals( '0', $data['totals']->total_shipping_tax ); + $this->assertEquals( '0', $data['totals']->total_tax ); + $this->assertEquals( '2900', $data['totals']->total_price ); } /** @@ -101,11 +106,26 @@ class Cart extends TestCase { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\Cart(); $cart = wc()->cart; $response = $controller->prepare_item_for_response( $cart, [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'items_count', $response->get_data() ); - $this->assertArrayHasKey( 'items', $response->get_data() ); - $this->assertArrayHasKey( 'needs_shipping', $response->get_data() ); - $this->assertArrayHasKey( 'items_weight', $response->get_data() ); - $this->assertArrayHasKey( 'totals', $response->get_data() ); + $this->assertArrayHasKey( 'items_count', $data ); + $this->assertArrayHasKey( 'items', $data ); + $this->assertArrayHasKey( 'needs_shipping', $data ); + $this->assertArrayHasKey( 'items_weight', $data ); + $this->assertArrayHasKey( 'totals', $data ); + } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\Cart(); + $cart = wc()->cart; + $response = $controller->prepare_item_for_response( $cart, [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartCoupons.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartCoupons.php index b531f895964..7e9b1dc29bb 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartCoupons.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartCoupons.php @@ -11,6 +11,7 @@ use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; use \WC_Helper_Coupon as CouponHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Cart Coupons Controller Tests. @@ -62,8 +63,8 @@ class CartCoupons extends TestCase { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( $this->coupon->get_code(), $data['code'] ); - $this->assertEquals( '0', $data['totals']['total_discount'] ); - $this->assertEquals( '0', $data['totals']['total_discount_tax'] ); + $this->assertEquals( '0', $data['totals']->total_discount ); + $this->assertEquals( '0', $data['totals']->total_discount_tax ); } /** @@ -162,8 +163,22 @@ class CartCoupons extends TestCase { public function test_prepare_item_for_response() { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartCoupons(); $response = $controller->prepare_item_for_response( $this->coupon->get_code(), [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'code', $response->get_data() ); - $this->assertArrayHasKey( 'totals', $response->get_data() ); + $this->assertArrayHasKey( 'code', $data ); + $this->assertArrayHasKey( 'totals', $data ); + } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartCoupons(); + $response = $controller->prepare_item_for_response( $this->coupon->get_code(), [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartItems.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartItems.php index 2a5b6b5710d..05b4688b908 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartItems.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartItems.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Cart Controller Tests. @@ -19,6 +20,8 @@ class CartItems extends TestCase { * Setup test products data. Called before every test. */ public function setUp() { + global $wpdb; + parent::setUp(); wp_set_current_user( 0 ); @@ -27,22 +30,28 @@ class CartItems extends TestCase { $this->products = []; - // Create some test products. + // Create a test simple product. $this->products[0] = ProductHelper::create_simple_product( false ); $this->products[0]->set_weight( 10 ); $this->products[0]->set_regular_price( 10 ); $this->products[0]->save(); - $this->products[1] = ProductHelper::create_simple_product( false ); + $image_url = media_sideload_image( 'http://cldup.com/Dr1Bczxq4q.png', $this->products[0]->get_id(), '', 'src' ); + $image_id = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid = %s", $image_url ) ); + $this->products[0]->set_image_id( $image_id[0] ); + $this->products[0]->save(); + + // Create a test variable product. + $this->products[1] = ProductHelper::create_variation_product( false ); $this->products[1]->set_weight( 10 ); $this->products[1]->set_regular_price( 10 ); $this->products[1]->save(); wc_empty_cart(); - $this->keys = []; + $this->keys = []; $this->keys[] = wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); - $this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + $this->keys[] = wc()->cart->add_to_cart( $this->products[1]->get_id(), 1, current( $this->products[1]->get_children() ), [ 'size' => 'small' ] ); } /** @@ -79,8 +88,8 @@ class CartItems extends TestCase { $this->assertEquals( $this->products[0]->get_sku(), $data['sku'] ); $this->assertEquals( $this->products[0]->get_permalink(), $data['permalink'] ); $this->assertEquals( 2, $data['quantity'] ); - $this->assertEquals( '2000', $data['totals']['line_subtotal'] ); - $this->assertEquals( '2000', $data['totals']['line_total'] ); + $this->assertEquals( '2000', $data['totals']->line_subtotal ); + $this->assertEquals( '2000', $data['totals']->line_total ); $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/XXX815416f775098fe977004015c6193' ); $response = $this->server->dispatch( $request ); @@ -135,7 +144,6 @@ class CartItems extends TestCase { ) ); $response = $this->server->dispatch( $request ); - $data = $response->get_data(); $this->assertEquals( 403, $response->get_status() ); } @@ -161,14 +169,14 @@ class CartItems extends TestCase { * Test delete item. */ public function test_delete_item() { - $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] ); + $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $this->assertEquals( 204, $response->get_status() ); $this->assertEmpty( $data ); - $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] ); + $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items/' . $this->keys[0] ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); @@ -179,7 +187,7 @@ class CartItems extends TestCase { * Test delete all items. */ public function test_delete_items() { - $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items' ); + $request = new WP_REST_Request( 'DELETE', '/wc/store/cart/items' ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); @@ -218,15 +226,38 @@ class CartItems extends TestCase { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartItems(); $cart = wc()->cart->get_cart(); $response = $controller->prepare_item_for_response( current( $cart ), [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'key', $response->get_data() ); - $this->assertArrayHasKey( 'id', $response->get_data() ); - $this->assertArrayHasKey( 'quantity', $response->get_data() ); - $this->assertArrayHasKey( 'name', $response->get_data() ); - $this->assertArrayHasKey( 'sku', $response->get_data() ); - $this->assertArrayHasKey( 'permalink', $response->get_data() ); - $this->assertArrayHasKey( 'images', $response->get_data() ); - $this->assertArrayHasKey( 'totals', $response->get_data() ); - $this->assertArrayHasKey( 'variation', $response->get_data() ); + $this->assertArrayHasKey( 'key', $data ); + $this->assertArrayHasKey( 'id', $data ); + $this->assertArrayHasKey( 'quantity', $data ); + $this->assertArrayHasKey( 'name', $data ); + $this->assertArrayHasKey( 'sku', $data ); + $this->assertArrayHasKey( 'permalink', $data ); + $this->assertArrayHasKey( 'images', $data ); + $this->assertArrayHasKey( 'totals', $data ); + $this->assertArrayHasKey( 'variation', $data ); + } + + /** + * Test schema matches responses. + * + * Tests schema of both products in cart to cover as much schema as possible. + */ + public function test_schema_matches_response() { + $cart = wc()->cart->get_cart(); + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartItems(); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + // Simple product. + $response = $controller->prepare_item_for_response( current( $cart ), [] ); + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + + // Variable product. + $response = $controller->prepare_item_for_response( end( $cart ), [] ); + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartOrder.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartOrder.php index 599c7716f23..16901cc7e57 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartOrder.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartOrder.php @@ -11,6 +11,8 @@ use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; use \WC_Helper_Order as OrderHelper; +use \WC_Helper_Coupon as CouponHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Cart Order Controller Tests. @@ -95,16 +97,16 @@ class CartOrder extends TestCase { $this->assertArrayHasKey( 'items', $data ); $this->assertArrayHasKey( 'totals', $data ); - $this->assertEquals( 'Margaret', $data['billing_address']['first_name'] ); - $this->assertEquals( 'Thatchcroft', $data['billing_address']['last_name'] ); - $this->assertEquals( '123 South Street', $data['billing_address']['address_1'] ); - $this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] ); - $this->assertEquals( 'Philadelphia', $data['billing_address']['city'] ); - $this->assertEquals( 'PA', $data['billing_address']['state'] ); - $this->assertEquals( '19123', $data['billing_address']['postcode'] ); - $this->assertEquals( 'US', $data['billing_address']['country'] ); - $this->assertEquals( 'test@test.com', $data['billing_address']['email'] ); - $this->assertEquals( '', $data['billing_address']['phone'] ); + $this->assertEquals( 'Margaret', $data['billing_address']->first_name ); + $this->assertEquals( 'Thatchcroft', $data['billing_address']->last_name ); + $this->assertEquals( '123 South Street', $data['billing_address']->address_1 ); + $this->assertEquals( 'Apt 1', $data['billing_address']->address_2 ); + $this->assertEquals( 'Philadelphia', $data['billing_address']->city ); + $this->assertEquals( 'PA', $data['billing_address']->state ); + $this->assertEquals( '19123', $data['billing_address']->postcode ); + $this->assertEquals( 'US', $data['billing_address']->country ); + $this->assertEquals( 'test@test.com', $data['billing_address']->email ); + $this->assertEquals( '', $data['billing_address']->phone ); $this->assertEquals( 'checkout-draft', $data['status'] ); $this->assertEquals( 2, count( $data['items'] ) ); @@ -139,19 +141,38 @@ class CartOrder extends TestCase { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartOrder(); $order = OrderHelper::create_order(); $response = $controller->prepare_item_for_response( $order, [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'id', $response->get_data() ); - $this->assertArrayHasKey( 'number', $response->get_data() ); - $this->assertArrayHasKey( 'status', $response->get_data() ); - $this->assertArrayHasKey( 'order_key', $response->get_data() ); - $this->assertArrayHasKey( 'created_via', $response->get_data() ); - $this->assertArrayHasKey( 'prices_include_tax', $response->get_data() ); - $this->assertArrayHasKey( 'events', $response->get_data() ); - $this->assertArrayHasKey( 'customer', $response->get_data() ); - $this->assertArrayHasKey( 'billing_address', $response->get_data() ); - $this->assertArrayHasKey( 'shipping_address', $response->get_data() ); - $this->assertArrayHasKey( 'customer_note', $response->get_data() ); - $this->assertArrayHasKey( 'items', $response->get_data() ); - $this->assertArrayHasKey( 'totals', $response->get_data() ); + $this->assertArrayHasKey( 'id', $data ); + $this->assertArrayHasKey( 'number', $data ); + $this->assertArrayHasKey( 'status', $data ); + $this->assertArrayHasKey( 'order_key', $data ); + $this->assertArrayHasKey( 'created_via', $data ); + $this->assertArrayHasKey( 'prices_include_tax', $data ); + $this->assertArrayHasKey( 'events', $data ); + $this->assertArrayHasKey( 'customer', $data ); + $this->assertArrayHasKey( 'billing_address', $data ); + $this->assertArrayHasKey( 'shipping_address', $data ); + $this->assertArrayHasKey( 'customer_note', $data ); + $this->assertArrayHasKey( 'items', $data ); + $this->assertArrayHasKey( 'totals', $data ); + } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartOrder(); + + $order = OrderHelper::create_order(); + $coupon = CouponHelper::create_coupon(); + $order->apply_coupon( $coupon ); + + $response = $controller->prepare_item_for_response( $order, [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartShippingRates.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartShippingRates.php index c0aa1d4ec2a..69d2d16578a 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartShippingRates.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/CartShippingRates.php @@ -10,6 +10,8 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use \WC_Helper_Shipping; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Cart Shipping Rates Controller Tests. @@ -35,6 +37,8 @@ class CartShippingRates extends TestCase { wc_empty_cart(); wc()->cart->add_to_cart( $this->products[0]->get_id(), 2 ); wc()->cart->add_to_cart( $this->products[1]->get_id(), 1 ); + + WC_Helper_Shipping::create_simple_flat_rate(); } /** @@ -61,12 +65,12 @@ class CartShippingRates extends TestCase { $this->assertArrayHasKey( 'items', $data[0] ); $this->assertArrayHasKey( 'shipping_rates', $data[0] ); - $this->assertEquals( null, $data[0]['destination']['address_1'] ); - $this->assertEquals( null, $data[0]['destination']['address_2'] ); - $this->assertEquals( null, $data[0]['destination']['city'] ); - $this->assertEquals( null, $data[0]['destination']['state'] ); - $this->assertEquals( null, $data[0]['destination']['postcode'] ); - $this->assertEquals( 'US', $data[0]['destination']['country'] ); + $this->assertEquals( null, $data[0]['destination']->address_1 ); + $this->assertEquals( null, $data[0]['destination']->address_2 ); + $this->assertEquals( null, $data[0]['destination']->city ); + $this->assertEquals( null, $data[0]['destination']->state ); + $this->assertEquals( null, $data[0]['destination']->postcode ); + $this->assertEquals( 'US', $data[0]['destination']->country ); } /** @@ -94,12 +98,12 @@ class CartShippingRates extends TestCase { $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 'Test address 1', $data[0]['destination']['address_1'] ); - $this->assertEquals( 'Test address 2', $data[0]['destination']['address_2'] ); - $this->assertEquals( 'Test City', $data[0]['destination']['city'] ); - $this->assertEquals( 'AL', $data[0]['destination']['state'] ); - $this->assertEquals( '90210', $data[0]['destination']['postcode'] ); - $this->assertEquals( 'US', $data[0]['destination']['country'] ); + $this->assertEquals( 'Test address 1', $data[0]['destination']->address_1 ); + $this->assertEquals( 'Test address 2', $data[0]['destination']->address_2 ); + $this->assertEquals( 'Test City', $data[0]['destination']->city ); + $this->assertEquals( 'AL', $data[0]['destination']->state ); + $this->assertEquals( '90210', $data[0]['destination']->postcode ); + $this->assertEquals( 'US', $data[0]['destination']->country ); // US address with invalid state. $request = new WP_REST_Request( 'GET', '/wc/store/cart/shipping-rates' ); @@ -118,8 +122,8 @@ class CartShippingRates extends TestCase { $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 'AL', $data[0]['destination']['state'] ); - $this->assertEquals( 'US', $data[0]['destination']['country'] ); + $this->assertEquals( 'AL', $data[0]['destination']->state ); + $this->assertEquals( 'US', $data[0]['destination']->country ); } /** @@ -149,10 +153,11 @@ class CartShippingRates extends TestCase { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartShippingRates(); $packages = wc()->shipping->calculate_shipping( wc()->cart->get_shipping_packages() ); $response = $controller->prepare_item_for_response( current( $packages ), [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'destination', $response->get_data() ); - $this->assertArrayHasKey( 'items', $response->get_data() ); - $this->assertArrayHasKey( 'shipping_rates', $response->get_data() ); + $this->assertArrayHasKey( 'destination', $data ); + $this->assertArrayHasKey( 'items', $data ); + $this->assertArrayHasKey( 'shipping_rates', $data ); } /** @@ -169,4 +174,24 @@ class CartShippingRates extends TestCase { $this->assertArrayHasKey( 'country', $params ); $this->assertArrayHasKey( 'postcode', $params ); } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\CartShippingRates(); + $request = new WP_REST_Request( 'GET', '/wc/store/cart/shipping-rates' ); + $request->set_param( 'address_1', 'Test address 1' ); + $request->set_param( 'address_2', 'Test address 2' ); + $request->set_param( 'city', 'Test City' ); + $request->set_param( 'state', 'AL' ); + $request->set_param( 'postcode', '90210' ); + $request->set_param( 'country', 'US' ); + $response = $this->server->dispatch( $request ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( current( $response->get_data() ) ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Customer.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Customer.php index 8f433af0733..aa7b278d600 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Customer.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Customer.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Customer Controller Tests. @@ -57,12 +58,12 @@ class Customer extends TestCase { $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( '123 South Street', $data['billing_address']['address_1'] ); - $this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] ); - $this->assertEquals( 'Philadelphia', $data['billing_address']['city'] ); - $this->assertEquals( 'PA', $data['billing_address']['state'] ); - $this->assertEquals( '19123', $data['billing_address']['postcode'] ); - $this->assertEquals( 'US', $data['billing_address']['country'] ); + $this->assertEquals( '123 South Street', $data['billing_address']->address_1 ); + $this->assertEquals( 'Apt 1', $data['billing_address']->address_2 ); + $this->assertEquals( 'Philadelphia', $data['billing_address']->city ); + $this->assertEquals( 'PA', $data['billing_address']->state ); + $this->assertEquals( '19123', $data['billing_address']->postcode ); + $this->assertEquals( 'US', $data['billing_address']->country ); // Invalid email. $request = new WP_REST_Request( 'POST', '/wc/store/customer' ); @@ -121,22 +122,57 @@ class Customer extends TestCase { $response = $controller->prepare_item_for_response( $customer, [] ); $data = $response->get_data(); - $this->assertEquals( 'Name', $data['billing_address']['first_name'] ); - $this->assertEquals( 'Surname', $data['billing_address']['last_name'] ); - $this->assertEquals( '123 South Street', $data['billing_address']['address_1'] ); - $this->assertEquals( 'Apt 1', $data['billing_address']['address_2'] ); - $this->assertEquals( 'Philadelphia', $data['billing_address']['city'] ); - $this->assertEquals( 'PA', $data['billing_address']['state'] ); - $this->assertEquals( '19123', $data['billing_address']['postcode'] ); - $this->assertEquals( 'US', $data['billing_address']['country'] ); + $this->assertEquals( 'Name', $data['billing_address']->first_name ); + $this->assertEquals( 'Surname', $data['billing_address']->last_name ); + $this->assertEquals( '123 South Street', $data['billing_address']->address_1 ); + $this->assertEquals( 'Apt 1', $data['billing_address']->address_2 ); + $this->assertEquals( 'Philadelphia', $data['billing_address']->city ); + $this->assertEquals( 'PA', $data['billing_address']->state ); + $this->assertEquals( '19123', $data['billing_address']->postcode ); + $this->assertEquals( 'US', $data['billing_address']->country ); - $this->assertEquals( 'Name', $data['shipping_address']['first_name'] ); - $this->assertEquals( 'Surname', $data['shipping_address']['last_name'] ); - $this->assertEquals( '123 South Street', $data['shipping_address']['address_1'] ); - $this->assertEquals( 'Apt 1', $data['shipping_address']['address_2'] ); - $this->assertEquals( 'Philadelphia', $data['shipping_address']['city'] ); - $this->assertEquals( 'PA', $data['shipping_address']['state'] ); - $this->assertEquals( '19123', $data['shipping_address']['postcode'] ); - $this->assertEquals( 'US', $data['shipping_address']['country'] ); + $this->assertEquals( 'Name', $data['shipping_address']->first_name ); + $this->assertEquals( 'Surname', $data['shipping_address']->last_name ); + $this->assertEquals( '123 South Street', $data['shipping_address']->address_1 ); + $this->assertEquals( 'Apt 1', $data['shipping_address']->address_2 ); + $this->assertEquals( 'Philadelphia', $data['shipping_address']->city ); + $this->assertEquals( 'PA', $data['shipping_address']->state ); + $this->assertEquals( '19123', $data['shipping_address']->postcode ); + $this->assertEquals( 'US', $data['shipping_address']->country ); + } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\Customer(); + $customer = new \WC_Customer(); + + $customer->set_billing_first_name( 'Name' ); + $customer->set_billing_last_name( 'Surname' ); + $customer->set_billing_email( 'test@test.com' ); + $customer->set_billing_phone( '+44 01010101011' ); + $customer->set_billing_address_1( '123 South Street' ); + $customer->set_billing_address_2( 'Apt 1' ); + $customer->set_billing_city( 'Philadelphia' ); + $customer->set_billing_state( 'PA' ); + $customer->set_billing_postcode( '19123' ); + $customer->set_billing_country( 'US' ); + + $customer->set_shipping_first_name( 'Name' ); + $customer->set_shipping_last_name( 'Surname' ); + $customer->set_shipping_address_1( '123 South Street' ); + $customer->set_shipping_address_2( 'Apt 1' ); + $customer->set_shipping_city( 'Philadelphia' ); + $customer->set_shipping_state( 'PA' ); + $customer->set_shipping_postcode( '19123' ); + $customer->set_shipping_country( 'US' ); + + $response = $controller->prepare_item_for_response( $customer, [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributeTerms.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributeTerms.php index 98c3ec16ed7..3270045b88a 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributeTerms.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributeTerms.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Product Attributes Controller Tests. @@ -27,10 +28,14 @@ class ProductAttributeTerms extends TestCase { $this->attributes[0] = ProductHelper::create_attribute( 'color', [ 'red', 'green', 'blue' ] ); $this->attributes[1] = ProductHelper::create_attribute( 'size', [ 'small', 'medium', 'large' ] ); - wp_insert_term( 'test', 'pa_size', [ - 'description' => 'This is a test description', - 'slug' => 'test-slug', - ] ); + wp_insert_term( + 'test', + 'pa_size', + [ + 'description' => 'This is a test description', + 'slug' => 'test-slug', + ] + ); } /** @@ -99,4 +104,17 @@ class ProductAttributeTerms extends TestCase { $this->assertArrayHasKey( 'orderby', $params ); $this->assertArrayHasKey( 'hide_empty', $params ); } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\ProductAttributeTerms(); + $response = $controller->prepare_item_for_response( get_term_by( 'name', 'test', 'pa_size' ), [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributes.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributes.php index 102e48a01d8..f2c29e330f6 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributes.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductAttributes.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Product Attributes Controller Tests. @@ -24,7 +25,7 @@ class ProductAttributes extends TestCase { wp_set_current_user( 0 ); $color_attribute = ProductHelper::create_attribute( 'color', [ 'red', 'green', 'blue' ] ); - $size_attribute = ProductHelper::create_attribute( 'size', [ 'small', 'medium', 'large' ] ); + $size_attribute = ProductHelper::create_attribute( 'size', [ 'small', 'medium', 'large' ] ); $this->attributes = []; $this->attributes[] = wc_get_attribute( $color_attribute['attribute_id'] ); @@ -94,12 +95,26 @@ class ProductAttributes extends TestCase { public function test_prepare_item_for_response() { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\ProductAttributes(); $response = $controller->prepare_item_for_response( $this->attributes[0], [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'id', $response->get_data() ); - $this->assertArrayHasKey( 'name', $response->get_data() ); - $this->assertArrayHasKey( 'slug', $response->get_data() ); - $this->assertArrayHasKey( 'type', $response->get_data() ); - $this->assertArrayHasKey( 'order', $response->get_data() ); - $this->assertArrayHasKey( 'has_archives', $response->get_data() ); + $this->assertArrayHasKey( 'id', $data ); + $this->assertArrayHasKey( 'name', $data ); + $this->assertArrayHasKey( 'slug', $data ); + $this->assertArrayHasKey( 'type', $data ); + $this->assertArrayHasKey( 'order', $data ); + $this->assertArrayHasKey( 'has_archives', $data ); + } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\ProductAttributes(); + $response = $controller->prepare_item_for_response( $this->attributes[0], [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductCollectionData.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductCollectionData.php index 97947daf794..9c79c6e268b 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductCollectionData.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/ProductCollectionData.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Controller Tests. @@ -23,7 +24,7 @@ class ProductCollectionData extends TestCase { wp_set_current_user( 0 ); - $this->products = []; + $this->products = []; $this->products[0] = ProductHelper::create_simple_product( false ); $this->products[0]->set_regular_price( 10 ); $this->products[0]->save(); @@ -32,31 +33,35 @@ class ProductCollectionData extends TestCase { $this->products[1]->set_regular_price( 100 ); $this->products[1]->save(); - wp_insert_comment( [ - 'comment_post_ID' => $this->products[0]->get_id(), - 'comment_author' => 'admin', - 'comment_author_email' => 'woo@woo.local', - 'comment_author_url' => '', - 'comment_content' => 'Good product.', - 'comment_approved' => 1, - 'comment_type' => 'review', - 'comment_meta' => [ - 'rating' => 5, + wp_insert_comment( + [ + 'comment_post_ID' => $this->products[0]->get_id(), + 'comment_author' => 'admin', + 'comment_author_email' => 'woo@woo.local', + 'comment_author_url' => '', + 'comment_content' => 'Good product.', + 'comment_approved' => 1, + 'comment_type' => 'review', + 'comment_meta' => [ + 'rating' => 5, + ], ] - ] ); + ); - wp_insert_comment( [ - 'comment_post_ID' => $this->products[1]->get_id(), - 'comment_author' => 'admin', - 'comment_author_email' => 'woo@woo.local', - 'comment_author_url' => '', - 'comment_content' => 'Another very good product.', - 'comment_approved' => 1, - 'comment_type' => 'review', - 'comment_meta' => [ - 'rating' => 4, + wp_insert_comment( + [ + 'comment_post_ID' => $this->products[1]->get_id(), + 'comment_author' => 'admin', + 'comment_author_email' => 'woo@woo.local', + 'comment_author_url' => '', + 'comment_content' => 'Another very good product.', + 'comment_approved' => 1, + 'comment_type' => 'review', + 'comment_meta' => [ + 'rating' => 4, + ], ] - ] ); + ); \WC_Comments::clear_transients( $this->products[0]->get_id() ); \WC_Comments::clear_transients( $this->products[1]->get_id() ); @@ -93,9 +98,9 @@ class ProductCollectionData extends TestCase { $data = $response->get_data(); $this->assertEquals( 200, $response->get_status() ); - $this->assertEquals( 2, $data['price_range']['currency_minor_unit'] ); - $this->assertEquals( '1000', $data['price_range']['min_price'] ); - $this->assertEquals( '10000', $data['price_range']['max_price'] ); + $this->assertEquals( 2, $data['price_range']->currency_minor_unit ); + $this->assertEquals( '1000', $data['price_range']->min_price ); + $this->assertEquals( '10000', $data['price_range']->max_price ); $this->assertEquals( null, $data['attribute_counts'] ); $this->assertEquals( null, $data['rating_counts'] ); } @@ -113,7 +118,7 @@ class ProductCollectionData extends TestCase { [ 'taxonomy' => 'pa_size', 'query_type' => 'and', - ] + ], ] ); $response = $this->server->dispatch( $request ); @@ -123,8 +128,8 @@ class ProductCollectionData extends TestCase { $this->assertEquals( null, $data['price_range'] ); $this->assertEquals( null, $data['rating_counts'] ); - $this->assertArrayHasKey( 'term', $data['attribute_counts'][0] ); - $this->assertArrayHasKey( 'count', $data['attribute_counts'][0] ); + $this->assertObjectHasAttribute( 'term', $data['attribute_counts'][0] ); + $this->assertObjectHasAttribute( 'count', $data['attribute_counts'][0] ); } /** @@ -139,16 +144,19 @@ class ProductCollectionData extends TestCase { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( null, $data['price_range'] ); $this->assertEquals( null, $data['attribute_counts'] ); - $this->assertEquals( [ + $this->assertEquals( [ - 'rating' => 4, - 'count' => 1, + (object) [ + 'rating' => 4, + 'count' => 1, + ], + (object) [ + 'rating' => 5, + 'count' => 1, + ], ], - [ - 'rating' => 5, - 'count' => 1, - ] - ], $data['rating_counts'] ); + $data['rating_counts'] + ); } /** @@ -177,4 +185,30 @@ class ProductCollectionData extends TestCase { $this->assertArrayHasKey( 'calculate_attribute_counts', $params ); $this->assertArrayHasKey( 'calculate_rating_counts', $params ); } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + ProductHelper::create_variation_product(); + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\ProductCollectionData(); + $request = new WP_REST_Request( 'GET', '/wc/store/products/collection-data' ); + $request->set_param( 'calculate_price_range', true ); + $request->set_param( + 'calculate_attribute_counts', + [ + [ + 'taxonomy' => 'pa_size', + 'query_type' => 'and', + ], + ] + ); + $request->set_param( 'calculate_rating_counts', true ); + $response = $this->server->dispatch( $request ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + } } diff --git a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Products.php b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Products.php index e44f7643221..e2a781f6b4f 100644 --- a/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Products.php +++ b/plugins/woocommerce-blocks/tests/php/RestApi/StoreApi/Controllers/Products.php @@ -10,6 +10,7 @@ namespace Automattic\WooCommerce\Blocks\Tests\RestApi\StoreApi\Controllers; use \WP_REST_Request; use \WC_REST_Unit_Test_Case as TestCase; use \WC_Helper_Product as ProductHelper; +use Automattic\WooCommerce\Blocks\Tests\Helpers\ValidateSchema; /** * Products Controller Tests. @@ -19,13 +20,25 @@ class Products extends TestCase { * Setup test products data. Called before every test. */ public function setUp() { + global $wpdb; + parent::setUp(); wp_set_current_user( 0 ); - $this->products = []; + $this->products = []; $this->products[0] = ProductHelper::create_simple_product( true ); $this->products[1] = ProductHelper::create_simple_product( true ); + + $image_url = media_sideload_image( 'http://cldup.com/Dr1Bczxq4q.png', $this->products[0]->get_id(), '', 'src' ); + $image_id = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid = %s", $image_url ) ); + $this->products[0]->set_image_id( $image_id[0] ); + $this->products[0]->save(); + + $image_url = media_sideload_image( 'http://cldup.com/Dr1Bczxq4q.png', $this->products[1]->get_id(), '', 'src' ); + $image_id = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid = %s", $image_url ) ); + $this->products[1]->set_image_id( $image_id[0] ); + $this->products[1]->save(); } /** @@ -49,14 +62,14 @@ class Products extends TestCase { $this->assertEquals( $this->products[0]->get_title(), $data['name'] ); $this->assertEquals( $this->products[0]->get_permalink(), $data['permalink'] ); $this->assertEquals( $this->products[0]->get_sku(), $data['sku'] ); - $this->assertEquals( $this->products[0]->get_price(), $data['prices']['price'] / ( 10 ** $data['prices']['currency_minor_unit'] ) ); + $this->assertEquals( $this->products[0]->get_price(), $data['prices']->price / ( 10 ** $data['prices']->currency_minor_unit ) ); $this->assertEquals( $this->products[0]->get_average_rating(), $data['average_rating'] ); $this->assertEquals( $this->products[0]->get_review_count(), $data['review_count'] ); $this->assertEquals( $this->products[0]->has_options(), $data['has_options'] ); $this->assertEquals( $this->products[0]->is_purchasable(), $data['is_purchasable'] ); $this->assertEquals( $this->products[0]->is_in_stock(), $data['is_in_stock'] ); - $this->assertEquals( $this->products[0]->add_to_cart_text(), $data['add_to_cart']['text'] ); - $this->assertEquals( $this->products[0]->add_to_cart_description(), $data['add_to_cart']['description'] ); + $this->assertEquals( $this->products[0]->add_to_cart_text(), $data['add_to_cart']->text ); + $this->assertEquals( $this->products[0]->add_to_cart_description(), $data['add_to_cart']->description ); $this->assertEquals( $this->products[0]->is_on_sale(), $data['on_sale'] ); } @@ -116,22 +129,23 @@ class Products extends TestCase { public function test_prepare_item_for_response() { $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\Products(); $response = $controller->prepare_item_for_response( $this->products[0], [] ); + $data = $response->get_data(); - $this->assertArrayHasKey( 'id', $response->get_data() ); - $this->assertArrayHasKey( 'name', $response->get_data() ); - $this->assertArrayHasKey( 'variation', $response->get_data() ); - $this->assertArrayHasKey( 'permalink', $response->get_data() ); - $this->assertArrayHasKey( 'description', $response->get_data() ); - $this->assertArrayHasKey( 'on_sale', $response->get_data() ); - $this->assertArrayHasKey( 'sku', $response->get_data() ); - $this->assertArrayHasKey( 'prices', $response->get_data() ); - $this->assertArrayHasKey( 'average_rating', $response->get_data() ); - $this->assertArrayHasKey( 'review_count', $response->get_data() ); - $this->assertArrayHasKey( 'images', $response->get_data() ); - $this->assertArrayHasKey( 'has_options', $response->get_data() ); - $this->assertArrayHasKey( 'is_purchasable', $response->get_data() ); - $this->assertArrayHasKey( 'is_in_stock', $response->get_data() ); - $this->assertArrayHasKey( 'add_to_cart', $response->get_data() ); + $this->assertArrayHasKey( 'id', $data ); + $this->assertArrayHasKey( 'name', $data ); + $this->assertArrayHasKey( 'variation', $data ); + $this->assertArrayHasKey( 'permalink', $data ); + $this->assertArrayHasKey( 'description', $data ); + $this->assertArrayHasKey( 'on_sale', $data ); + $this->assertArrayHasKey( 'sku', $data ); + $this->assertArrayHasKey( 'prices', $data ); + $this->assertArrayHasKey( 'average_rating', $data ); + $this->assertArrayHasKey( 'review_count', $data ); + $this->assertArrayHasKey( 'images', $data ); + $this->assertArrayHasKey( 'has_options', $data ); + $this->assertArrayHasKey( 'is_purchasable', $data ); + $this->assertArrayHasKey( 'is_in_stock', $data ); + $this->assertArrayHasKey( 'add_to_cart', $data ); } /** @@ -170,4 +184,17 @@ class Products extends TestCase { $this->assertArrayHasKey( 'catalog_visibility', $params ); $this->assertArrayHasKey( 'rating', $params ); } + + /** + * Test schema matches responses. + */ + public function test_schema_matches_response() { + $controller = new \Automattic\WooCommerce\Blocks\RestApi\StoreApi\Controllers\Products(); + $response = $controller->prepare_item_for_response( $this->products[0], [] ); + $schema = $controller->get_item_schema(); + $validate = new ValidateSchema( $schema ); + + $diff = $validate->get_diff_from_object( $response->get_data() ); + $this->assertEmpty( $diff, print_r( $diff, true ) ); + } }