Skip to content

Commit

Permalink
Load connection fixtures command (#7)
Browse files Browse the repository at this point in the history
* Introduce Elasticsearch Fixtures

* Update tests and readme

* Add final keyword

* Update composer

* Provide  to ElasticSearchExecutor & update tests

* Update readme

* Define data-fixtures package dependency version

* Address code review

* Apply Code Standards

* Format config

* Update parameters.yml

* Added LoadDatabaseFixturesCommand

* Added append option and confirm to LoadDatabaseFixturesCommand

* Refactor in DoctrineCompilerPass; added tests for LoadDatabaseFixturesCommand; added LoadDatabaseFixturesCommand to readme; refactor in LoadDatabaseFixturesCommand

* Improvements in README.md

* Code Review addressed
  • Loading branch information
kununu-drocha authored Nov 7, 2019
1 parent 12f9ac6 commit 598d114
Show file tree
Hide file tree
Showing 14 changed files with 627 additions and 91 deletions.
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# kununu testing-bundle

At kununu we do functional and integration tests. [LiipFunctionalTestBundle](https://github.com/liip/LiipFunctionalTestBundle) and [DoctrineTestBundle](https://github.com/dmaicher/doctrine-test-bundle) are great options however they do not match our requirements and heavily depend on [Doctrine ORM](https://github.com/doctrine/orm).

Also we have the necessity to load database fixtures for DEV/TEST/E2E environments.

The main requirements that we had to solve that this bundle addresses are:
- **Database schema is not touched when loading fixtures**. This requirement excludes LiipFunctionalTestBundle because it drops and creates the schema when loading fixtures. Another drawback of LiipFunctionalTestBundle is that it relies on Doctrine Mapping Metadata to recreate the schema which for us is a limitation since we do not always map everything but instead use Migrations.
- **We really want to hit the database**. This requirement excludes DoctrineTestBundle because it wraps your fixtures in a transaction.
Expand Down Expand Up @@ -71,7 +74,7 @@ final class IntegrationTest extends FixturesAwareTestCase

### Connection Fixtures

All Doctrine Connections are elegible to be used to load fixtures.
All Doctrine Connections are eligible to be used to load fixtures.
For example, assuming you are using the [Doctrine Bundle](https://github.com/doctrine/DoctrineBundle).

```
Expand Down Expand Up @@ -126,6 +129,34 @@ final class IntegrationTest extends FixturesAwareTestCase
}
```

#### Command to load database fixtures

This bundles can automatically create a command to load database fixtures.

```
php bin/console kununu_testing:load_fixtures:connections:CONNECTION_NAME [--append]
```

There is the need to define the files with the fixtures in the configuration of the bundle

```
# kununu_testing.yaml
kununu_testing:
connections:
default:
load_command_fixtures_classes_namespace:
- 'Kununu\TestingBundle\Tests\App\Fixtures\Connection\ConnectionFixture3' # FQDN for a fixtures class
```

Then the fixtures can be loaded running:

```
php bin/console kununu_testing:load_fixtures:connections:default --append
```

If `--append` option is not used, then the database will be truncated. A prompt appears to confirm database truncation.

### Elasticsearch Fixtures

If you want to load Elasticsearch fixtures in your tests first you will need to configure the bundle:
Expand Down Expand Up @@ -219,6 +250,8 @@ final class WebTestCaseTest extends WebTestCase
kununu_testing:
connections:
connection_name:
load_command_fixtures_classes_namespace:
- 'Kununu\TestingBundle\Tests\App\Fixtures\Connection\ConnectionFixture3' # FQDN for a fixtures class
excluded_tables:
- table_to_exclude_from_purge
Expand All @@ -240,5 +273,5 @@ kununu test lib testing-bundle [--exclude-group integration]
vendor/phpunit/phpunit/phpunit tests [--exclude-group integration]
```

**If you want to run the integration tests you will need the extension `ext-pdo_sqlite`.**
**If you want to run the integration tests you will need to have an Elasticsearch cluster running. To change the hosts of the cluster please go to `tests/App/config/packages/parameters.yml`.**
**If you want to run the integration tests you will need the extension `ext-pdo_sqlite` (For installing int ubuntu run `apt update && apt install php-sqlite3`).**
**If you want to run the integration tests you will need to have an Elasticsearch cluster running. To change the hosts of the cluster please go to `tests/App/config/packages/parameters.yml`.**
55 changes: 55 additions & 0 deletions src/Command/LoadDatabaseFixturesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types=1);

namespace Kununu\TestingBundle\Command;

use Kununu\TestingBundle\Service\Orchestrator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class LoadDatabaseFixturesCommand extends Command
{
private $connectionName;
private $orchestrator;
private $fixturesClassNames;

public function __construct(string $connectionName, Orchestrator $orchestrator, array $fixturesClassNames)
{
parent::__construct(sprintf('kununu_testing:load_fixtures:connections:%s', $connectionName));

$this->connectionName = $connectionName;
$this->orchestrator = $orchestrator;
$this->fixturesClassNames = $fixturesClassNames;
}

protected function configure(): void
{
parent::configure();

$this
->setDescription('Load Database Fixtures')
->addOption('append', null, InputOption::VALUE_NONE, 'Append the data fixtures instead of deleting all data from the database first.');
}

protected function execute(InputInterface $input, OutputInterface $output): void
{
$appendOption = $input->getOption('append');

$ui = new SymfonyStyle($input, $output);

if (!$appendOption &&
!$ui->confirm(
sprintf('Careful, database "%s" will be purged. Do you want to continue?', $this->connectionName),
!$input->isInteractive()
)
) {
return;
}

$this->orchestrator->execute($this->fixturesClassNames, $appendOption);

$output->writeln(sprintf('Fixtures loaded with success for connection "%s"', $this->connectionName));
}
}
82 changes: 67 additions & 15 deletions src/DependencyInjection/Compiler/DoctrineCompilerPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Kununu\DataFixtures\Executor\ConnectionExecutor;
use Kununu\DataFixtures\Loader\ConnectionFixturesLoader;
use Kununu\DataFixtures\Purger\ConnectionPurger;
use Kununu\TestingBundle\Command\LoadDatabaseFixturesCommand;
use Kununu\TestingBundle\Service\Orchestrator;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
Expand All @@ -14,7 +15,13 @@

final class DoctrineCompilerPass implements CompilerPassInterface
{
private const SERVICE_PREFIX = 'kununu_testing.orchestrator.connections';
private const EXCLUDED_TABLES_CONFIG = 'excluded_tables';
private const LOAD_COMMAND_FIXTURES_CLASSES_NAMESPACE_CONFIG = 'load_command_fixtures_classes_namespace';

private const ORCHESTRATOR_SERVICE_PREFIX = 'kununu_testing.orchestrator.connections';

private const LOAD_FIXTURES_COMMAND_SERVICE_PREFIX = 'kununu_testing.command.load_fixtures.connections';
private const LOAD_FIXTURES_COMMAND_PREFIX = 'kununu_testing:load_fixtures:connections';

public function process(ContainerBuilder $container): void
{
Expand All @@ -25,36 +32,43 @@ public function process(ContainerBuilder $container): void
$connections = $container->getParameter('doctrine.connections');

foreach ($connections as $connName => $connId) {
$this->buildConnectionOrchestrator($container, $connName, $connId);
}
}

private function buildConnectionOrchestrator(ContainerBuilder $container, string $connName, string $id): void
{
$excludedTables = [];
$connConfigsParameterName = sprintf('kununu_testing.connections.%s', $connName);

$connConfigsParameterName = sprintf('kununu_testing.connections.%s', $connName);
// Connection is not configured for kununu\testing-bundle
if (! $container->hasParameter($connConfigsParameterName)) {
continue;
}

if ($container->hasParameter($connConfigsParameterName)) {
$connConfigs = $container->getParameter($connConfigsParameterName);
$excludedTables = !empty($connConfigs['excluded_tables']) ? $connConfigs['excluded_tables'] : [];

$orchestratorId = $this->buildConnectionOrchestrator($container, $connName, $connConfigs, $connId);
$this->buildConnectionLoadFixturesCommand($container, $connName, $connConfigs, $orchestratorId);
}
}

private function buildConnectionOrchestrator(
ContainerBuilder $container,
string $connName,
array $connConfigs,
string $id
): string {
$excludedTables = empty($connConfigs[self::EXCLUDED_TABLES_CONFIG])? [] : $connConfigs[self::EXCLUDED_TABLES_CONFIG];

/** @var Connection $connection */
$connection = new Reference($id);

// Purger Definition for the Connection with provided $id
$purgerId = sprintf('%s.%s.purger', self::SERVICE_PREFIX, $connName);
$purgerId = sprintf('%s.%s.purger', self::ORCHESTRATOR_SERVICE_PREFIX, $connName);
$purgerDefinition = new Definition(ConnectionPurger::class, [$connection, $excludedTables]);
$container->setDefinition($purgerId, $purgerDefinition);

// Executor Definition for the Connection with provided $id
$executorId = sprintf('%s.%s.executor', self::SERVICE_PREFIX, $connName);
$executorId = sprintf('%s.%s.executor', self::ORCHESTRATOR_SERVICE_PREFIX, $connName);
$executorDefinition = new Definition(ConnectionExecutor::class, [$connection, new Reference($purgerId)]);
$container->setDefinition($executorId, $executorDefinition);

// Loader Definition for the Connection with provided $id
$loaderId = sprintf('%s.%s.loader', self::SERVICE_PREFIX, $connName);
$loaderId = sprintf('%s.%s.loader', self::ORCHESTRATOR_SERVICE_PREFIX, $connName);
$loaderDefinition = new Definition(ConnectionFixturesLoader::class);
$container->setDefinition($loaderId, $loaderDefinition);

Expand All @@ -68,6 +82,44 @@ private function buildConnectionOrchestrator(ContainerBuilder $container, string
);
$connectionOrchestratorDefinition->setPublic(true);

$container->setDefinition(sprintf('%s.%s', self::SERVICE_PREFIX, $connName), $connectionOrchestratorDefinition);
$orchestratorId = sprintf('%s.%s', self::ORCHESTRATOR_SERVICE_PREFIX, $connName);

$container->setDefinition($orchestratorId, $connectionOrchestratorDefinition);

return $orchestratorId;
}

private function buildConnectionLoadFixturesCommand(
ContainerBuilder $container,
string $connName,
array $connConfigs,
string $orchestratorId
): void {
// Connection does not have fixtures configured for LoadDatabaseFixturesCommand
if (! isset($connConfigs[self::LOAD_COMMAND_FIXTURES_CLASSES_NAMESPACE_CONFIG]) ||
empty($connConfigs[self::LOAD_COMMAND_FIXTURES_CLASSES_NAMESPACE_CONFIG])
) {
return;
}

$connectionLoadFixturesDefinition = new Definition(
LoadDatabaseFixturesCommand::class,
[
$connName,
new Reference($orchestratorId),
$connConfigs[self::LOAD_COMMAND_FIXTURES_CLASSES_NAMESPACE_CONFIG]
]
);
$connectionLoadFixturesDefinition->setPublic(true);
$connectionLoadFixturesDefinition->setTags(
['console.command' => [
['command' => sprintf('%s:%s', self::LOAD_FIXTURES_COMMAND_PREFIX, $connName)]]
]
);

$container->setDefinition(
sprintf('%s.%s', self::LOAD_FIXTURES_COMMAND_SERVICE_PREFIX, $connName),
$connectionLoadFixturesDefinition
);
}
}
4 changes: 4 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public function getConfigTreeBuilder()
->useAttributeAsKey('name')
->arrayPrototype()
->children()
->arrayNode('load_command_fixtures_classes_namespace')
->scalarPrototype()
->end()
->end()
->arrayNode('excluded_tables')
->scalarPrototype()
->end()
Expand Down
15 changes: 15 additions & 0 deletions tests/App/Fixtures/Connection/ConnectionFixture3.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace Kununu\TestingBundle\Tests\App\Fixtures\Connection;

use Doctrine\DBAL\Connection;
use Kununu\DataFixtures\Adapter\ConnectionFixtureInterface;

final class ConnectionFixture3 implements ConnectionFixtureInterface
{
public function load(Connection $connection): void
{
$connection->exec('INSERT INTO `table_1` (`name`, `description`) VALUES (\'name3\', \'description3\');');
$connection->exec('INSERT INTO `table_2` (`name`, `description`) VALUES (\'name3\', \'description3\');');
}
}
15 changes: 15 additions & 0 deletions tests/App/Fixtures/Connection/ConnectionFixture4.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php declare(strict_types=1);

namespace Kununu\TestingBundle\Tests\App\Fixtures\Connection;

use Doctrine\DBAL\Connection;
use Kununu\DataFixtures\Adapter\ConnectionFixtureInterface;

final class ConnectionFixture4 implements ConnectionFixtureInterface
{
public function load(Connection $connection): void
{
$connection->exec('INSERT INTO `table_1` (`name`, `description`) VALUES (\'name4\', \'description4\');');
$connection->exec('INSERT INTO `table_2` (`name`, `description`) VALUES (\'name4\', \'description4\');');
}
}
41 changes: 41 additions & 0 deletions tests/App/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env php
<?php

use Kununu\TestingBundle\Tests\App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Debug\Debug;

set_time_limit(0);

require dirname(__DIR__).'/../../vendor/autoload.php';

if (!class_exists(Application::class)) {
throw new RuntimeException('You need to add "symfony/framework-bundle" as a Composer dependency.');
}

require dirname(__DIR__).'/config/bootstrap.php';

$input = new ArgvInput();

if (null !== $env = $input->getParameterOption(['--env', '-e'], $_ENV['APP_ENV'], true)) {
putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env);
}

if ($input->hasParameterOption('--no-debug', true) ||
$_ENV['APP_ENV'] === 'prod'
) {
putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0');
}

if ($_SERVER['APP_DEBUG']) {
umask(0000);

if (class_exists(Debug::class)) {
Debug::enable();
}
}

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$application = new Application($kernel);
$application->run($input);
2 changes: 1 addition & 1 deletion tests/App/config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Kununu\TestingBundle\KununuTestingBundle::class => ['test' => true],
Kununu\TestingBundle\KununuTestingBundle::class => ['dev' => true, 'test' => true],
Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
];
5 changes: 5 additions & 0 deletions tests/App/config/packages/doctrine.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ doctrine:
path: "%kernel.cache_dir%/default.db"
charset: UTF8

persistence:
driver: pdo_sqlite
path: "%kernel.cache_dir%/persistence.db"
charset: UTF8

monolithic:
driver: pdo_sqlite
path: "%kernel.cache_dir%/monolithic.db"
Expand Down
2 changes: 2 additions & 0 deletions tests/App/config/packages/kununu_testing.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
kununu_testing:
connections:
default:
load_command_fixtures_classes_namespace:
- 'Kununu\TestingBundle\Tests\App\Fixtures\Connection\ConnectionFixture3'
excluded_tables:
- table_to_exclude
monolithic:
Expand Down
Loading

0 comments on commit 598d114

Please sign in to comment.