We will work on Apr 26th (Saturday) and will be off from Apr 30th (Wednesday) until May 2nd (Friday) for public holiday in our country

Commit c03bbe26 authored by Ivan Chepurnyi's avatar Ivan Chepurnyi

+ Feature #25 Implement EAV Fixture loading

    = eav type of fixture row, automatically executable indexes 
    = possibility to disable indexer run via @doNotIndexAll or @doNotIndex indexer_code
    = full Global/Website/Store support
    = full product & categories support    
+ Feature #26: Implement GWS Fixture
    = scope fixture type
    = $this->setCurrentStore() inside of the test
! Test suite application was refactored to improve absolutely isolated execution
parent 4cd00ef8
......@@ -56,6 +56,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/
protected static $_oldEventCollection = null;
/**
* List of singletons in original application
*
* @var array
*/
protected static $_oldRegistry = null;
/**
* Configuration model class name for unit tests
*
......@@ -77,6 +84,13 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
*/
protected static $_cacheClass = 'EcomDev_PHPUnit_Model_Cache';
/**
* Enabled events flag
*
* @var boolean
*/
protected $_eventsEnabled = true;
/**
* This method replaces application, event and config objects
* in Mage to perform unit tests in separate Magento steam
......@@ -88,15 +102,14 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
self::$_oldApplication = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_app');
self::$_oldConfig = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_config');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_events');
$resource = Mage::getSingleton('core/resource');
self::$_oldEventCollection = EcomDev_Utils_Reflection::getRestrictedPropertyValue('Mage', '_registry');
// Setting environment variables for unit tests
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', new self::$_configClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', new self);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', new self::$_eventCollectionClass);
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array());
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', array());
// All unit tests will be runned in admin scope, to get rid of frontend restrictions
Mage::app()->initTest();
......@@ -113,7 +126,22 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
$this->_config->setOptions($options);
$this->_initBaseConfig();
$this->_initCache();
// Set using cache
// for things that shouldn't be reloaded each time
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$this->_cache,
'_allowedCacheOptions',
array(
'eav' => 1,
'layout' => 1,
'translate' => 1
)
);
// Clean cache before the whole suite is running
$this->getCache()->clean();
// Init modules runs install proccess for table structures,
// It is required for setting up proper setup script
$this->_initModules();
......@@ -127,6 +155,18 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
return $this;
}
/**
* Overriden to fix issue with stores loading
* (non-PHPdoc)
* @see Mage_Core_Model_App::_initStores()
*/
protected function _initStores()
{
$this->_store = null;
parent::_initStores();
return $this;
}
/**
* Discard test scope for application, returns all the objects from live version
*
......@@ -137,18 +177,44 @@ class EcomDev_PHPUnit_Model_App extends Mage_Core_Model_App
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_app', self::$_oldApplication);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_config', self::$_oldConfig);
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_events', self::$_oldEventCollection);
$resource = Mage::getSingleton('core/resource');
EcomDev_Utils_Reflection::setRestrictedPropertyValue($resource, '_connections', array());
EcomDev_Utils_Reflection::setRestrictedPropertyValue('Mage', '_registry', self::$_oldRegistry);
}
/**
* Disables events fire
*
* @return EcomDev_PHPUnit_Model_App
*/
public function disableEvents()
{
$this->_eventsEnabled = false;
return $this;
}
/**
* We will not use cache for UnitTests
*
* @return boolean
*/
public function useCache($type=null)
/**
* Enable events fire
*
* @return EcomDev_PHPUnit_Model_App
*/
public function enableEvents()
{
return false;
$this->_eventsEnabled = true;
return $this;
}
/**
* Overriden for disabling events
* fire during fixutre loading
*
* (non-PHPdoc)
* @see Mage_Core_Model_App::dispatchEvent()
*/
public function dispatchEvent($eventName, $args)
{
if ($this->_eventsEnabled) {
parent::dispatchEvent($eventName, $args);
}
return $this;
}
}
......@@ -23,6 +23,77 @@
*/
class EcomDev_PHPUnit_Model_Config extends Mage_Core_Model_Config
{
/**
* Scope snapshot without applied configurations,
* It is used for proper store/website/default loading on per store basis
*
* @var Mage_Core_Model_Config_Base
*/
protected $_scopeSnapshot = null;
/**
* Load config data from DB
*
* @return Mage_Core_Model_Config
*/
public function loadDb()
{
if ($this->_isLocalConfigLoaded
&& Mage::isInstalled()
&& $this->_scopeSnapshot === null) {
$this->saveScopeSnapshot();
}
parent::loadDb();
return $this;
}
/**
* Loads scope snapshot
*
* @return EcomDev_PHPUnit_Model_Config
*/
public function loadScopeSnapshot()
{
if ($this->_scopeSnapshot === null) {
throw new RuntimeException('Cannot load scope snapshot, because it was not saved before');
}
$scopeNode = $this->_scopeSnapshot->getNode();
foreach ($scopeNode->children() as $nodeName => $values) {
// Remove somehow modified before xml node
unset($this->getNode()->$nodeName);
// Add saved snapshot of configuration node
$this->getNode()->addChild($nodeName);
$this->getNode()->$nodeName->extend($values);
}
return $this;
}
/**
* Saves current configuration snapshot,
* for pussible restoring in feature
*
* @param array $nodesToSave list of nodes for saving data, by default it is 'default', 'webistes', 'stores'
* @return EcomDev_PHPUnit_Model_Config
*/
public function saveScopeSnapshot($nodesToSave = array('default', 'websites', 'stores'))
{
$this->_scopeSnapshot = clone $this->_prototype;
$this->_scopeSnapshot->loadString('<config />');
$scopeNode = $this->_scopeSnapshot->getNode();
foreach ($nodesToSave as $node) {
$scopeNode->addChild($node);
$scopeNode->{$node}->extend(
$this->getNode($node),
true
);
}
return $this;
}
/**
* Loads additional configuration for unit tests
* (non-PHPdoc)
......
......@@ -28,6 +28,15 @@ require_once 'Spyc/spyc.php';
*/
class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
{
// Configuration path for eav loaders
const XML_PATH_FIXTURE_EAV_LOADERS = 'phpunit/suite/fixture/eav';
// Default eav loader class node in loaders configuration
const DEFAULT_EAV_LOADER_NODE = 'default';
// Default eav loader class alias
const DEFAULT_EAV_LOADER_CLASS = 'ecomdev_phpunit/fixture_eav_default';
/**
* Fixtures array, contains config,
* table and eav keys.
......@@ -59,13 +68,13 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/
protected $_fixture = array();
/**
* Associative array of configuration values that was changed by fixture,
* it is used to preserve
* Fixture options
*
* @var array
*/
protected $_originalConfiguration = array();
protected $_options = array();
/**
* Associative array of configuration nodes xml that was changed by fixture,
......@@ -75,6 +84,14 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/
protected $_originalConfigurationXml = array();
/**
* Hash of current scope instances (store, website, group)
*
* @return array
*/
protected $_currentScope = array();
/**
* Model constuctor, just defines wich resource model to use
* (non-PHPdoc)
......@@ -85,6 +102,18 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
$this->_init('ecomdev_phpunit/fixture');
}
/**
* Set fixture options
*
* @param array $options
* @return EcomDev_PHPUnit_Model_Fixture
*/
public function setOptions(array $options)
{
$this->_options = $options;
return $this;
}
/**
* Load YAML file
*
......@@ -152,47 +181,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
if (!is_array($configuration)) {
throw new InvalidArgumentException('Configuration part should be an associative list');
}
Mage::getConfig()->loadScopeSnapshot();
foreach ($configuration as $path => $value) {
$this->_originalConfiguration[$path] = (string) Mage::getConfig()->getNode($path);
$this->_setConfigNodeValue($path, $value);
}
return $this;
}
/**
* Setting config value with applying the values to stores and websites
*
* @param string $path
* @param string $value
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _setConfigNodeValue($path, $value)
{
$pathArray = explode('/', $path);
$scope = array_shift($pathArray);
switch ($scope) {
case 'stores':
$storeCode = array_shift($pathArray);
Mage::app()->getStore($storeCode)->setConfig(
implode('/', $pathArray), $value
);
break;
case 'websites':
$websiteCode = array_shift($pathArray);
Mage::app()->getWebsite($websiteCode)->setConfig(
implode('/', $pathArray), $value
);
break;
default:
Mage::getConfig()->setNode($path, $value);
break;
}
Mage::getConfig()->loadDb();
Mage::app()->reinitStores();
return $this;
}
......@@ -238,13 +232,12 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
*/
protected function _discardConfig()
{
foreach ($this->_originalConfiguration as $path => $value) {
$this->_setConfigNodeValue($path, $value);
}
$this->_originalConfiguration = array();
Mage::getConfig()->loadScopeSnapshot();
Mage::getConfig()->loadDb();
Mage::app()->reinitStores();
return $this;
}
/**
* Reverts fixture configuration xml values in Mage_Core_Model_Config
*
......@@ -306,10 +299,165 @@ class EcomDev_PHPUnit_Model_Fixture extends Mage_Core_Model_Abstract
}
/**
* @todo Create Implementation for EAV models
* Setting config value with applying the values to stores and websites
*
* @param string $path
* @param string $value
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _setConfigNodeValue($path, $value)
{
$pathArray = explode('/', $path);
$scope = array_shift($pathArray);
switch ($scope) {
case 'stores':
$storeCode = array_shift($pathArray);
Mage::app()->getStore($storeCode)->setConfig(
implode('/', $pathArray), $value
);
break;
case 'websites':
$websiteCode = array_shift($pathArray);
$website = Mage::app()->getWebsite($websiteCode);
$website->setConfig(implode('/', $pathArray), $value);
break;
default:
Mage::getConfig()->setNode($path, $value);
break;
}
return $this;
}
/**
* Retrieves eav loader for a particular entity type
*
* @param string $entityType
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
protected function _getEavLoader($entityType)
{
$loaders = Mage::getConfig()->getNode(self::XML_PATH_FIXTURE_EAV_LOADERS);
if (isset($loaders->$entityType)) {
$classAlias = (string)$loaders->$entityType;
} elseif (isset($loaders->{self::DEFAULT_EAV_LOADER_NODE})) {
$classAlias = (string)$loaders->{self::DEFAULT_EAV_LOADER_NODE};
} else {
$classAlias = self::DEFAULT_EAV_LOADER_CLASS;
}
return Mage::getResourceSingleton($classAlias);
}
/**
* @todo Create Implementation for Websites/Stores/Groups
* Applies fixture EAV values
*
* @param array $configuration
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyEav($entities)
{
if (!is_array($entities)) {
throw new InvalidArgumentException('EAV part should be an associative list with rows as value and entity type as key');
}
foreach ($entities as $entityType => $values) {
$this->_getEavLoader($entityType)
->setOptions($this->_options)
->loadEntity($entityType, $values);
}
return $this;
}
/**
* Clean applied eav data
*
* @param array $entities
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardEav($entities)
{
foreach (array_keys($entities) as $entityType) {
$this->_getEavLoader($entityType)
->cleanEntity($entityType);
}
return $this;
}
/**
* Applies scope fixture,
* i.e., website, store, store group
*
* @param array $types
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _applyScope($types)
{
$modelByType = array(
'store' => 'core/store',
'group' => 'core/store_group',
'website' => 'core/website'
);
Mage::app()->disableEvents();
foreach ($types as $type => $rows) {
if (!isset($modelByType[$type])) {
throw new RuntimeException(sprintf('Unknown "%s" scope type specified', $type));
}
foreach ($rows as $row) {
$scopeModel = Mage::getModel($modelByType[$type]);
$this->_currentScope[$type][] = $scopeModel;
$scopeModel->setData($row);
// Change property for saving new objects with specified ids
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', true
);
$scopeModel->isObjectNew(true);
$scopeModel->save();
// Revert changed property
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
$scopeModel->getResource(), '_useIsObjectNew', false
);
}
}
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
/**
* Removes scope fixture changes,
* i.e., website, store, store group
*
* @return EcomDev_PHPUnit_Model_Fixture
*/
protected function _discardScope()
{
Mage::app()->disableEvents();
$scope = array_reverse($this->_currentScope);
foreach ($scope as $models) {
foreach ($models as $model) {
$model->delete();
}
}
$this->_currentScope = array();
Mage::app()->getCache()->clean(
Zend_Cache::CLEANING_MODE_MATCHING_TAG,
array(Mage_Core_Model_Mysql4_Collection_Abstract::CACHE_TAG)
);
Mage::app()->enableEvents();
Mage::app()->reinitStores();
return $this;
}
}
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Base Catalog EAV fixture loader
*
*
*/
abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
const SCOPE_TYPE_STORE = 'stores';
const SCOPE_TYPE_WEBSITE = 'websites';
/**
* Overriden to add GWS implementation for attribute records
*
* @param array $row
* @param Mage_Catalog_Model_Resource_Eav_Attribute $attribute
* @param array $tableColumns
* @return array
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
$records = parent::_getAttributeRecords($row, $attribute, $tableColumns);
// If the attribute is not global,
// then walk over all websites and stores scopes for attribute value
if ($attribute->isScopeStore() || $attribute->isScopeWebsite()) {
// Search for website values and fullfil data per website's store
$storeValues = array();
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_WEBSITE) as $websiteCode) {
$website = Mage::app()->getWebsite($websiteCode);
$value = $this->_getGwsValue($row, $attribute, $websiteCode, self::SCOPE_TYPE_WEBSITE);
if ($value !== null) {
foreach ($website->getStoreIds() as $storeId) {
$storeValues[$storeId] = $value;
}
}
}
// If attribute has store scope, then override website values by store ones
if ($attribute->isScopeStore()) {
foreach ($this->_getGwsCodes($row, self::SCOPE_TYPE_STORE) as $storeCode) {
$store = Mage::app()->getStore($storeCode);
$value = $this->_getGwsValue($row, $attribute, $storeCode, self::SCOPE_TYPE_STORE);
if ($value !== null) {
$storeValues[$store->getId()] = $value;
}
}
}
// Apply collected values
$valueInfo = $this->_getAttributeValueInfo($row, $attribute);
foreach ($storeValues as $storeId => $value) {
$valueInfo['store_id'] = $storeId;
$valueInfo['value'] = $value;
$records[] = $this->_getTableRecord($valueInfo, $tableColumns);
}
}
return $records;
}
/**
* Check is available store/website values
*
* @param array $row
* @param string $scopeType
* @return boolean
*/
protected function _hasGwsValues($row, $scopeType = self::SCOPE_TYPE_STORE)
{
return isset($row['/' . $scopeType]);
}
/**
* Retrieves list of websites/stores codes
*
* @param array $row
* @param string $scopeType
*/
protected function _getGwsCodes($row, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!$this->_hasGwsValues($row, $scopeType)) {
return array();
}
return array_keys($row['/' . $scopeType]);
}
/**
* Retrieves scope dependent value from fixture value, i.e,
* store view or
* website attribute value
*
*
* @param array $row
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @param string $scopeCode
* @param string $scopeType
* @return mixed|null
*/
protected function _getGwsValue($row, $attribute, $scopeCode, $scopeType = self::SCOPE_TYPE_STORE)
{
if (!isset($row['/' . $scopeType][$scopeCode]) || !is_array($row['/' . $scopeType][$scopeCode])) {
return null;
}
return $this->_getAttributeValue($row['/' . $scopeType][$scopeCode], $attribute);
}
/**
* Overriden to add default store id
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getAttributeValueInfo()
*/
protected function _getAttributeValueInfo($row, $attribute)
{
$info = parent::_getAttributeValueInfo($row, $attribute);
$info['store_id'] = 0;
return $info;
}
}
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Category EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Category extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'catalog_category_flat'
);
/**
* Overriden to add easy fixture loading for product associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
return $this->_getProductAssociationRecords($row, $entityTypeModel);
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductAssociationRecords($row, $entityTypeModel)
{
if (isset($row['products']) && is_array($row['products'])) {
$records = array();
foreach ($row['products'] as $productId => $position) {
$records[] = array(
'category_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'product_id' => $productId,
'position' => $position
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Product EAV fixture loader
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Product extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract
{
protected $_requiredIndexers = array(
'cataloginventory_stock',
'catalog_product_flat',
'catalog_product_attribute',
'catalog_product_price'
);
/**
* Ovveriden to fix issue with flat tables existance mark
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::loadEntity()
*/
public function loadEntity($entityType, $values)
{
// Fix of Product Flat Indexer
EcomDev_Utils_Reflection::setRestrictedPropertyValue(
Mage::getResourceSingleton('catalog/product_flat_indexer'),
'_existsFlatTables',
array()
);
return parent::loadEntity($entityType, $values);
}
/**
* Overriden to add easy fixture loading for websites and categories associations
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_getCustomTableRecords()
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
$records = array();
$records += $this->_getWebsiteVisibilityRecords($row, $entityTypeModel);
$records += $this->_getTierPriceRecords($row, $entityTypeModel);
$records += $this->_getCategoryAssociationRecords($row, $entityTypeModel);
$records += $this->_getProductStockRecords($row, $entityTypeModel);
return $records;
}
/**
* Changed to support price attribute type multi-scope
* (non-PHPdoc)
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Catalog_Abstract::_getAttributeRecords()
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
if ($attribute->getFrontendInput() == 'price') {
$attribute->setIsGlobal(
Mage_Catalog_Model_Resource_Eav_Attribute::SCOPE_WEBSITE
);
}
return parent::_getAttributeRecords($row, $attribute, $tableColumns);
}
/**
* Generates records for catalog_product_website table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
* @throws RuntimeException
*/
protected function _getWebsiteVisibilityRecords($row, $entityTypeModel)
{
if (isset($row['website_ids']) && is_array($row['website_ids'])) {
$records = array();
foreach ($row['website_ids'] as $websiteId) {
$website = Mage::app()->getWebsite($websiteId);
$records[] = array(
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)],
'website_id' => $website->getId()
);
}
// We shouldn't return empty table data
if ($records) {
return array('catalog/product_website' => $records);
}
}
return array();
}
/**
* Generates records for catalog_product_entity_tier_price table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getTierPriceRecords($row, $entityTypeModel)
{
if (isset($row['tier_price']) && is_array($row['tier_price'])) {
$tableName = $entityTypeModel->getValueTablePrefix() . '_tier_price';
$columns = $this->_getWriteAdapter()->describeTable(
$tableName
);
$records = array();
foreach ($row['tier_price'] as $tierPrice) {
$tierPrice[$this->_getEntityIdField($entityTypeModel)] = $row[$this->_getEntityIdField($entityTypeModel)];
$records[] = $this->_getTableRecord($tierPrice, $columns);
}
if ($records) {
return array($tableName => $records);
}
}
return array();
}
/**
* Generates records for catalog_category_product table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getCategoryAssociationRecords($row, $entityTypeModel)
{
if (isset($row['category_ids']) && is_array($row['category_ids'])) {
$records = array();
foreach ($row['category_ids'] as $categoryId) {
$records[] = array(
'category_id' => $categoryId,
'product_id' => $row[$this->_getEntityIdField($entityTypeModel)]
);
}
if ($records) {
$this->addRequiredIndexer('catalog_category_product');
return array('catalog/category_product' => $records);
}
}
return array();
}
/**
* Generates records for cataloginventory_stock_item table
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getProductStockRecords($row, $entityTypeModel)
{
if (isset($row['stock']) && is_array($row['stock'])) {
$columns = $this->_getWriteAdapter()->describeTable(
$this->getTable('cataloginventory/stock_item')
);
$row['stock']['product_id'] = $row[$this->_getEntityIdField($entityTypeModel)];
if (!isset($row['stock']['stock_id'])) {
$row['stock']['stock_id'] = 1;
}
return array(
'cataloginventory/stock_item' => array(
$this->_getTableRecord($row['stock'], $columns)
)
);
}
return array();
}
/**
* Adding enabled and visibility indexes
*
* (non-PHPdoc)
* @see EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract::_customEntityAction()
*/
protected function _customEntityAction($entity, $entityTypeModel)
{
Mage::getResourceSingleton('catalog/product_status')
->refreshEnabledIndex($entity[$this->_getEntityIdField($entityTypeModel)], 0);
parent::_customEntityAction($entity, $entityTypeModel);
return $this;
}
}
\ No newline at end of file
<?php
/**
* PHP Unit test suite for Magento
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
*
* @category EcomDev
* @package EcomDev_PHPUnit
* @copyright Copyright (c) 2011 Ecommerce Developers (http://www.ecomdev.org)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
* @author Ivan Chepurnyi <ivan.chepurnyi@ecomdev.org>
*/
/**
* Default EAV fixture loaded
*
*
*/
class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Default extends EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
{
}
\ No newline at end of file
......@@ -34,6 +34,14 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/
protected $_expectations = null;
/**
* Original store kept for tearDown,
* if was set in test method
*
* @var Mage_Core_Model_Store
*/
protected $_originalStore = null;
/**
* Loads expectations for current test case
*
......@@ -90,7 +98,7 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
if (!is_array($dataPart)) {
throw new InvalidArgumentException(
'Argument values for specifying special scope of expectations should be presented '
. ' in expectation file and should be an associative list'
. ' in expectation file and should be an associative list (dataKey: "' . $dataKey . '")'
);
}
......@@ -168,7 +176,8 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
$this->_getFixture()->loadYaml($filePath);
}
}
// Pass methods for
$this->_getFixture()->setOptions($annotations['method']);
$this->_getFixture()->apply();
parent::setUp();
}
......@@ -192,6 +201,24 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
return Spyc::YAMLLoad($filePath);
}
/**
* Set current store scope for test
*
* @param int|string|Mage_Core_Model_Store $store
* @return EcomDev_PHPUnit_Test_Case
*/
public function setCurrentStore($store)
{
if (!$this->_originalStore) {
$this->_originalStore = Mage::app()->getStore();
}
Mage::app()->setCurrentStore(
Mage::app()->getStore($store)
);
return $this;
}
/**
* Performs a clean up after a particular test was run
* (non-PHPdoc)
......@@ -199,6 +226,11 @@ abstract class EcomDev_PHPUnit_Test_Case extends PHPUnit_Framework_TestCase
*/
protected function tearDown()
{
if ($this->_originalStore) { // Remove store scope, that was set in test
Mage::app()->setCurrentStore($this->_originalStore);
$this->_originalStore = null;
}
$this->_expectations = null; // Clear expectation values
$this->_getFixture()->discard(); // Clear applied fixture
parent::tearDown();
......
......@@ -29,6 +29,8 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
const XML_PATH_UNIT_TEST_GROUPS = 'phpunit/suite/groups';
const XML_PATH_UNIT_TEST_MODULES = 'phpunit/suite/modules';
const XML_PATH_UNIT_TEST_APP = 'phpunit/suite/app';
const CACHE_TAG = 'ECOMDEV_PHPUNIT';
const CACHE_TYPE = 'ecomdev_phpunit';
/**
* Setting up test scope for Magento
......@@ -86,47 +88,82 @@ class EcomDev_PHPUnit_Test_Suite extends PHPUnit_Framework_TestSuite
continue;
}
$directoryIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($searchPath)
);
$currentGroups = array(
$group->getName(),
$module->getName()
);
foreach ($directoryIterator as $fileObject) {
/* @var $fileObject SplFileObject */
// Skip entry if it is not a php file
if (!$fileObject->isFile() || $fileObject->getBasename('.php') === $fileObject->getBasename()) {
continue;
}
$testCases = self::_loadTestCases($searchPath, $moduleCodeDir);
foreach ($testCases as $className) {
$classReflection = EcomDev_Utils_Reflection::getRelflection($className);
$suite->addTest(new PHPUnit_Framework_TestSuite($classReflection), $currentGroups);
}
}
}
if (!$suite->count()) {
$suite->addTest(self::warning('There were no test cases for the current run'));
}
return $suite;
}
/**
* Loads test cases from search path,
* Will return cached result
*
* @param string $searchPath path for searching files with tests
* @param string $moduleCodeDir path where the module files are placed (e.g. app/code/local),
* used for determining the class name
*/
protected static function _loadTestCases($searchPath, $moduleCodeDir)
{
if (Mage::app()->useCache(self::CACHE_TYPE)) {
$cachedTestCases = Mage::app()->loadCache(
self::CACHE_TYPE . md5($searchPath)
);
if ($cachedTestCases) {
return unserialize($cachedTestCases);
}
}
$testCases = array();
$classPath = substr($fileObject->getPath() . DS . $fileObject->getBasename('.php'), strlen($moduleCodeDir));
$className = uc_words(ltrim($classPath, DS), '_', DS);
$directoryIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($searchPath)
);
// Add unit test case only
// if it is a valid class extended from EcomDev_PHPUnit_Test_Case
if (class_exists($className, true)) {
foreach ($directoryIterator as $fileObject) {
/* @var $fileObject SplFileObject */
// Skip entry if it is not a php file
if (!$fileObject->isFile() || $fileObject->getBasename('.php') === $fileObject->getBasename()) {
continue;
}
$reflectionClass = EcomDev_Utils_Reflection::getRelflection($className);
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case')
|| $reflectionClass->isAbstract()) {
continue;
}
$classPath = substr($fileObject->getPath() . DS . $fileObject->getBasename('.php'), strlen($moduleCodeDir));
$className = uc_words(ltrim($classPath, DS), '_', DS);
$suite->addTest(new PHPUnit_Framework_TestSuite($reflectionClass), $currentGroups);
}
// Add unit test case only
// if it is a valid class extended from EcomDev_PHPUnit_Test_Case
if (class_exists($className, true)) {
$reflectionClass = EcomDev_Utils_Reflection::getRelflection($className);
if (!$reflectionClass->isSubclassOf('EcomDev_PHPUnit_Test_Case')
|| $reflectionClass->isAbstract()) {
continue;
}
$testCases[] = $className;
}
}
if (!$suite->count()) {
$suite->addTest(self::warning('There were no test cases for the current run'));
if (Mage::app()->useCache(self::CACHE_TYPE)) {
Mage::app()->saveCache(
serialize($testCases),
self::CACHE_TYPE . md5($searchPath),
array(self::CACHE_TAG)
);
}
return $suite;
return $testCases;
}
}
......@@ -33,6 +33,15 @@
<class>EcomDev_PHPUnit_Model_Mysql4</class>
</ecomdev_phpunit_mysql4>
</models>
<cache>
<types>
<ecomdev_phpunit>
<label>Unit Test Cases</label>
<description>Unit test case file paths cache.</description>
<tags>ECOMDEV_PHPUNIT</tags>
</ecomdev_phpunit>
</types>
</cache>
</global>
<phpunit>
<suite>
......@@ -42,6 +51,16 @@
<helpers>Helper</helpers>
<blocks>Block</blocks>
</groups>
<fixture>
<eav>
<!-- Here goes the list of fixture loaders for EAV
If no fixture loader is specified for entity, then default will be used
-->
<default>ecomdev_phpunit/fixture_eav_default</default>
<catalog_product>ecomdev_phpunit/fixture_eav_catalog_product</catalog_product>
<catalog_category>ecomdev_phpunit/fixture_eav_catalog_category</catalog_category>
</eav>
</fixture>
<!-- Application model class name for running tests -->
<app>EcomDev_PHPUnit_Model_App</app>
<modules>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment