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
This commit is contained in:
Nestor Soriano 2021-04-30 12:10:25 +02:00
parent e58d26f377
commit 467af94b1d
No known key found for this signature in database
GPG Key ID: 08110F3518C12CAD
3 changed files with 288 additions and 2 deletions

View File

@ -14,6 +14,22 @@ defined( 'ABSPATH' ) || exit;
*/ */
class LookupDataStore { 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. * 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. * 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 ) { private function delete_lookup_table_entries_for( int $product_id ) {
global $wpdb; global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->query(
$wpdb->prepare( $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 $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 ) { 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; global $wpdb;
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$wpdb->query( $wpdb->query(
$wpdb->prepare( $wpdb->prepare(
'INSERT INTO wp_wc_product_attributes_lookup ( 'INSERT INTO ' . $this->lookup_table_name . ' (
product_id, product_id,
product_or_parent_id, product_or_parent_id,
taxonomy, taxonomy,
@ -271,5 +290,6 @@ class LookupDataStore {
$has_stock ? 1 : 0 $has_stock ? 1 : 0
) )
); );
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
} }
} }

82
tests/Tools/FakeQueue.php Normal file
View File

@ -0,0 +1,82 @@
<?php
/**
* FakeQueue class file.
*
* @package WooCommerce\Testing\Tools
*/
namespace Automattic\WooCommerce\Testing\Tools;
/**
* Fake scheduled actions queue for unit tests, it just records all the method calls
* in a publicly accessible $methods_called property.
*
* To use, add this to the setUp method of the unit tests class:
*
* add_filter( 'woocommerce_queue_class', function() { return FakeQueue::class; } );
*
* then WC->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 );
}
}

View File

@ -0,0 +1,184 @@
<?php
/**
* LookupDataStoreTest class file.
*/
namespace Automattic\WooCommerce\Tests\Internal\ProductAttributesLookup;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
use Automattic\WooCommerce\Testing\Tools\FakeQueue;
use Automattic\WooCommerce\Utilities\ArrayUtil;
/**
* Tests for the LookupDataStore class.
* @package Automattic\WooCommerce\Tests\Internal\ProductAttributesLookup
*/
class LookupDataStoreTest extends \WC_Unit_Test_Case {
/**
* The system under test.
*
* @var LookupDataStore
*/
private $sut;
/**
* Runs before each test.
*/
public function setUp() {
global $wpdb;
$this->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;
}
}