refactor haveProduct() method to allow custom attribute handling

This commit is contained in:
Herbert Maschke 2020-03-18 14:34:27 +01:00
parent 7faf5bb97f
commit b8e4692114
4 changed files with 255 additions and 415 deletions

View File

@ -37,16 +37,17 @@
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.1",
"codeception/codeception": "4.1.1",
"codeception/module-asserts": "^1.1",
"codeception/module-filesystem": "^1.0",
"codeception/module-laravel5": "^1.0",
"codeception/module-webdriver": "^1.0",
"filp/whoops": "^2.0",
"fzaninotto/faker": "^1.4",
"mockery/mockery": "^1.0",
"nunomaduro/collision": "^2.0",
"phpunit/phpunit": "^7.0",
"barryvdh/laravel-debugbar": "^3.1",
"fzaninotto/faker": "^1.4"
"phpunit/phpunit": "^7.0"
},
"replace": {

122
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "dca322023597f38ef7991bc9d9bdc50f",
"content-hash": "027ac2836216da68226e72e0ca7e4211",
"packages": [
{
"name": "astrotomic/laravel-translatable",
@ -5619,6 +5619,61 @@
],
"time": "2019-10-10T15:58:56+00:00"
},
{
"name": "codeception/module-webdriver",
"version": "1.0.5",
"source": {
"type": "git",
"url": "https://github.com/Codeception/module-webdriver.git",
"reference": "cfeadb39170aa80e6fe244de6a130453edd79fe9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Codeception/module-webdriver/zipball/cfeadb39170aa80e6fe244de6a130453edd79fe9",
"reference": "cfeadb39170aa80e6fe244de6a130453edd79fe9",
"shasum": ""
},
"require": {
"codeception/codeception": "4.0.x-dev | ^4.0",
"php": ">=5.6.0 <8.0",
"php-webdriver/webdriver": "^1.6.0"
},
"require-dev": {
"codeception/util-robohelpers": "dev-master"
},
"suggest": {
"codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Bodnarchuk"
},
{
"name": "Gintautas Miselis"
},
{
"name": "Zaahid Bateson"
}
],
"description": "WebDriver module for Codeception",
"homepage": "http://codeception.com/",
"keywords": [
"acceptance-testing",
"browser-testing",
"codeception"
],
"time": "2020-03-06T08:39:02+00:00"
},
{
"name": "codeception/phpunit-wrapper",
"version": "7.8.0",
@ -6248,6 +6303,71 @@
"description": "Library for handling version information and constraints",
"time": "2018-07-08T19:19:57+00:00"
},
{
"name": "php-webdriver/webdriver",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/php-webdriver/php-webdriver.git",
"reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab",
"reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-zip": "*",
"php": "^5.6 || ~7.0",
"symfony/polyfill-mbstring": "^1.12",
"symfony/process": "^2.8 || ^3.1 || ^4.0 || ^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.0",
"jakub-onderka/php-parallel-lint": "^1.0",
"php-coveralls/php-coveralls": "^2.0",
"php-mock/php-mock-phpunit": "^1.1",
"phpunit/phpunit": "^5.7",
"sebastian/environment": "^1.3.4 || ^2.0 || ^3.0",
"sminnee/phpunit-mock-objects": "^3.4",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^3.3 || ^4.0 || ^5.0"
},
"suggest": {
"ext-SimpleXML": "For Firefox profile creation"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
}
},
"autoload": {
"psr-4": {
"Facebook\\WebDriver\\": "lib/"
},
"files": [
"lib/Exception/TimeoutException.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP client for Selenium WebDriver. Previously facebook/webdriver.",
"homepage": "https://github.com/php-webdriver/php-webdriver",
"keywords": [
"Chromedriver",
"geckodriver",
"php",
"selenium",
"webdriver"
],
"time": "2020-03-04T14:40:12+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.0.0",

View File

@ -5,15 +5,19 @@ namespace Webkul\Core\Helpers;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
use Faker\Factory;
use Illuminate\Support\Str;
use Codeception\Module\Laravel5;
use Webkul\Checkout\Models\Cart;
use Webkul\Customer\Models\Customer;
use Webkul\Checkout\Models\CartItem;
use Illuminate\Support\Facades\Event;
use Webkul\Product\Models\Product;
use Webkul\Attribute\Models\Attribute;
use Webkul\Checkout\Models\CartAddress;
use Webkul\Product\Models\ProductInventory;
use Webkul\Core\Contracts\Validations\Slug;
use Webkul\Customer\Models\CustomerAddress;
use Webkul\Attribute\Models\AttributeOption;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Product\Models\ProductDownloadableLink;
use Webkul\Product\Models\ProductDownloadableLinkTranslation;
@ -25,7 +29,8 @@ class Laravel5Helper extends Laravel5
public const DOWNLOADABLE_PRODUCT = 3;
/**
* Returns field name of given attribute.
* Returns the field name of the given attribute in which a value should be saved inside
* the 'product_attribute_values' table. Depends on the type.
*
* @param string $attribute
*
@ -34,36 +39,23 @@ class Laravel5Helper extends Laravel5
*/
public static function getAttributeFieldName(string $attribute): ?string
{
$attributes = [
'product_id' => 'integer_value',
'sku' => 'text_value',
'name' => 'text_value',
'url_key' => 'text_value',
'tax_category_id' => 'integer_value',
'new' => 'boolean_value',
'featured' => 'boolean_value',
'visible_individually' => 'boolean_value',
'status' => 'boolean_value',
'short_description' => 'text_value',
'description' => 'text_value',
'price' => 'float_value',
'cost' => 'float_value',
'special_price' => 'float_value',
'special_price_from' => 'date_value',
'special_price_to' => 'date_value',
'meta_title' => 'text_value',
'meta_keywords' => 'text_value',
'meta_description' => 'text_value',
'width' => 'integer_value',
'height' => 'integer_value',
'depth' => 'integer_value',
'weight' => 'integer_value',
'color' => 'integer_value',
'size' => 'integer_value',
'brand' => 'text_value',
'guest_checkout' => 'boolean_value',
$attributes = [];
// @todo implement json_value ?
$possibleTypes = [
'text' => 'text_value',
'select' => 'integer_value',
'boolean' => 'boolean_value',
'textarea' => 'text_value',
'price' => 'float_value',
'date' => 'date_value',
];
foreach (Attribute::all() as $item) {
$attributes[$item->code] = $possibleTypes[$item->type];
}
if (! array_key_exists($attribute, $attributes)) {
return null;
}
@ -71,16 +63,6 @@ class Laravel5Helper extends Laravel5
return $attributes[$attribute];
}
/**
* Generate a cart for the customer. Usually this is necessary to prepare the database
* before testing the checkout.
*
* @param array $options pass some options to configure some of the properties of the cart
*
* @return array the generated mocks as array
*
* @throws \Exception
*/
public function prepareCart(array $options = []): array
{
$faker = \Faker\Factory::create();
@ -165,49 +147,6 @@ class Laravel5Helper extends Laravel5
'cartItems' => $cartItems,
'totalQtyOrdered' => $totalQtyOrdered,
];
}
/**
* Helper function to generate products for testing
*
* @param int $productType
* @param array $configs
* @param array $productStates
*
* @return \Webkul\Product\Models\Product
* @part ORM
*/
public function haveProduct(
int $productType,
array $configs = [],
array $productStates = []
): Product
{
$I = $this;
switch ($productType) {
case self::DOWNLOADABLE_PRODUCT:
$product = $I->haveDownloadableProduct($configs, $productStates);
break;
case self::VIRTUAL_PRODUCT:
$product = $I->haveVirtualProduct($configs, $productStates);
break;
case self::SIMPLE_PRODUCT:
default:
$product = $I->haveSimpleProduct($configs, $productStates);
}
if ($product !== null) {
Event::dispatch('catalog.product.create.after', $product);
}
return $product;
}
/**
@ -231,19 +170,53 @@ class Laravel5Helper extends Laravel5
}
/**
* Helper function to generate products for testing.
*
* By default, the product will be generated as saleable, this means it has a price,
* weight, is active and has a positive inventory stock, if necessary.
*
* @param int $productType see constants in this class for usage
* @param array $configs
* @param array $productStates
*
* @return \Webkul\Product\Models\Product
* @part ORM
*/
public function haveProduct(int $productType, array $configs = [], array $productStates = []): Product
{
$I = $this;
switch ($productType) {
case self::DOWNLOADABLE_PRODUCT:
$product = $I->haveDownloadableProduct($configs, $productStates);
break;
case self::VIRTUAL_PRODUCT:
$product = $I->haveVirtualProduct($configs, $productStates);
break;
case self::SIMPLE_PRODUCT:
default:
$product = $I->haveSimpleProduct($configs, $productStates);
}
if ($product !== null) {
Event::dispatch('catalog.product.create.after', $product);
}
return $product;
}
/**
* @param array $configs
* @param array $productStates
*
* @return \Webkul\Product\Models\Product
*/
private function haveSimpleProduct(
array $configs = [],
array $productStates = []
): Product
private function haveSimpleProduct(array $configs = [], array $productStates = []): Product
{
$I = $this;
if (! in_array('simple', $productStates)) {
$productStates = array_merge($productStates, ['simple']);
}
@ -251,9 +224,9 @@ class Laravel5Helper extends Laravel5
/** @var Product $product */
$product = $I->createProduct($configs['productAttributes'] ?? [], $productStates);
$I->createAttributeValues($product->id, $configs['attributeValues'] ?? []);
$I->createAttributeValues($product, $configs['attributeValues'] ?? []);
$I->createInventory($product->id, $configs['productInventory'] ?? []);
$I->createInventory($product->id, $configs['productInventory'] ?? ['qty' => 10]);
return $product->refresh();
}
@ -262,12 +235,11 @@ class Laravel5Helper extends Laravel5
* @param array $configs
* @param array $productStates
*
* @return \Webkul\Product\Contracts\Product
* @return \Webkul\Product\Models\Product
*/
private function haveVirtualProduct(array $configs = [], array $productStates = []): Product
{
$I = $this;
if (! in_array('virtual', $productStates)) {
$productStates = array_merge($productStates, ['virtual']);
}
@ -275,9 +247,9 @@ class Laravel5Helper extends Laravel5
/** @var Product $product */
$product = $I->createProduct($configs['productAttributes'] ?? [], $productStates);
$I->createAttributeValues($product->id, $configs['attributeValues'] ?? []);
$I->createAttributeValues($product, $configs['attributeValues'] ?? []);
$I->createInventory($product->id, $configs['productInventory'] ?? []);
$I->createInventory($product->id, $configs['productInventory'] ?? ['qty' => 10]);
return $product->refresh();
}
@ -286,12 +258,11 @@ class Laravel5Helper extends Laravel5
* @param array $configs
* @param array $productStates
*
* @return \Webkul\Product\Contracts\Product
* @return \Webkul\Product\Models\Product
*/
private function haveDownloadableProduct(array $configs = [], array $productStates = []): Product
{
$I = $this;
if (! in_array('downloadable', $productStates)) {
$productStates = array_merge($productStates, ['downloadable']);
}
@ -299,7 +270,7 @@ class Laravel5Helper extends Laravel5
/** @var Product $product */
$product = $I->createProduct($configs['productAttributes'] ?? [], $productStates);
$I->createAttributeValues($product->id, $configs['attributeValues'] ?? []);
$I->createAttributeValues($product, $configs['attributeValues'] ?? []);
$I->createDownloadableLink($product->id);
@ -320,30 +291,22 @@ class Laravel5Helper extends Laravel5
/**
* @param int $productId
* @param array $inventoryConfig
*
* @return void
*/
private function createInventory(int $productId, array $inventoryConfig = []): void
{
$I = $this;
$I->have(ProductInventory::class, array_merge($inventoryConfig, [
'product_id' => $productId,
'inventory_source_id' => 1,
'qty' => random_int(100, 666),
]));
}
/**
* @param int $productId
*
* @return void
*/
private function createDownloadableLink(int $productId): void
{
$I = $this;
$link = $I->have(ProductDownloadableLink::class, [
'product_id' => $productId,
]);
@ -354,49 +317,74 @@ class Laravel5Helper extends Laravel5
}
/**
* @param int $productId
* @param array $attributeValues
*
* @return void
* @param Product $product
* @param array $attributeValues
*/
private function createAttributeValues(int $productId, array $attributeValues = []): void
private function createAttributeValues(Product $product, array $attributeValues = []): void
{
$I = $this;
$productAttributeValues = [
'sku',
'url_key',
'tax_category_id',
'price',
'cost',
'name',
'new',
'visible_individually',
'featured',
'status',
'guest_checkout',
'short_description',
'description',
'meta_title',
'meta_keywords',
'meta_description',
'weight',
$faker = Factory::create();
$brand = Attribute::where(['code' => 'brand'])->first(); // usually 25
if (! AttributeOption::where(['attribute_id' => $brand->id])->exists()) {
AttributeOption::create([
'admin_name' => 'Webkul Demo Brand (c) 2020',
'attribute_id' => $brand->id,
]);
}
/** @var array $defaultAttributeValues
* Some defaults that should apply to all generated products.
* By defaults products will be generated as saleable.
* If you do not want this, this defaults can be overriden by $attributeValues.
*/
$defaultAttributeValues = [
'name' => $faker->word,
'description' => $faker->sentence,
'short_description' => $faker->sentence,
'sku' => $faker->word,
'url_key' => $faker->slug,
'status' => true,
'visible_individually' => true,
'special_price_from' => null,
'special_price_to' => null,
'special_price' => null,
'price' => '1.00',
'weight' => '1.00', // necessary for shipping
'brand' => AttributeOption::firstWhere('attribute_id', $brand->id)->id,
];
foreach ($productAttributeValues as $attribute) {
$data = ['product_id' => $productId];
$attributeValues = array_merge($defaultAttributeValues, $attributeValues);
if (array_key_exists($attribute, $attributeValues)) {
$fieldName = self::getAttributeFieldName($attribute);
/** @var array $possibleAttributeValues list of the possible attributes a product can have */
$possibleAttributeValues = Attribute::all()->pluck('code')->toArray();
if (! array_key_exists($fieldName, $data)) {
$data[$fieldName] = $attributeValues[$attribute];
} else {
$data = [$fieldName => $attributeValues[$attribute]];
}
// set a value for _every_ possible attribute, even if empty:
foreach ($attributeValues as $attribute => $value) {
if (! in_array($attribute, $possibleAttributeValues)) {
throw new \Exception(
"The given attribute '{$attribute}' does not exist in the 'attributes' table (column: code)");
}
$I->have(ProductAttributeValue::class, $data, $attribute);
$attributeId = Attribute::query()
->where('code', $attribute)
->select('id')
->firstOrFail()
->id;
$data = [
'product_id' => $product->id,
'attribute_id' => $attributeId,
];
$fieldName = self::getAttributeFieldName($attribute);
$data[$fieldName] = $value;
$I->have(ProductAttributeValue::class, $data);
}
}
}

View File

@ -5,282 +5,13 @@
use Faker\Generator as Faker;
use Webkul\Product\Models\Product;
use Webkul\Product\Models\ProductAttributeValue;
use Webkul\Attribute\Models\AttributeOption;
$factory->defineAs(ProductAttributeValue::class, 'sku', function (Faker $faker) {
$factory->define(ProductAttributeValue::class, function (Faker $faker) {
return [
'product_id' => function () {
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'text_value' => $faker->uuid,
'attribute_id' => 1,
'locale' => 'en',
'channel' => 'default',
];
});
$factory->defineAs(ProductAttributeValue::class, 'name', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->words(2, true),
'attribute_id' => 2,
];
});
$factory->defineAs(ProductAttributeValue::class, 'url_key', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'text_value' => $faker->unique()->slug,
'attribute_id' => 3,
];
});
$factory->defineAs(ProductAttributeValue::class, 'tax_category_id', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'channel' => 'default',
'integer_value' => null,
'attribute_id' => 4,
];
});
$factory->defineAs(ProductAttributeValue::class, 'new', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'boolean_value' => 1,
'attribute_id' => 5,
];
});
$factory->defineAs(ProductAttributeValue::class, 'featured', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'boolean_value' => 1,
'attribute_id' => 6,
];
});
$factory->defineAs(ProductAttributeValue::class, 'visible_individually', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'boolean_value' => 1,
'attribute_id' => 7,
];
});
$factory->defineAs(ProductAttributeValue::class, 'status', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'boolean_value' => 1,
'attribute_id' => 8,
];
});
$factory->defineAs(ProductAttributeValue::class, 'short_description', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->sentence,
'attribute_id' => 9,
];
});
$factory->defineAs(ProductAttributeValue::class, 'description', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->sentences(3, true),
'attribute_id' => 10,
];
});
$factory->defineAs(ProductAttributeValue::class, 'price', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'float_value' => $faker->randomFloat(4, 0, 1000),
'attribute_id' => 11,
];
});
$factory->defineAs(ProductAttributeValue::class, 'cost', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'channel' => 'default',
'float_value' => $faker->randomFloat(4, 0, 10),
'attribute_id' => 12,
];
});
$factory->defineAs(ProductAttributeValue::class, 'special_price', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'float_value' => $faker->randomFloat(4, 0, 100),
'attribute_id' => 13,
];
});
$factory->defineAs(ProductAttributeValue::class, 'special_price_from', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'channel' => 'default',
'date_value' => $faker->dateTimeBetween('-5 days', 'now', 'Europe/Berlin'),
'attribute_id' => 14,
];
});
$factory->defineAs(ProductAttributeValue::class, 'special_price_to', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'channel' => 'default',
'date_value' => $faker->dateTimeBetween('now', '+ 5 days', 'Europe/Berlin'),
'attribute_id' => 15,
];
});
$factory->defineAs(ProductAttributeValue::class, 'meta_title', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->words(2, true),
'attribute_id' => 16,
];
});
$factory->defineAs(ProductAttributeValue::class, 'meta_keywords', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->words(5, true),
'attribute_id' => 17,
];
});
$factory->defineAs(ProductAttributeValue::class, 'meta_description', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'locale' => 'en', //$faker->languageCode,
'channel' => 'default',
'text_value' => $faker->sentence,
'attribute_id' => 18,
];
});
$factory->defineAs(ProductAttributeValue::class, 'width', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 50),
'attribute_id' => 19,
];
});
$factory->defineAs(ProductAttributeValue::class, 'height', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 50),
'attribute_id' => 20,
];
});
$factory->defineAs(ProductAttributeValue::class, 'depth', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 50),
'attribute_id' => 21,
];
});
$factory->defineAs(ProductAttributeValue::class, 'weight', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 50),
'attribute_id' => 22,
];
});
$factory->defineAs(ProductAttributeValue::class, 'color', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 5),
'attribute_id' => 23,
];
});
$factory->defineAs(ProductAttributeValue::class, 'size', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'integer_value' => $faker->numberBetween(1, 5),
'attribute_id' => 24,
];
});
$factory->defineAs(ProductAttributeValue::class, 'brand', function (Faker $faker) {
return [
'product_id' => function () {
return factory(Product::class)->create()->id;
},
'attribute_id' => 25,
'integer_value' => function () {
return factory(AttributeOption::class)->create()->id;
},
];
});
$factory->defineAs(ProductAttributeValue::class, 'guest_checkout', function ( Faker $faker) {
return [
'product_id' => function() {
return factory(Product::class)->create()->id;
},
'boolean_value' => 1,
'attribute_id' => 26,
];
});