Use FixtureFactory for YAML, move helpers to FixtureContext, consistently create assets

This commit is contained in:
Ingo Schommer 2013-06-04 13:58:50 +02:00
parent ea55cbfc2c
commit fd9e84006f
2 changed files with 136 additions and 224 deletions

View File

@ -7,9 +7,11 @@ use Behat\Behat\Context\ClosuredContextInterface,
Behat\Behat\Context\BehatContext,
Behat\Behat\Context\Step,
Behat\Behat\Event\StepEvent,
Behat\Behat\Exception\PendingException;
use Behat\Mink\Driver\Selenium2Driver;
use Behat\Gherkin\Node\PyStringNode,
Behat\Behat\Event\FeatureEvent,
Behat\Behat\Event\ScenarioEvent,
Behat\Behat\Exception\PendingException,
Behat\Mink\Driver\Selenium2Driver,
Behat\Gherkin\Node\PyStringNode,
Behat\Gherkin\Node\TableNode;
// PHPUnit
@ -28,9 +30,17 @@ class FixtureContext extends BehatContext
*/
protected $fixtureFactory;
/**
* @var String Absolute path where file fixtures are located.
* These will automatically get copied to their location
* declare through the 'Given a file "..."' step defition.
*/
protected $filesPath;
protected $createdFilesPaths;
/**
* @var String Tracks all files and folders created from fixtures, for later cleanup.
*/
protected $createdFilesPaths = array();
public function __construct(array $parameters)
{
@ -47,7 +57,7 @@ class FixtureContext extends BehatContext
*/
public function getFixtureFactory() {
if(!$this->fixtureFactory) {
$this->fixtureFactory = \Injector::inst()->create('FixtureFactory', 'FixtureContextFactory');
$this->fixtureFactory = \Injector::inst()->create('FixtureFactory', 'FixtureContextFactory');
}
return $this->fixtureFactory;
}
@ -59,6 +69,59 @@ class FixtureContext extends BehatContext
$this->fixtureFactory = $factory;
}
/**
* @param String
*/
public function setFilesPath($path) {
$this->filesPath = $path;
}
/**
* @return String
*/
public function getFilesPath() {
return $this->filesPath;
}
/**
* @BeforeScenario @database-defaults
*/
public function beforeDatabaseDefaults(ScenarioEvent $event)
{
\SapphireTest::empty_temp_db();
\DB::getConn()->quiet();
$dataClasses = \ClassInfo::subclassesFor('DataObject');
array_shift($dataClasses);
foreach ($dataClasses as $dataClass) {
\singleton($dataClass)->requireDefaultRecords();
}
}
/**
* @AfterScenario
*/
public function afterResetDatabase(ScenarioEvent $event)
{
\SapphireTest::empty_temp_db();
}
/**
* @AfterScenario
*/
public function afterResetAssets(ScenarioEvent $event)
{
if (is_array($this->createdFilesPaths)) {
$createdFiles = array_reverse($this->createdFilesPaths);
foreach ($createdFiles as $path) {
if (is_dir($path)) {
\Filesystem::removeFolder($path);
} else {
@unlink($path);
}
}
}
}
/**
* Example: Given a page "Page 1"
*
@ -67,7 +130,13 @@ class FixtureContext extends BehatContext
public function stepCreateRecord($type, $id)
{
$class = $this->convertTypeToClass($type);
$this->fixtureFactory->createObject($class, $id);
if(is_a($class, 'File', true)) {
$fields = $this->prepareAsset($class, $id);
var_dump($fields);
} else {
$fields = array();
}
$this->fixtureFactory->createObject($class, $id, $fields);
}
/**
@ -87,6 +156,9 @@ class FixtureContext extends BehatContext
$class,
array_combine($matches['key'], $matches['value'])
);
if(is_a($class, 'File', true)) {
$fields = $this->prepareAsset($class, $id, $fields);
}
$this->fixtureFactory->createObject($class, $id, $fields);
}
@ -103,6 +175,9 @@ class FixtureContext extends BehatContext
$class = $this->convertTypeToClass($type);
// TODO Support more than one record
$fields = $this->convertFields($class, $fieldsTable->getRowsHash());
if(is_a($class, 'File', true)) {
$fields = $this->prepareAsset($class, $id, $fields);
}
$this->fixtureFactory->createObject($class, $id, $fields);
}
@ -174,6 +249,50 @@ class FixtureContext extends BehatContext
}
}
/**
* @Given /^there are the following ([^\s]*) records$/
*/
public function stepThereAreTheFollowingRecords($dataObject, PyStringNode $string)
{
$yaml = array_merge(array($dataObject . ':'), $string->getLines());
$yaml = implode("\n ", $yaml);
// Save fixtures into database
// TODO Run prepareAsset() for each File and Folder record
$yamlFixture = new \YamlFixture($yaml);
$yamlFixture->writeInto($this->getFixtureFactory());
}
protected function prepareAsset($class, $identifier, $data = null) {
if(!$data) $data = array();
$relativeTargetPath = (isset($data['Filename'])) ? $data['Filename'] : $identifier;
$relativeTargetPath = preg_replace('/^' . ASSETS_DIR . '/', '', $relativeTargetPath);
$targetPath = $this->joinPaths(ASSETS_PATH, $relativeTargetPath);
$sourcePath = $this->joinPaths($this->getFilesPath(), basename($relativeTargetPath));
// Create file or folder on filesystem
if(is_a($class, 'Folder', true)) {
$parent = \Folder::find_or_make($relativeTargetPath);
} else {
if(!file_exists($sourcePath)) {
throw new \InvalidArgumentException(sprintf(
'Source file for "%s" cannot be found in "%s"',
$targetPath,
$sourcePath
));
}
$parent = \Folder::find_or_make(dirname($relativeTargetPath));
copy($sourcePath, $targetPath);
}
$data['Filename'] = $this->joinPaths(ASSETS_DIR, $relativeTargetPath);
if(!isset($data['Name'])) $data['Name'] = basename($relativeTargetPath);
$data['ParentID'] = $parent->ID;
$this->createdFilesPaths[] = $targetPath;
return $data;
}
/**
* Converts a natural language class description to an actual class name.
* Respects {@link DataObject::$singular_name} variations.
@ -188,7 +307,7 @@ class FixtureContext extends BehatContext
// Try direct mapping
$class = str_replace(' ', '', ucfirst($type));
if(class_exists($class) || !is_subclass_of($class, 'DataObject')) {
if(class_exists($class) || !is_a($class, 'DataObject', true)) {
return $class;
}
@ -222,5 +341,14 @@ class FixtureContext extends BehatContext
}
return $fields;
}
protected function joinPaths() {
$args = func_get_args();
$paths = array();
foreach($args as $arg) $paths = array_merge($paths, (array)$arg);
foreach($paths as &$path) $path = trim($path, '/');
if (substr($args[0], 0, 1) == '/') $paths[0] = '/' . $paths[0];
return join('/', $paths);
}
}

View File

@ -58,16 +58,8 @@ class SilverStripeContext extends MinkContext implements SilverStripeAwareContex
*/
protected $screenshotPath;
/**
* @var FixtureFactory
*/
protected $fixtureFactory;
protected $context;
protected $fixtures;
protected $fixturesLazy;
protected $filesPath;
protected $createdFilesPaths;
/**
* Initializes context.
@ -136,20 +128,6 @@ class SilverStripeContext extends MinkContext implements SilverStripeAwareContex
return $this->screenshotPath;
}
public function getFixture($dataObject)
{
if (!array_key_exists($dataObject, $this->fixtures)) {
throw new \OutOfBoundsException(sprintf('Data object `%s` does not exist!', $dataObject));
}
return $this->fixtures[$dataObject];
}
public function getFixtures()
{
return $this->fixtures;
}
/**
* @BeforeScenario
*/
@ -172,191 +150,6 @@ class SilverStripeContext extends MinkContext implements SilverStripeAwareContex
$this->getSession()->visit($url);
}
/**
* @BeforeScenario @database-defaults
*/
public function beforeDatabaseDefaults(ScenarioEvent $event)
{
\SapphireTest::empty_temp_db();
\DB::getConn()->quiet();
$dataClasses = \ClassInfo::subclassesFor('DataObject');
array_shift($dataClasses);
foreach ($dataClasses as $dataClass) {
\singleton($dataClass)->requireDefaultRecords();
}
}
/**
* @AfterScenario @database-defaults
*/
public function afterDatabaseDefaults(ScenarioEvent $event)
{
\SapphireTest::empty_temp_db();
}
/**
* @AfterScenario @assets
*/
public function afterResetAssets(ScenarioEvent $event)
{
if (is_array($this->createdFilesPaths)) {
$createdFiles = array_reverse($this->createdFilesPaths);
foreach ($createdFiles as $path) {
if (is_dir($path)) {
\Filesystem::removeFolder($path);
} else {
@unlink($path);
}
}
}
\SapphireTest::empty_temp_db();
}
/**
* @Given /^there are the following ([^\s]*) records$/
*/
public function thereAreTheFollowingRecords($dataObject, PyStringNode $string)
{
if (!is_array($this->fixtures)) {
$this->fixtures = array();
}
if (!is_array($this->fixturesLazy)) {
$this->fixturesLazy = array();
}
if (!isset($this->filesPath)) {
$this->filesPath = realpath($this->getMinkParameter('files_path'));
}
if (!is_array($this->createdFilesPaths)) {
$this->createdFilesPaths = array();
}
if (array_key_exists($dataObject, $this->fixtures)) {
throw new \InvalidArgumentException(sprintf('Data object `%s` already exists!', $dataObject));
}
$fixture = array_merge(array($dataObject . ':'), $string->getLines());
$fixture = implode("\n ", $fixture);
if ('Folder' === $dataObject) {
$this->prepareTestAssetsDirectories($fixture);
}
if ('File' === $dataObject) {
$this->prepareTestAssetsFiles($fixture);
}
$fixturesLazy = array($dataObject => array());
if (preg_match('/=>(\w+)/', $fixture)) {
$fixture_content = Yaml::parse($fixture);
foreach ($fixture_content[$dataObject] as $identifier => &$fields) {
foreach ($fields as $field_val) {
if (substr($field_val, 0, 2) == '=>') {
$fixturesLazy[$dataObject][$identifier] = $fixture_content[$dataObject][$identifier];
unset($fixture_content[$dataObject][$identifier]);
}
}
}
$fixture = Yaml::dump($fixture_content);
}
// As we're dealing with split fixtures and can't join them, replace references by hand
// if (preg_match('/=>(\w+)\.([\w.]+)/', $fixture, $matches)) {
// if ($matches[1] !== $dataObject) {
// $fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
// }
// }
$fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
// Save fixtures into database
$this->fixtures[$dataObject] = new \YamlFixture($fixture);
$model = \DataModel::inst();
$this->fixtures[$dataObject]->writeInto($this->getFixtureFactory());
// Lazy load fixtures into database
// Loop is required for nested lazy fixtures
foreach ($fixturesLazy[$dataObject] as $identifier => $fields) {
$fixture = array(
$dataObject => array(
$identifier => $fields,
),
);
$fixture = Yaml::dump($fixture);
$fixture = preg_replace_callback('/=>(\w+)\.([\w.]+)/', array($this, 'replaceFixtureReferences'), $fixture);
$this->fixturesLazy[$dataObject][$identifier] = new \YamlFixture($fixture);
$this->fixturesLazy[$dataObject][$identifier]->writeInto($this->getFixtureFactory());
}
}
protected function prepareTestAssetsDirectories($fixture)
{
$folders = Yaml::parse($fixture);
foreach ($folders['Folder'] as $fields) {
foreach ($fields as $field => $value) {
if ('Filename' === $field) {
if (0 === strpos($value, 'assets/')) {
$value = substr($value, strlen('assets/'));
}
$folder_path = ASSETS_PATH . DIRECTORY_SEPARATOR . $value;
if (file_exists($folder_path) && !is_dir($folder_path)) {
throw new \Exception(sprintf('`%s` already exists and is not a directory', $this->filesPath));
}
\Filesystem::makeFolder($folder_path);
$this->createdFilesPaths[] = $folder_path;
}
}
}
}
protected function prepareTestAssetsFiles($fixture)
{
$files = Yaml::parse($fixture);
foreach ($files['File'] as $fields) {
foreach ($fields as $field => $value) {
if ('Filename' === $field) {
if (0 === strpos($value, 'assets/')) {
$value = substr($value, strlen('assets/'));
}
$filePath = $this->filesPath . DIRECTORY_SEPARATOR . basename($value);
if (!file_exists($filePath) || !is_file($filePath)) {
throw new \Exception(sprintf('`%s` does not exist or is not a file', $this->filesPath));
}
$asset_path = ASSETS_PATH . DIRECTORY_SEPARATOR . $value;
if (file_exists($asset_path) && !is_file($asset_path)) {
throw new \Exception(sprintf('`%s` already exists and is not a file', $this->filesPath));
}
if (!file_exists($asset_path)) {
if (@copy($filePath, $asset_path)) {
$this->createdFilesPaths[] = $asset_path;
}
}
}
}
}
}
protected function replaceFixtureReferences($references)
{
if (!array_key_exists($references[1], $this->fixtures)) {
throw new \OutOfBoundsException(sprintf('Data object `%s` does not exist!', $references[1]));
}
return $this->idFromFixture($references[1], $references[2]);
}
protected function idFromFixture($className, $identifier)
{
if (false !== ($id = $this->getFixtureFactory()->getId($className, $identifier))) {
return $id;
}
if (isset($this->fixturesLazy[$className], $this->fixturesLazy[$className][$identifier]) &&
false !== ($id = $this->fixturesLazy[$className][$identifier]->idFromFixture($className, $identifier))) {
return $id;
}
throw new \OutOfBoundsException(sprintf('`%s` identifier in Data object `%s` does not exist!', $identifier, $className));
}
/**
* Parses given URL and returns its components
*
@ -419,15 +212,6 @@ class SilverStripeContext extends MinkContext implements SilverStripeAwareContex
return $this->getMinkParameter('base_url') ?: '';
}
/**
* @return FixtureFactory
*/
public function getFixtureFactory()
{
if(!$this->fixtureFactory) $this->fixtureFactory = \Injector::inst()->create('FixtureFactory');
return $this->fixtureFactory;
}
/**
* Joins URL parts into an URL using forward slash.
* Forward slash usages are normalised to one between parts.