redmatter/Codeception-MultiDb

Adding support to "dump", "populate" and "cleanup" parameters

henrydekap opened this issue · 3 comments

This is meant for branch 1.0, I didn't have the time to test it in 2.0

Adding the standard Db module functionalities to load a specified dump for each connection, and setup the cleanup and populate config:

example config from codeception.yml:

Codeception\Extension\MultiDb:
    timezone: '+00:00'
    connectors:
        Dev:
            dsn: "mysql:host=127.0.0.1;port=3306;dbname=test_dev"
            user: 'testuser'
            password: 'whatever'
            dump: tests/_data/test_dev.sql
            populate: true
            cleanup: true
        Prod:
            dsn: "mysql:host=127.0.0.1;port=3306;dbname=test_prod"
            user: 'testuser'
            password: 'whatever'
            dump: tests/_data/test_prod.sql
            populate: true
            cleanup: true

What is needed is to add some code to the _initialize() function (taken directly from the Db module), and two protected functions to do the load and cleanup for the specified driver:

https://github.com/redmatter/Codeception-MultiDb/blob/1.0/src/Codeception/Extension/MultiDb.php

<?php
/**
 * @copyright 2009-2014 Red Matter Ltd (UK)
 */

namespace Codeception\Extension;

use Codeception\Exception\TestRuntime as TestRuntimeException;
use Codeception\Extension\MultiDb\Utils\AsIs;
use Codeception\Extension\MultiDb\Utils\CleanupAction;
use Codeception\Lib\Driver\Db as Driver;
use Codeception\Exception\Module as ModuleException;
use Codeception\Exception\ModuleConfig as ModuleConfigException;
use Codeception\Module;
use Codeception\TestCase;
use Codeception\Configuration as Configuration;

/**
 * MultiDb - Module that allows tests to perform setup queries and assertions across multiple databases.
 */
class MultiDb extends Module
{
    const ASIS_PREFIX = '@asis ';

    const CLEANUP_NEVER = 0;
    const CLEANUP_AFTER_TEST = 1;
    const CLEANUP_AFTER_SUITE = 2;

    protected $dbh;

    protected $config = ['connectors' => false, 'timezone' => 'UTC'];

    protected $requiredFields = ['connectors'];

    /** @var  Driver[] */
    protected $drivers = [];

    /** @var  Driver */
    protected $chosenDriver = null;

    protected $chosenConnector;

    /** @var CleanupAction[] */
    protected $test_cleanup_actions = [];
    /** @var CleanupAction[] */
    protected $suite_cleanup_actions = [];

    protected $connectorRequiredFields = ['dsn', 'user', 'password'];

    /** @var string */
    private $timezone;

    /** @var int */
    private $transaction_level = 0;
    /** @var string */
    private $transaction_connector = null;

    // HOOK: used after configuration is loaded
    // @codingStandardsIgnoreLine overridden function from \Codeception\Module
    public function _initialize()
    {
        $configOk = false;

        if (is_array($this->config['connectors'])) {
            foreach ($this->config['connectors'] as $connector => $connectorConfig) {
                if (is_array($connectorConfig)) {
                    $fields = array_keys($connectorConfig);
                    $configOk = (
                        array_intersect($this->connectorRequiredFields, $fields) == $this->connectorRequiredFields
                    );
                    if (!$configOk) {
                        break;
                    }
                }
            }
        }

        if (!$configOk) {
            throw new ModuleConfigException(
                __CLASS__,
                "\nOptions: " . implode(', ', $this->connectorRequiredFields) . " are required\n
                        Please, update the configuration and set all the required fields\n\n"
            );
        }

        $this->timezone = $this->config['timezone'];

        parent::_initialize();

        foreach ($this->config['connectors'] as $connector => $connectorConfig) {
            if ($connectorConfig['populate']) {
                if ($connectorConfig['cleanup']) {
                    $this->cleanup($connector);
                }
                $this->loadDump($connector);
            }
        }
    }

    protected function loadDump($connector)
    {
        $config = $this->config['connectors'][$connector];

        if ($config['dump'] && ($config['cleanup'] or ($config['populate']))) {
            if (!file_exists(Configuration::projectDir() . $config['dump'])) {
                throw new ModuleConfigException(
                  __CLASS__,
                  "\nFile with dump doesn't exist.
                    Please, check path for sql file: " . $config['dump']
                );
            }
            $sql = file_get_contents(Configuration::projectDir() . $config['dump']);
            $sql = preg_replace('%/\*(?!!\d+)(?:(?!\*/).)*\*/%s', "", $sql);
            if (!empty($sql)) {
                $sql = explode("\n", $sql);
            }
        }
        if (!$sql) {
            return;
        }
        try {
            $this->getDriver($connector)->load($sql);
        } catch (\PDOException $e) {
            throw new ModuleException(
              __CLASS__,
              $e->getMessage() . "\nSQL query being executed: " . $sql
            );
        }
    }

    protected function cleanup($connector)
    {
        try {
            $this->getDriver($connector)->cleanup();
        } catch (\Exception $e) {
            throw new ModuleException(__CLASS__, $e->getMessage());
        }
    }
  [...]
}

Very good idea @henrydekap; feel free to make a pull request.

I would like to see this feature completed in the near future. Just swapped out the DB module for this MultiDb and this is the part that is missing.

PR #10 merged and released

If you are using Codeception 2.1, please can you report back how you are getting on with MultiDb.
If you are generally happy with it, I would like to bring that out of RC at some point.

Enjoy!