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 implementation of EAV fixtures loader
*
*/
abstract class EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract extends EcomDev_PHPUnit_Model_Mysql4_Fixture
{
/**
* List of indexers required to build
*
* @var array
*/
protected $_requiredIndexers = array();
/**
* Fixture options
*
* @var array
*/
protected $_options = array();
/**
* Retrieve required indexers for re-building
*
* @var array
*/
public function getRequiredIndexers()
{
return $this->_requiredIndexers;
}
/**
* Set fixture options
*
* @param array $options
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function setOptions(array $options)
{
$this->_options = $options;
return $this;
}
/**
* Add indexer by specific code to required indexers list
*
* @param string $code
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function addRequiredIndexer($code)
{
if (!in_array($code, $this->_requiredIndexers)) {
$this->_requiredIndexers[] = $code;
}
return $this;
}
/**
* Clean entity data table
*
* @param string $entityType
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
public function cleanEntity($entityType)
{
$entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType);
$this->cleanTable($entityTypeModel->getEntityTable());
return $this;
}
/**
* Loads EAV data into DB tables
*
* @param string $entityType
* @param array $values
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
* @throws RuntimeException
*/
public function loadEntity($entityType, $values)
{
$entityTypeModel = Mage::getSingleton('eav/config')->getEntityType($entityType);
$entityTableColumns = $this->_getWriteAdapter()->describeTable(
$this->getTable($entityTypeModel->getEntityTable())
);
$attributeTableColumns = $this->_getAttributeTablesColumnList($entityTypeModel);
$entities = array();
$entityValues = array();
// Custom values array is used for
// inserting custom entity data in custom tables.
// It is an associative array with table name as key,
// and rows list as value
// See getCustomTableRecords
$customValues = array();
foreach ($values as $index => &$row) {
if (!isset($row[$this->_getEntityIdField($entityTypeModel)])) {
throw new RuntimeException('Entity Id should be specified in EAV fixture');
}
// Fullfil neccessary information
$row['entity_type_id'] = $entityTypeModel->getEntityTypeId();
if (!isset($row['attribute_set_id'])) {
$row['attribute_set_id'] = $entityTypeModel->getDefaultAttributeSetId();
}
// Preparing entity table record
$entity = $this->_getTableRecord($row, $entityTableColumns);
$entities[] = $entity;
// Preparing simple attributes records
foreach ($entityTypeModel->getAttributeCollection() as $attribute) {
$attributeBackendTable = $attribute->getBackendTable();
if (!$attribute->isStatic()
&& $attributeBackendTable
&& isset($attributeTableColumns[$attributeBackendTable])) {
// Prepearing data for insert per attribute table
$attributeRecords = $this->_getAttributeRecords(
$row,
$attribute,
$attributeTableColumns[$attributeBackendTable]
);
if ($attributeRecords) {
if (!isset($entityValues[$attributeBackendTable])) {
$entityValues[$attributeBackendTable] = array();
}
$entityValues[$attributeBackendTable] = array_merge(
$entityValues[$attributeBackendTable],
$attributeRecords
);
}
}
}
// Processing custom entity values
$customValues = array_merge_recursive(
$customValues,
$this->_getCustomTableRecords($row, $entityTypeModel)
);
}
$this->_getWriteAdapter()->insertOnDuplicate(
$this->getTable($entityTypeModel->getEntityTable()),
$entities
);
foreach ($entityValues as $tableName => $records) {
$this->_getWriteAdapter()->insertOnDuplicate(
$tableName,
$records
);
}
foreach ($customValues as $tableName => $records) {
$this->_getWriteAdapter()->insertOnDuplicate(
(strpos($tableName, '/') !== false ? $this->getTable($tableName) : $tableName),
$records
);
}
foreach ($entities as $entity) {
$this->_customEntityAction($entity, $entityTypeModel);
}
if (empty($this->_options['doNotIndexAll'])) {
$indexer = Mage::getSingleton('index/indexer');
foreach ($this->getRequiredIndexers() as $indexerCode) {
if (empty($this->_options['doNotIndex'])
|| !in_array($indexerCode, $this->_options['doNotIndex'])) {
$indexer->getProcessByCode($indexerCode)
->reindexAll();
}
}
}
return $this;
}
/**
* Performs custom action on entity
*
* @param array $entity
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return EcomDev_PHPUnit_Model_Mysql4_Fixture_Eav_Abstract
*/
protected function _customEntityAction($entity, $entityTypeModel)
{
return $this;
}
/**
* If you have some custom EAV tables,
* this method will help you to insert
* them on fixture processing step
* It should return an associative array, where an entry key
* is the table name and its value is a list of table rows
*
* @example
* return array(
* 'some/table' => array(
* array(
* 'field' => 'value'
* )
* )
* )
*
* @param array $row
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getCustomTableRecords($row, $entityTypeModel)
{
return array();
}
/**
* Retrieves associative list of attribute tables and their columns
*
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
* @return array
*/
protected function _getAttributeTablesColumnList($entityTypeModel)
{
$tableNames = array_unique(
$entityTypeModel->getAttributeCollection()
->walk('getBackendTable')
);
$columnsByTable = array();
foreach ($tableNames as $table) {
if ($table) {
$columnsByTable[$table] = $this->_getWriteAdapter()
->describeTable(
$table
);
}
}
return $columnsByTable;
}
/**
* Prepares entity table record from array
*
* @param array $row
* @param array $tableColumns list of entity_table columns
* @return array
*/
protected function _getTableRecord($row, $tableColumns)
{
$record = array();
// Fullfil table records with data
foreach ($tableColumns as $columnName => $definition) {
if (isset($row[$columnName])) {
$record[$columnName] = $row[$columnName];
} elseif ($definition['DEFAULT'] !== null) {
$record[$columnName] = $definition['DEFAULT'];
} else {
$record[$columnName] = (($definition['NULLABLE']) ? null : '');
}
}
return $record;
}
/**
* Retrieves attribute records for single entity
*
* @param array $row
* @param array $attribute
* @param Mage_Eav_Model_Entity_Type $entityTypeModel
*/
protected function _getAttributeRecords($row, $attribute, $tableColumns)
{
$records = array();
$value = $this->_getAttributeValue($row, $attribute);
if ($value !== null) {
$valueInfo = $this->_getAttributeValueInfo($row, $attribute);
$valueInfo['value'] = $value;
$records[] = $this->_getTableRecord($valueInfo, $tableColumns);
}
return $records;
}
/**
* Returns attribute meta info for record,
* e.g. entity_type_id, attribute_id, etc
*
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @return array
*/
protected function _getAttributeValueInfo($row, $attribute)
{
return array(
'attribute_id' => $attribute->getId(),
'entity_type_id' => $attribute->getEntityTypeId(),
$this->_getEntityIdField($attribute) => $row[$this->_getEntityIdField($attribute)]
);
}
/**
* Retrieves attribute value
*
* @param array $row
* @param Mage_Eav_Model_Entity_Attribute $attribute
* @return mixed|null
*/
protected function _getAttributeValue($row, $attribute)
{
if (isset($row[$attribute->getAttributeCode()]) && !is_array($row[$attribute->getAttributeCode()])) {
$value = $row[$attribute->getAttributeCode()];
} elseif ($attribute->getIsRequired()
&& $attribute->getDefaultValue() !== null
&& $attribute->getDefaultValue() !== ''
&& !is_array($attribute->getDefaultValue())) {
$value = $attribute->getDefaultValue();
} else {
$value = null;
}
return $value;
}
/**
* Retrieves entity id field, based on entity configuration
*
* @param Mage_Eav_Model_Entity_Type|Mage_Eav_Model_Entity_Attribute $entityTypeModel
* @return string
*/
protected function _getEntityIdField($entityTypeModel)
{
if ($entityTypeModel->getEntityIdField()) {
return $entityTypeModel->getEntityIdField();
}
return Mage_Eav_Model_Entity::DEFAULT_ENTITY_ID_FIELD;
}
}
\ 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>
*/
/**
* 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