at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
+338
View File
@@ -0,0 +1,338 @@
<?php
namespace Common\Context;
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\PyStringNode;
use Common\Util\DatabaseHelper;
use Common\Util\Index\InternalSourceConnection;
use Common\Util\Processor\DataProcessor;
use Common\Util\Index\ExternalIndexConnection;
use Common\Util\Index\InternalIndexConnection;
use Common\Util\Matcher\AppMatcher;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\ORM\EntityManagerInterface;
use IndexBundle\Fixture\Executor\Factory\IndexFixtureExecutorFactory;
use IndexBundle\Fixture\Executor\Factory\IndexFixtureExecutorFactoryInterface;
use IndexBundle\Fixture\Loader\IndexFixtureLoader;
use Seld\JsonLint\JsonParser;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Class AbstractContext
* Base class for all application test context's.
*
* @package Common\Context
*/
class AbstractContext extends \PHPUnit_Framework_Assert implements Context
{
use DatabaseContextTrait,
IndexContextTrait;
/**
* True if test running with debug.
*
* @var boolean
*/
protected $debug;
/**
* @var ContainerInterface
*/
protected $container;
/**
* Path to data fixtures directory.
*
* @var string
*/
protected $fixturesDir;
/**
* @var DataProcessor
*/
protected $processor;
/**
* @var IndexFixtureExecutorFactoryInterface
*/
protected $indexExecutorFactory;
/**
* True if all indices initialized.
*
* @var boolean
*/
private static $indexInitialized = false;
/**
* Setup database schemas.
*
* @BeforeSuite
*
* @return void
*/
public static function setup()
{
if (strtolower(trim(getenv('WITHOUT_CLEAR'))) !== 'true') {
system('./bin/console --env=test cache:clear');
system('./bin/console --env=test doctrine:database:create --if-not-exists -n');
system('./bin/console --env=test doctrine:schema:update --force -n');
}
}
/**
* Clear external index and load fixtures before scenario.
*
* @BeforeScenario @external-index-fixtures
*
* @return void
*/
public function setupExternalIndexFixtures()
{
$this->createIndexes();
echo 'Purge external index ... ';
$this->externalIndex->purge();
echo 'done'. PHP_EOL;
echo 'Load indices fixtures: '. PHP_EOL;
$loader = new IndexFixtureLoader($this->container);
$loader->loadFromDirectory($this->fixturesDir);
$this->indexExecutorFactory->external($this->externalIndex->getIndex())
->setLogger(function ($message) {
echo " > {$message}". PHP_EOL;
})
->execute($loader->getFixtures());
// Wait to insure that all fixtures was indexed.
sleep(2);
}
/**
* Clear external index and load fixtures before scenario.
*
* @BeforeScenario @internal-index-fixtures
*
* @return void
*/
public function setupInternalIndexFixtures()
{
$this->createIndexes();
echo 'Purge internal index ... ';
$this->internalIndex->purge();
echo 'done'. PHP_EOL;
echo 'Load indices fixtures: '. PHP_EOL;
$loader = new IndexFixtureLoader($this->container);
$loader->loadFromDirectory($this->fixturesDir);
$this->indexExecutorFactory->internal($this->internalIndex->getIndex())
->setLogger(function ($message) {
echo " > {$message}". PHP_EOL;
})
->execute($loader->getFixtures());
// Wait to insure that all fixtures was indexed.
sleep(2);
}
/**
* Clear external index and load fixtures before scenario.
*
* @BeforeScenario @source-index-fixtures
*
* @return void
*/
public function setupSourceIndexFixtures()
{
//
// Remove source_update.date
//
// NOTICE: Insure that you don't run test in production :)
//
unlink(realpath(__DIR__ . '/../../../var/source_update.date'));
$this->createIndexes();
$this->setupExternalIndexFixtures();
echo 'Purge source index ... ';
$this->sourceIndex->purge();
echo 'done'. PHP_EOL;
echo 'Load indices fixtures: '. PHP_EOL;
$loader = new IndexFixtureLoader($this->container);
$loader->loadFromDirectory($this->fixturesDir);
$this->indexExecutorFactory->source($this->sourceIndex->getIndex())
->setLogger(function ($message) {
echo " > {$message}". PHP_EOL;
})
->execute($loader->getFixtures());
// Wait to insure that all fixtures was indexed.
sleep(2);
}
/**
* Create indexes if we want to upload index fixtures.
*
* @return void
*/
public function createIndexes()
{
if (! self::$indexInitialized
&& (strtolower(trim(getenv('WITHOUT_CLEAR'))) !== 'true')) {
$this->externalIndex->setup();
$this->internalIndex->setup();
$this->sourceIndex->setup();
self::$indexInitialized = true;
}
}
/**
* @param ContainerInterface $container A ContainerInterface instance.
* @param string $fixturesDir Path to fixtures directory.
*/
public function __construct(ContainerInterface $container, $fixturesDir)
{
$this->debug = getenv('DEBUG') !== false;
$this->container = $container;
$this->fixturesDir = realpath($fixturesDir);
if ($container->getParameter('kernel.environment') !== 'test') {
$message = 'You should run test in test environment /:|';
throw new \InvalidArgumentException($message);
}
// Create database helper.
// See Common\Context\DatabaseContextTrait
$this->dataBaseHelper = new DatabaseHelper($container->get('doctrine'));
// Get serializer metadata for all entities.
// We make it for simplification testing process. With this information
// we can make more powerful expanders and matchers which help as to
// write less but make more.
/** @var EntityManagerInterface $em */
$em = $this->container->get('doctrine.orm.default_entity_manager');
// Get all available entity fqcn's.
$fqcnList = array_map(function (ClassMetadata $metadata) {
return $metadata->getName();
}, $em->getMetadataFactory()->getAllMetadata());
$entities = $this->processEntityMetadata($em, $fqcnList);
// Register all entities.
AppMatcher::registerEntities($entities);
$this->processor = new DataProcessor($container);
// Decorate indices connections.
$this->externalIndex = new ExternalIndexConnection(
$this->get('index.external')
);
$this->internalIndex = new InternalIndexConnection(
$this->get('index.articles')
);
$this->sourceIndex = new InternalSourceConnection(
$this->get('index.sources')
);
$this->indexExecutorFactory = new IndexFixtureExecutorFactory();
}
/**
* @When /^(?:|I )[Ww]ait (?P<milliseconds>\d+) millisecond(?: until| for)?[\w\s]*$/
*
* @param integer $milliseconds Seconds count.
*
* @return void
*/
public function wait($milliseconds)
{
usleep($milliseconds * 1000);
}
/**
* Gets a service.
*
* @param string $id The service identifier.
*
* @return object The associated service.
*/
protected function get($id)
{
return $this->container->get($id);
}
/**
* Gets a parameter.
*
* @param string $name The parameter name.
*
* @return mixed The parameter value.
*/
protected function getParameter($name)
{
return $this->container->getParameter($name);
}
/**
* Match value against specified pattern.
*
* @param mixed $value Matched value.
* @param mixed $pattern Pattern.
* @param string $error Occurred error.
*
* @return boolean
*/
protected function match($value, $pattern, &$error)
{
$value = $this->processor->process($value);
$pattern = preg_replace('/\\s{2,}/', '', $pattern);
// Lint pattern only if it contains json.
if (($pattern[0] === '{') || ($pattern[0] === '[')) {
// Lint pattern.
$lint = new JsonParser();
$exception = $lint->lint($pattern);
if ($exception !== null) {
throw new \RuntimeException('Pattern lint: ' . $exception->getMessage());
}
}
// Process expressions between ##.
$pattern = $this->processor->process($pattern);
return AppMatcher::match($value, $pattern, $error);
}
/**
* Lint json.
*
* @param PyStringNode|string $json Json to lint.
*
* @return void
*/
protected function lintJson($json)
{
if ($json instanceof PyStringNode) {
$json = $json->getRaw();
}
$linter = new JsonParser();
$exception = $linter->lint($json);
if ($exception !== null) {
throw $exception;
}
}
}
@@ -0,0 +1,231 @@
<?php
namespace Common\Context;
use ApiBundle\Entity\NormalizableEntityInterface;
use ApiBundle\Serializer\Metadata\Metadata;
use AppBundle\Utils\Purger\TruncateORMPurger;
use Behat\Gherkin\Node\TableNode;
use Common\Util\DatabaseHelper;
use Common\Util\Metadata\EntityMetadata;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
/**
* Class DatabaseContextTrait
* Contains steps definitions for working with database.
*
* @package Common\Context
*/
trait DatabaseContextTrait
{
/**
* @var DatabaseHelper
*/
private $dataBaseHelper;
/**
* Clear database and load fixtures before scenario.
*
* @BeforeScenario @db-fixtures
*
* @return void
*/
public function setupDbFixtures()
{
/** @var EntityManagerInterface $em */
$em = $this->get('doctrine.orm.entity_manager');
echo 'Purge database ... ';
$purger = new TruncateORMPurger(new ORMPurger($em));
$purger->purge();
echo 'done'. PHP_EOL;
$loader = new ContainerAwareLoader($this->container);
$loader->loadFromDirectory($this->fixturesDir);
echo 'Load database fixtures:'. PHP_EOL;
$executor = new ORMExecutor($em);
$executor
->setLogger(function ($message) {
echo " > {$message}". PHP_EOL;
});
$executor->execute($loader->getFixtures(), true);
}
/**
* @Then /^(?:|[Dd]atabase )[Hh]as entity (?P<name>.+)$/
*
* @param string $name Entity short name like AppBundle:Entity or FQCN.
* @param TableNode $table Search parameters in table format.
*
* @return void
*/
public function hasEntity($name, TableNode $table)
{
$params = [];
$tableData = $table->getTable();
foreach ($tableData as $row) {
$params[current($row)] = next($row);
}
$entity = $this->dataBaseHelper->getEntity($name, $params);
self::assertNotNull(
$entity,
"Can't find entity {$name} with parameters " . PHP_EOL
. json_encode($params, JSON_PRETTY_PRINT)
);
}
/**
* @Then /^(?:|[Dd]atabase )[Hh]as (?P<count>\d+) entity (?P<name>.+)$/
* @Then /?[Dd]on't has entity (?P<name>.+)$?
*
* @param string $name Entity short name like AppBundle:Entity or FQCN.
* @param integer $count Expected entities count.
* @param TableNode $table Search parameters in table format.
*
* @return void
*/
public function hasEntities($name, $count, TableNode $table)
{
$params = [];
$tableData = $table->getTable();
foreach ($tableData as $row) {
$params[current($row)] = next($row);
}
$entities = $this->dataBaseHelper->getEntities($name, $params);
self::assertCount(
(int) $count,
$entities,
"Can't find {$count} entity {$name} with parameters " . PHP_EOL
. json_encode($params, JSON_PRETTY_PRINT) .PHP_EOL .
'Actually found: '. count($entities)
);
}
/**
* @Then /^[Ii] want to delete entity (?P<name>.+)$/
*
* @param string $name Entity short name like AppBundle:Entity or FQCN.
* @param TableNode $table Search parameters in table format.
*
* @return void
*/
public function deleteEntity($name, TableNode $table)
{
$params = [];
$tableData = $table->getTable();
foreach ($tableData as $row) {
$params[current($row)] = next($row);
}
$this->dataBaseHelper->deleteEntity($name, $params);
}
/**
* @Then /^(?:|[Dd]atabase )[Dd]on't has entity (?P<name>.+)$/
* @Then /?[Hh]as entity (?P<name>.+)$?
*
* @param string $name Entity short name like AppBundle:Entity or FQCN.
* @param TableNode $table Search parameters ikn table format.
*
* @return void
*/
public function entityNotExists($name, TableNode $table)
{
$params = [];
$tableData = $table->getTable();
foreach ($tableData as $row) {
$params[current($row)] = next($row);
}
$entities = $this->dataBaseHelper->getEntities($name, $params);
self::assertCount(
0,
$entities,
"Entity {$name} with parameters " . PHP_EOL
. json_encode($params, JSON_PRETTY_PRINT) . PHP_EOL .'exists!'
);
}
/**
* @param EntityManagerInterface $em A EntityManagerInterface instance.
* @param array $fqcnList List of available fqcn's.
*
* @return array|mixed
*/
protected function processEntityMetadata(EntityManagerInterface $em, array $fqcnList)
{
$entities = [];
foreach ($fqcnList as $fqcn) {
$reflection = new \ReflectionClass($fqcn);
if ($reflection->implementsInterface(NormalizableEntityInterface::class)) {
$name = \app\c\entityFqcnToShort($fqcn);
if ($reflection->isAbstract()) {
$entities[$name] = new EntityMetadata($this->processAbstractMetadata($em, $reflection));
} else {
/** @var NormalizableEntityInterface $entity */
$entity = $reflection->newInstanceWithoutConstructor();
$entities[$name] = new EntityMetadata($entity->getMetadata());
}
}
}
return $entities;
}
/**
* @param EntityManagerInterface $em A EntityManagerInterface
* instance.
* @param \ReflectionClass $reflection A ReflectionClass instance.
*
* @return Metadata
*/
protected function processAbstractMetadata(
EntityManagerInterface $em,
\ReflectionClass $reflection
) {
/** @var ClassMetadataInfo $doctrineMetadata */
$doctrineMetadata = $em->getClassMetadata($reflection->getName());
$map = $doctrineMetadata->discriminatorMap;
if (! is_array($map) || (count($map) === 0)) {
// Parsed abstract class don't has discriminator column.
$message = 'Abstract class without discriminator column not allowed';
throw new \InvalidArgumentException($message);
}
$metadata = new Metadata($reflection->getName());
$metadataList = array_map(function ($fqcn) use ($em) {
$reflection = new \ReflectionClass($fqcn);
if ($reflection->isAbstract()) {
/** @var ClassMetadataInfo $doctrineMetadata */
$doctrineMetadata = $em->getClassMetadata($fqcn);
$map = $doctrineMetadata->discriminatorMap;
return $this->processAbstractMetadata($em, $map);
}
/** @var NormalizableEntityInterface $entity */
$entity = $reflection->newInstanceWithoutConstructor();
return $entity->getMetadata();
}, $map);
return $metadata->admixList($metadataList);
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
namespace Common\Context;
use Behat\Gherkin\Node\TableNode;
use Common\Util\Index\ExternalIndexConnection;
use Common\Util\Index\InternalIndexConnection;
use Common\Util\Index\InternalSourceConnection;
use Common\Util\Index\TestIndexConnectionInterface;
/**
* Class IndexContextTrait
* Contains steps definitions for working with indices.
*
* @package Common\Context
*/
trait IndexContextTrait
{
/**
* @var ExternalIndexConnection
*/
protected $externalIndex;
/**
* @var InternalIndexConnection
*/
protected $internalIndex;
/**
* @var InternalSourceConnection
*/
protected $sourceIndex;
/**
* @Transform /^([Ee]xternal|[Ii]nternal|[Ss]ource)$/
*
* @param string $index Index name.
*
* @return TestIndexConnectionInterface
*/
public function getConnectionByName($index)
{
$index = strtolower($index);
switch ($index) {
case 'external':
return $this->externalIndex;
case 'internal':
return $this->internalIndex;
case 'source':
return $this->sourceIndex;
}
throw new \InvalidArgumentException("Unknown index '{$index}'");
}
/**
* Json parameters should have only necessary fields. Over will be auto
* generated.
*
* @Given /^(?:I add|[Hh]as) new document (?:in|to) (?P<connection>(external|internal|source)) index$/
*
* @param TestIndexConnectionInterface $index A TestIndexConnectionInterface
* instance.
* @param TableNode $table A TableNode instance.
*
* @return void
*/
public function indexDocument(
TestIndexConnectionInterface $index,
TableNode $table
) {
/** @var AbstractContext $this */
$document = $index->createDocument();
$params = [];
$tableData = $table->getTable();
foreach ($tableData as $row) {
$params[current($row)] = $this->processor->process(next($row));
}
foreach ($params as $name => $value) {
$document[$name] = $value;
}
$index->index($document);
// Wait to insure that all fixtures was indexed.
usleep(100000);
}
/**
* @Then /^(?P<connection>([Ee]xternal|[Ii]nternal|[Ss]ource)) index has (?P<count>\d+) document[s]?$/
*
* @param TestIndexConnectionInterface $connection A
* TestIndexConnectionInterface
* instance.
* @param integer $count A expected documents
* count.
* @param TableNode $table A TableNode instance.
*
* @return void
*/
public function hasDocuments(
TestIndexConnectionInterface $connection,
$count,
TableNode $table
) {
/** @var AbstractContext $this */
$tableData = $table->getTable();
$factory = $connection->getFilterFactory();
$filters = [];
foreach ($tableData as $row) {
$field = current($row);
$type = next($row);
$value = next($row);
if ($type === 'in') {
$value = array_filter(array_map('trim', explode(',', $value)));
}
$filters[] =
$factory->{$type}($field, $this->processor->process($value));
}
$results = $connection->createRequestBuilder()
->setFilters($filters)
->build()
->execute();
self::assertCount((int) $count, $results);
}
}