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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 38 additions & 15 deletions includes/Generator/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ public static function generate( $save = true, $assoc_args = array() ) {

if ( $product ) {
$product->save();

// Assign brand terms using wp_set_object_terms, but only if the taxonomy exists.
if ( taxonomy_exists( 'product_brand' ) ) {
$brand_ids = self::get_term_ids( 'product_brand', self::$faker->numberBetween( 1, 3 ) );
if ( ! empty( $brand_ids ) ) {
$brand_result = wp_set_object_terms( $product->get_id(), $brand_ids, 'product_brand' );
if ( is_wp_error( $brand_result ) ) {
return $brand_result;
}
}
}
}

// Limit size of stored relationship IDs.
Expand All @@ -119,8 +130,8 @@ public static function generate( $save = true, $assoc_args = array() ) {
/**
* Create multiple products.
*
* @param int $amount The number of products to create.
* @param array $args Additional args for product creation.
* @param int $amount The number of products to create.
* @param array $args Additional args for product creation.
*
* @return int[]|\WP_Error
*/
Expand All @@ -137,7 +148,7 @@ public static function batch( $amount, array $args = array() ) {

$product_ids = array();

for ( $i = 1; $i <= $amount; $i ++ ) {
for ( $i = 1; $i <= $amount; $i++ ) {
$product = self::generate( true, $args );

// Skip products that failed to generate.
Expand All @@ -151,6 +162,7 @@ public static function batch( $amount, array $args = array() ) {
// In case multiple batches are being run in one request, refresh the cache data.
RandomRuntimeCache::clear( 'product_cat' );
RandomRuntimeCache::clear( 'product_tag' );
RandomRuntimeCache::clear( 'product_brand' );

return $product_ids;
}
Expand Down Expand Up @@ -322,9 +334,9 @@ protected static function generate_variable_product() {
'name' => $name,
'featured' => self::$faker->boolean( 10 ),
'sku' => sanitize_title( $name ) . '-' . self::$faker->ean8,
'global_unique_id' => self::$faker->randomElement( [ self::$faker->ean13, self::$faker->isbn10 ] ),
'global_unique_id' => self::$faker->randomElement( array( self::$faker->ean13, self::$faker->isbn10 ) ),
'attributes' => $attributes,
'tax_status' => self::$faker->randomElement( [ 'taxable', 'shipping', 'none' ] ),
'tax_status' => self::$faker->randomElement( array( 'taxable', 'shipping', 'none' ) ),
'tax_class' => '',
'manage_stock' => $will_manage_stock,
'stock_quantity' => $will_manage_stock ? self::$faker->numberBetween( -100, 100 ) : null,
Expand All @@ -348,22 +360,22 @@ protected static function generate_variable_product() {
$variation_attributes = wc_list_pluck( array_filter( $product->get_attributes(), 'wc_attributes_array_filter_variation' ), 'get_slugs' );
$possible_attributes = array_reverse( wc_array_cartesian( $variation_attributes ) );
foreach ( $possible_attributes as $possible_attribute ) {
$price = self::$faker->randomFloat( 2, 1, 1000 );
$is_on_sale = self::$faker->boolean( 35 );
$price = self::$faker->randomFloat( 2, 1, 1000 );
$is_on_sale = self::$faker->boolean( 35 );
$has_sale_schedule = $is_on_sale && self::$faker->boolean( 40 ); // ~40% of on-sale variations have a schedule.
$sale_price = $is_on_sale ? self::$faker->randomFloat( 2, 0, $price ) : '';
$sale_price = $is_on_sale ? self::$faker->randomFloat( 2, 0, $price ) : '';
$date_on_sale_from = $has_sale_schedule ? self::$faker->dateTimeBetween( '-3 days', '+3 days' )->format( DATE_ATOM ) : '';
$date_on_sale_to = $has_sale_schedule ? self::$faker->dateTimeBetween( '+4 days', '+4 months' )->format( DATE_ATOM ) : '';
$is_virtual = self::$faker->boolean( 20 );
$variation = new \WC_Product_Variation();
$is_virtual = self::$faker->boolean( 20 );
$variation = new \WC_Product_Variation();
$variation->set_props( array(
'parent_id' => $product->get_id(),
'attributes' => $possible_attribute,
'regular_price' => $price,
'sale_price' => $sale_price,
'date_on_sale_from' => $date_on_sale_from,
'date_on_sale_to' => $date_on_sale_to,
'tax_status' => self::$faker->randomElement( [ 'taxable', 'shipping', 'none' ] ),
'tax_status' => self::$faker->randomElement( array( 'taxable', 'shipping', 'none' ) ),
'tax_class' => '',
'manage_stock' => $will_manage_stock,
'stock_quantity' => $will_manage_stock ? self::$faker->numberBetween( -20, 100 ) : null,
Expand All @@ -381,7 +393,7 @@ protected static function generate_variable_product() {
if ( wc_get_container()->get( 'Automattic\WooCommerce\Internal\CostOfGoodsSold\CostOfGoodsSoldController' )->feature_is_enabled() ) {
$variation->set_props( array( 'cogs_value' => round( $price * ( 1 - self::$faker->numberBetween( 15, 60 ) / 100 ), 2 ) ) );
}

$variation->save();
}
$data_store = $product->get_data_store();
Expand Down Expand Up @@ -417,13 +429,13 @@ protected static function generate_simple_product() {
'description' => self::$faker->paragraphs( self::$faker->numberBetween( 1, 5 ), true ),
'short_description' => self::$faker->text(),
'sku' => sanitize_title( $name ) . '-' . self::$faker->ean8,
'global_unique_id' => self::$faker->randomElement( [ self::$faker->ean13, self::$faker->isbn10 ] ),
'global_unique_id' => self::$faker->randomElement( array( self::$faker->ean13, self::$faker->isbn10 ) ),
'regular_price' => $price,
'sale_price' => $sale_price,
'date_on_sale_from' => $date_on_sale_from,
'date_on_sale_to' => $date_on_sale_to,
'total_sales' => self::$faker->numberBetween( 0, 10000 ),
'tax_status' => self::$faker->randomElement( [ 'taxable', 'shipping', 'none' ] ),
'tax_status' => self::$faker->randomElement( array( 'taxable', 'shipping', 'none' ) ),
'tax_class' => '',
'manage_stock' => $will_manage_stock,
'stock_quantity' => $will_manage_stock ? self::$faker->numberBetween( -100, 100 ) : null,
Expand Down Expand Up @@ -471,24 +483,35 @@ protected static function maybe_generate_terms( int $product_amount ): void {
$cats = 5;
$cat_depth = 1;
$tags = 10;
$brands = 5;
} elseif ( $product_amount < 50 ) {
$cats = 10;
$cat_depth = 2;
$tags = 20;
$brands = 10;
} else {
$cats = 20;
$cat_depth = 3;
$tags = 40;
$brands = 10;
}

$existing_cats = count( self::get_term_ids( 'product_cat', $cats ) );
if ( $existing_cats < $cats ) {
Term::batch( $cats - $existing_cats, 'product_cat', array( 'max-depth' => $cat_depth ) );
RandomRuntimeCache::clear( 'product_cat' );
}

$existing_tags = count( self::get_term_ids( 'product_tag', $tags ) );
if ( $existing_tags < $tags ) {
Term::batch( $tags - $existing_tags, 'product_tag' );
RandomRuntimeCache::clear( 'product_tag' );
}

$existing_brands = count( self::get_term_ids( 'product_brand', $brands ) );
if ( $existing_brands < $brands ) {
Term::batch( $brands - $existing_brands, 'product_brand' );
RandomRuntimeCache::clear( 'product_brand' );
}
}

Expand Down Expand Up @@ -548,7 +571,7 @@ protected static function maybe_get_gallery_image_ids() {

$image_count = wp_rand( 0, 3 );

for ( $i = 0; $i < $image_count; $i ++ ) {
for ( $i = 0; $i < $image_count; $i++ ) {
$gallery[] = self::get_image();
}

Expand Down
30 changes: 16 additions & 14 deletions includes/Generator/Term.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ public static function generate( $save = true, string $taxonomy = 'product_cat',

parent::maybe_initialize_generators();

if ( $taxonomy_obj->hierarchical ) {
if ( 'product_brand' === $taxonomy ) {
$term_name = self::$faker->company();
} elseif ( $taxonomy_obj->hierarchical ) {
$term_name = ucwords( self::$faker->department( 3 ) );
} else {
$term_name = self::random_weighted_element( array(
Expand Down Expand Up @@ -107,11 +109,11 @@ public static function batch( $amount, $taxonomy, array $args = array() ) {

$term_ids = array();

for ( $i = 1; $i <= $amount; $i ++ ) {
for ( $i = 1; $i <= $amount; $i++ ) {
$term = self::generate( true, $taxonomy );
if ( is_wp_error( $term ) ) {
if ( 'term_exists' === $term->get_error_code() ) {
$i --; // Try again.
--$i; // Try again.
continue;
}

Expand Down Expand Up @@ -173,11 +175,11 @@ protected static function batch_hierarchical( int $amount, string $taxonomy, arr

if ( $parent || 1 === $max_depth ) {
// All terms will be in the same hierarchy level.
for ( $i = 1; $i <= $amount; $i ++ ) {
for ( $i = 1; $i <= $amount; $i++ ) {
$term = self::generate( true, $taxonomy, $parent );
if ( is_wp_error( $term ) ) {
if ( 'term_exists' === $term->get_error_code() ) {
$i --; // Try again.
--$i; // Try again.
continue;
}

Expand All @@ -193,41 +195,41 @@ protected static function batch_hierarchical( int $amount, string $taxonomy, arr
}
$levels = array_fill( 1, $max_depth, array() );

for ( $i = 1; $i <= $max_depth; $i ++ ) {
for ( $i = 1; $i <= $max_depth; $i++ ) {
if ( 1 === $i ) {
// Always use the full term max for the top level of the hierarchy.
for ( $j = 1; $j <= $term_max && $remaining > 0; $j ++ ) {
for ( $j = 1; $j <= $term_max && $remaining > 0; $j++ ) {
$term = self::generate( true, $taxonomy );
if ( is_wp_error( $term ) ) {
if ( 'term_exists' === $term->get_error_code() ) {
$j --; // Try again.
--$j; // Try again.
continue;
}

return $term;
}
$term_ids[] = $term->term_id;
$term_ids[] = $term->term_id;
$levels[ $i ][] = $term->term_id;
$remaining --;
--$remaining;
}
} else {
// Subsequent hierarchy levels.
foreach ( $levels[ $i - 1 ] as $term_id ) {
$tcount = wp_rand( 0, $term_max );

for ( $j = 1; $j <= $tcount && $remaining > 0; $j ++ ) {
for ( $j = 1; $j <= $tcount && $remaining > 0; $j++ ) {
$term = self::generate( true, $taxonomy, $term_id );
if ( is_wp_error( $term ) ) {
if ( 'term_exists' === $term->get_error_code() ) {
$j --; // Try again.
--$j; // Try again.
continue;
}

return $term;
}
$term_ids[] = $term->term_id;
$term_ids[] = $term->term_id;
$levels[ $i ][] = $term->term_id;
$remaining --;
--$remaining;
}
}
}
Expand Down
74 changes: 68 additions & 6 deletions tests/Unit/Generator/ProductTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function test_generate_variable_product() {
$this->assertNotEmpty( $product->get_name() );

// Check that variations were created (refresh product to get updated data).
$product = wc_get_product( $product->get_id() );
$product = wc_get_product( $product->get_id() );
$variations = $product->get_children();
// Note: Variations may not be created if attribute registration fails in test environment.
// This is a known limitation of the test setup.
Expand Down Expand Up @@ -110,7 +110,7 @@ public function test_variations_have_prices() {
}

// Refresh product to get variations.
$product = wc_get_product( $product->get_id() );
$product = wc_get_product( $product->get_id() );
$variations = $product->get_children();

if ( empty( $variations ) ) {
Expand Down Expand Up @@ -332,13 +332,13 @@ public function test_product_backorders() {
* Test product action hook is fired.
*/
public function test_product_generated_action_hook() {
$hook_fired = false;
$hook_fired = false;
$generated_product = null;

add_action(
'smoothgenerator_product_generated',
function ( $product ) use ( &$hook_fired, &$generated_product ) {
$hook_fired = true;
$hook_fired = true;
$generated_product = $product;
}
);
Expand Down Expand Up @@ -368,7 +368,10 @@ public function test_batch_with_existing_terms() {
wp_insert_term( 'Test Category', 'product_cat' );
wp_insert_term( 'Test Tag', 'product_tag' );

$product_ids = Product::batch( 3, array( 'use-existing-terms' => true, 'type' => 'simple' ) );
$product_ids = Product::batch( 3, array(
'use-existing-terms' => true,
'type' => 'simple',
) );

$this->assertIsArray( $product_ids );
$this->assertCount( 3, $product_ids );
Expand All @@ -391,7 +394,7 @@ public function test_variation_sale_prices() {
}

// Refresh product to get variations.
$product = wc_get_product( $product->get_id() );
$product = wc_get_product( $product->get_id() );
$variations = $product->get_children();

if ( empty( $variations ) ) {
Expand Down Expand Up @@ -428,4 +431,63 @@ public function test_featured_products() {
}
$this->assertTrue( $found_featured, 'Should generate at least one featured product in 30 attempts' );
}

/**
* Test that products have brands assigned when taxonomy exists.
*/
public function test_products_have_brands_when_taxonomy_exists() {
// Register product_brand taxonomy for the test.
register_taxonomy(
'product_brand',
'product',
array(
'labels' => array( 'name' => 'Brands' ),
'hierarchical' => false,
'show_ui' => true,
'query_var' => true,
'rewrite' => array( 'slug' => 'brand' ),
)
);

// Create some brand terms.
wp_insert_term( 'Test Brand 1', 'product_brand' );
wp_insert_term( 'Test Brand 2', 'product_brand' );
wp_insert_term( 'Test Brand 3', 'product_brand' );

// Clear the cache to ensure fresh term lookup.
\WC\SmoothGenerator\Util\RandomRuntimeCache::clear( 'product_brand' );

$product = Product::generate( true, array( 'type' => 'simple' ) );

// Get assigned brands.
$brand_terms = wp_get_object_terms( $product->get_id(), 'product_brand' );

$this->assertIsArray( $brand_terms );
$this->assertGreaterThanOrEqual( 1, count( $brand_terms ), 'Product should have at least 1 brand' );
$this->assertLessThanOrEqual( 3, count( $brand_terms ), 'Product should have at most 3 brands' );

// Clean up.
unregister_taxonomy( 'product_brand' );
}

/**
* Test that product generation doesn't fail when brand taxonomy doesn't exist.
*/
public function test_product_generation_without_brand_taxonomy() {
// Ensure taxonomy doesn't exist.
if ( taxonomy_exists( 'product_brand' ) ) {
unregister_taxonomy( 'product_brand' );
}

$product = Product::generate( true, array( 'type' => 'simple' ) );

// Should still generate successfully.
$this->assertInstanceOf( \WC_Product::class, $product );
$this->assertTrue( $product->get_id() > 0 );
$this->assertEquals( 'simple', $product->get_type() );

// Should have no brand terms.
$brand_terms = wp_get_object_terms( $product->get_id(), 'product_brand' );
$this->assertTrue( is_wp_error( $brand_terms ) || empty( $brand_terms ) );
}
}