From 467af94b1d20c30837eb3dc40e031cde16a5914d Mon Sep 17 00:00:00 2001 From: Nestor Soriano Date: Fri, 30 Apr 2021 12:10:25 +0200 Subject: [PATCH] Add unit tests for LookupDataStore::update_data_for_product (simple products) Also: - Add the FakeQueue class - Fix LookupDataStore, it was using a hardcoded "wp_" lookup table name --- .../LookupDataStore.php | 24 ++- tests/Tools/FakeQueue.php | 82 ++++++++ .../LookupDataStoreTest.php | 184 ++++++++++++++++++ 3 files changed, 288 insertions(+), 2 deletions(-) create mode 100644 tests/Tools/FakeQueue.php create mode 100644 tests/php/src/Internal/ProductAttributesLookup/LookupDataStoreTest.php diff --git a/src/Internal/ProductAttributesLookup/LookupDataStore.php b/src/Internal/ProductAttributesLookup/LookupDataStore.php index 05d133fcdd5..35486f0a0e8 100644 --- a/src/Internal/ProductAttributesLookup/LookupDataStore.php +++ b/src/Internal/ProductAttributesLookup/LookupDataStore.php @@ -14,6 +14,22 @@ defined( 'ABSPATH' ) || exit; */ class LookupDataStore { + /** + * The lookup table name. + * + * @var string + */ + private $lookup_table_name; + + /** + * LookupDataStore constructor. + */ + public function __construct() { + global $wpdb; + + $this->lookup_table_name = $wpdb->prefix . 'wc_product_attributes_lookup'; + } + /** * Insert or update the lookup data for a given product or variation. * If a variable product is passed the information is updated for all of its variations. @@ -50,12 +66,14 @@ class LookupDataStore { private function delete_lookup_table_entries_for( int $product_id ) { global $wpdb; + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( - 'DELETE FROM wp_wc_product_attributes_lookup WHERE product_or_parent_id = %d', + 'DELETE FROM ' . $this->lookup_table_name . ' WHERE product_or_parent_id = %d', $product_id ) ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared } /** @@ -252,9 +270,10 @@ class LookupDataStore { private function insert_lookup_table_data( int $product_id, int $product_or_parent_id, string $taxonomy, int $term_id, bool $is_variation_attribute, bool $has_stock ) { global $wpdb; + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $wpdb->query( $wpdb->prepare( - 'INSERT INTO wp_wc_product_attributes_lookup ( + 'INSERT INTO ' . $this->lookup_table_name . ' ( product_id, product_or_parent_id, taxonomy, @@ -271,5 +290,6 @@ class LookupDataStore { $has_stock ? 1 : 0 ) ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared } } diff --git a/tests/Tools/FakeQueue.php b/tests/Tools/FakeQueue.php new file mode 100644 index 00000000000..fe8d8a24f24 --- /dev/null +++ b/tests/Tools/FakeQueue.php @@ -0,0 +1,82 @@ +queue() will return an instance of this class. + */ +class FakeQueue implements \WC_Queue_Interface { + + /** + * Records all the method calls to this instance. + * + * @var array + */ + public $methods_called = array(); + + // phpcs:disable Squiz.Commenting.FunctionComment.Missing + + public function add( $hook, $args = array(), $group = '' ) { + // TODO: Implement add() method. + } + + public function schedule_single( $timestamp, $hook, $args = array(), $group = '' ) { + $this->add_to_methods_called( 'schedule_single', $args, $group, array( 'hook' => $hook ) ); + } + + public function schedule_recurring( $timestamp, $interval_in_seconds, $hook, $args = array(), $group = '' ) { + // TODO: Implement schedule_recurring() method. + } + + public function schedule_cron( $timestamp, $cron_schedule, $hook, $args = array(), $group = '' ) { + // TODO: Implement schedule_cron() method. + } + + public function cancel( $hook, $args = array(), $group = '' ) { + // TODO: Implement cancel() method. + } + + public function cancel_all( $hook, $args = array(), $group = '' ) { + // TODO: Implement cancel_all() method. + } + + public function get_next( $hook, $args = null, $group = '' ) { + // TODO: Implement get_next() method. + } + + public function search( $args = array(), $return_format = OBJECT ) { + // TODO: Implement search() method. + } + + // phpcs:enable Squiz.Commenting.FunctionComment.Missing + + /** + * Registers a method call for this instance. + * + * @param string $method Name of the invoked method. + * @param array $args Arguments passed in '$args' to the method call. + * @param string $group Group name passed in '$group' to the method call. + * @param array $extra_args Any extra information to store about the method call. + */ + private function add_to_methods_called( $method, $args, $group, $extra_args = array() ) { + $value = array( + 'method' => $method, + 'args' => $args, + 'group' => $group, + ); + + $this->methods_called[] = array_merge( $value, $extra_args ); + } +} diff --git a/tests/php/src/Internal/ProductAttributesLookup/LookupDataStoreTest.php b/tests/php/src/Internal/ProductAttributesLookup/LookupDataStoreTest.php new file mode 100644 index 00000000000..5a9f61c1d20 --- /dev/null +++ b/tests/php/src/Internal/ProductAttributesLookup/LookupDataStoreTest.php @@ -0,0 +1,184 @@ +sut = new LookupDataStore(); + + // Initiating regeneration with a fake queue will just create the lookup table in the database. + add_filter( + 'woocommerce_queue_class', + function() { + return FakeQueue::class; + } + ); + $this->get_instance_of( DataRegenerator::class )->initiate_regeneration(); + } + + /** + * @testdox `test_update_data_for_product` throws an exception if a variation is passed. + */ + public function test_update_data_for_product_throws_if_variation_is_passed() { + $product = new \WC_Product_Variation(); + + $this->expectException( \Exception::class ); + $this->expectExceptionMessage( "LookupDataStore::update_data_for_product can't be called for variations." ); + + $this->sut->update_data_for_product( $product ); + } + + /** + * @testdox `test_update_data_for_product` creates the appropriate entries for simple products, skipping custom product attributes. + * + * @testWith [true] + * [false] + * + * @param bool $in_stock 'true' if the product is supposed to be in stock. + */ + public function test_update_data_for_simple_product( $in_stock ) { + $product = new \WC_Product_Simple(); + $product->set_id( 10 ); + $this->set_product_attributes( + $product, + array( + 'pa_attribute_1' => array( + 'id' => 100, + 'options' => array( 51, 52 ), + ), + 'pa_attribute_2' => array( + 'id' => 200, + 'options' => array( 73, 74 ), + ), + 'pa_custom_attribute' => array( + 'id' => 0, + 'options' => array( 'foo', 'bar' ), + ), + ) + ); + + if ( $in_stock ) { + $product->set_stock_status( 'instock' ); + $expected_in_stock = 1; + } else { + $product->set_stock_status( 'outofstock' ); + $expected_in_stock = 0; + } + + $this->sut->update_data_for_product( $product ); + + $expected = array( + array( + 'product_id' => 10, + 'product_or_parent_id' => 10, + 'taxonomy' => 'pa_attribute_1', + 'term_id' => 51, + 'in_stock' => $expected_in_stock, + 'is_variation_attribute' => 0, + ), + array( + 'product_id' => 10, + 'product_or_parent_id' => 10, + 'taxonomy' => 'pa_attribute_1', + 'term_id' => 52, + 'in_stock' => $expected_in_stock, + 'is_variation_attribute' => 0, + ), + array( + 'product_id' => 10, + 'product_or_parent_id' => 10, + 'taxonomy' => 'pa_attribute_2', + 'term_id' => 73, + 'in_stock' => $expected_in_stock, + 'is_variation_attribute' => 0, + ), + array( + 'product_id' => 10, + 'product_or_parent_id' => 10, + 'taxonomy' => 'pa_attribute_2', + 'term_id' => 74, + 'in_stock' => $expected_in_stock, + 'is_variation_attribute' => 0, + ), + ); + + $actual = $this->get_lookup_table_data(); + + $this->assertEquals( $expected, $actual ); + } + + /** + * Set the product attributes from an array with this format: + * + * [ + * 'taxonomy_or_custom_attribute_name' => + * [ + * 'id' => attribute id (0 for custom product attribute), + * 'options' => [term_id, term_id...] (for custom product attributes: ['term', 'term'...] + * 'variation' => 1|0 (optional, default 0) + * ], ... + * ] + * + * @param WC_Product $product The product to set the attributes. + * @param array $attributes_data The attributes to set. + */ + private function set_product_attributes( $product, $attributes_data ) { + $attributes = array(); + foreach ( $attributes_data as $taxonomy => $attribute_data ) { + $attribute = new \WC_Product_Attribute(); + $attribute->set_id( $attribute_data['id'] ); + $attribute->set_name( $taxonomy ); + $attribute->set_options( $attribute_data['options'] ); + $attribute->set_variation( ArrayUtil::get_value_or_default( $attribute_data, 'variation', false ) ); + $attributes[] = $attribute; + } + + $product->set_attributes( $attributes ); + } + + /** + * Get all the data in the lookup table as an array of associative arrays. + * + * @return array All the rows in the lookup table as an array of associative arrays. + */ + private function get_lookup_table_data() { + global $wpdb; + + $result = $wpdb->get_results( 'select * from ' . $wpdb->prefix . 'wc_product_attributes_lookup', ARRAY_A ); + + foreach ( $result as $row ) { + foreach ( $row as $column_name => $value ) { + if ( 'taxonomy' !== $column_name ) { + $row[ $column_name ] = (int) $value; + } + } + } + + return $result; + } +}