*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace SilverStripe\BehatExtension\Controllers;
use Behat\Testwork\Cli\Controller;
use Behat\Testwork\Suite\SuiteBootstrapper;
use Behat\Testwork\Suite\SuiteRepository;
use Exception;
use SilverStripe\Core\Manifest\Module;
use SilverStripe\View\ArrayData;
use SilverStripe\View\SSViewer;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\Output;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Yaml\Yaml;
/**
* Initialises module test environment.
*
* Replaces:
* @see \Behat\Testwork\Suite\Cli\InitializationController
*/
class ModuleInitialisationController implements Controller
{
use ModuleCommandTrait;
/**
* @var Container
*/
protected $container;
/**
* @var SuiteRepository
*/
private $repository;
/**
* @var SuiteBootstrapper
*/
private $bootstrapper;
/**
* Initializes controller.
*
* @param ContainerInterface $container
* @param SuiteRepository $repository
* @param SuiteBootstrapper $bootstrapper
*/
public function __construct(
ContainerInterface $container,
SuiteRepository $repository,
SuiteBootstrapper $bootstrapper
) {
$this->container = $container;
$this->repository = $repository;
$this->bootstrapper = $bootstrapper;
}
/**
* {@inheritdoc}
*/
public function configure(Command $command)
{
$command->addOption(
'--init',
null,
InputOption::VALUE_NONE,
'Initialize all registered test suites.'
);
$command->addOption(
'--namespace',
null,
InputOption::VALUE_REQUIRED,
'Set namespace for fixture'
);
}
/**
* {@inheritdoc}
*/
public function execute(InputInterface $input, OutputInterface $output)
{
if (!$input->getOption('init')) {
return null;
}
// If module not specified, bootstrap via legacy behaviour
if (!$input->hasArgument('module')) {
return $this->baseExecute($output);
}
if (!$input->hasOption('namespace')) {
throw new \BadMethodCallException(
"--namespace is required if --init is invoked with a module "
. "This should just be your root Vendor\\Module namespace (e.g. 'SilverStripe\\CMS')"
);
}
// Get module
$moduleName = $input->getArgument('module');
$module = $this->getModule($moduleName);
$namespaceRoot = $input->getOption('namespace');
// Init components
$this->initFeaturesPath($output, $module);
$this->initClassPath($output, $module, $namespaceRoot);
$this->initConfig($output, $module, $namespaceRoot);
return 0;
}
/**
* @param OutputInterface $output
* @return int
*/
protected function baseExecute(OutputInterface $output)
{
$suites = $this->repository->getSuites();
$this->bootstrapper->bootstrapSuites($suites);
$output->write(PHP_EOL);
return 0;
}
protected function initFeaturesPath(OutputInterface $output, Module $module)
{
// Create feature_path
$features = $this->container->getParameter('silverstripe_extension.context.features_path');
$fullPath = $module->getResourcePath($features);
if (is_dir($fullPath)) {
return;
}
mkdir($fullPath, 0777, true);
$output->writeln(
"{$fullPath} - place your *.feature files here"
);
// Create dummy feature
$featureContent = ArrayData::create([])
->renderWith(__DIR__.'/../../templates/SkeletonFeature.ss');
file_put_contents($fullPath.'/placeholder.feature', $featureContent);
}
/**
* Init class_path
*
* @param OutputInterface $output
* @param Module $module
* @param string $namespaceRoot
* @throws Exception
*/
protected function initClassPath(OutputInterface $output, Module $module, $namespaceRoot)
{
$classesPath = $this->container->getParameter('silverstripe_extension.context.class_path');
$dirPath = $module->getResourcePath($classesPath);
if (!is_dir($dirPath)) {
mkdir($dirPath, 0777, true);
}
// Scaffold base context file
$classPath = "{$dirPath}/FeatureContext.php";
if (is_file($classPath)) {
return;
}
// Build class name
$fullNamespace = $this->getFixtureNamespace($namespaceRoot);
$class = $this->getFixtureClass($namespaceRoot);
// Render class
$obj = ArrayData::create([
'Namespace' => $fullNamespace,
'ClassName' => $class,
]);
$classContent = $obj->renderWith(__DIR__.'/../../templates/FeatureContext.ss');
file_put_contents($classPath, $classContent);
// Log
$output->writeln(
"{$classPath} - place your feature related code here"
);
// Add to composer json
$composerFile = $module->getResourcePath('composer.json');
if (!file_exists($composerFile)) {
return;
}
// Add autoload directive to composer
$composerData = json_decode(file_get_contents($composerFile), true);
if (json_last_error()) {
throw new Exception(json_last_error_msg());
}
if (!isset($composerData['autoload'])) {
$composerData['autoload'] = [];
}
if (!isset($composerData['autoload']['psr-4'])) {
$composerData['autoload']['psr-4'] = [];
}
$composerData['autoload']['psr-4']["{$fullNamespace}\\"] = $classesPath;
file_put_contents(
$composerFile,
json_encode($composerData, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
);
$output->writeln(
"{$composerFile} - psr-4 autload for this class added"
);
}
/**
* Get fixture class name
*
* @param string $namespaceRoot
* @return string
*/
protected function getFixtureClass($namespaceRoot)
{
$fullNamespace = $this->getFixtureNamespace($namespaceRoot);
return $fullNamespace . '\FeatureContext';
}
/**
* @param string $namespaceRoot
* @return string
*/
protected function getFixtureNamespace($namespaceRoot)
{
$namespaceSuffix = $this->container->getParameter('silverstripe_extension.context.namespace_suffix');
return trim($namespaceRoot, '/\\') . '\\' . $namespaceSuffix;
}
/**
* Init config file behat.yml
*
* @param OutputInterface $output
* @param Module $module
* @param string $namespaceRoot
*/
protected function initConfig($output, $module, $namespaceRoot)
{
$configPath = $module->getResourcePath('behat.yml');
if (file_exists($configPath)) {
return;
}
$class = $this->getFixtureClass($namespaceRoot);
// load config from yml
$features = $this->container->getParameter('silverstripe_extension.context.features_path');
$data = Yaml::parse(file_get_contents(__DIR__.'/../../templates/config-base.yml'));
$shortname = $module->getShortName();
$data['default']['suites'][$shortname] = [
'paths' => [
"%paths.modules.{$shortname}%/{$features}",
],
'contexts' => [
$class,
\SilverStripe\Framework\Tests\Behaviour\CmsFormsContext::class,
\SilverStripe\Framework\Tests\Behaviour\CmsUiContext::class,
\SilverStripe\BehatExtension\Context\BasicContext::class,
\SilverStripe\BehatExtension\Context\EmailContext::class,
\SilverStripe\BehatExtension\Context\LoginContext::class,
[
\SilverStripe\BehatExtension\Context\FixtureContext::class => [
'%paths.modules.framework%/tests/behat/features/files/'
]
]
]
];
file_put_contents($configPath, Yaml::dump($data, 99999999, 2));
$output->writeln(
"{$configPath} - default behat.yml created"
);
}
}