Issues (3627)

bundles/CoreBundle/Test/MauticMysqlTestCase.php (4 issues)

1
<?php
2
3
namespace Mautic\CoreBundle\Test;
4
5
use Exception;
6
use LogicException;
7
use Mautic\InstallBundle\InstallFixtures\ORM\LeadFieldData;
8
use Mautic\InstallBundle\InstallFixtures\ORM\RoleData;
9
use Mautic\UserBundle\DataFixtures\ORM\LoadRoleData;
10
use Mautic\UserBundle\DataFixtures\ORM\LoadUserData;
11
12
abstract class MauticMysqlTestCase extends AbstractMauticTestCase
13
{
14
    /**
15
     * @var bool
16
     */
17
    private static $databasePrepared = false;
18
19
    /**
20
     * Use transaction rollback for cleanup. Sometimes it is not possible to use it because of the following:
21
     *     1. A query that alters a DB schema causes an open transaction being committed immediately.
22
     *     2. Full-text search does not see uncommitted changes.
23
     *
24
     * @var bool
25
     */
26
    protected $useCleanupRollback = true;
27
28
    /**
29
     * @throws Exception
30
     */
31
    protected function setUp(): void
32
    {
33
        parent::setUp();
34
35
        if (!self::$databasePrepared) {
36
            $this->prepareDatabase();
37
            self::$databasePrepared = true;
38
        }
39
40
        if ($this->useCleanupRollback) {
41
            $this->beforeBeginTransaction();
42
            $this->connection->beginTransaction();
43
        }
44
    }
45
46
    protected function tearDown(): void
47
    {
48
        if ($this->useCleanupRollback) {
49
            if ($this->connection->isTransactionActive()) {
50
                $this->connection->rollback();
51
            }
52
        } else {
53
            $this->prepareDatabase();
54
        }
55
56
        parent::tearDown();
57
    }
58
59
    /**
60
     * Override this method to execute some logic right before the transaction begins.
61
     */
62
    protected function beforeBeginTransaction(): void
63
    {
64
    }
65
66
    protected function setUpSymfony(array $defaultConfigOptions = []): void
67
    {
68
        if ($this->useCleanupRollback && $this->client) {
69
            throw new LogicException('You cannot re-create the client when a transaction rollback for cleanup is enabled. Turn it off using $useCleanupRollback property or avoid re-creating a client.');
70
        }
71
72
        parent::setUpSymfony($defaultConfigOptions);
73
    }
74
75
    /**
76
     * Helper method that eases resetting auto increment values for passed $tables.
77
     * You should avoid using this method as relying on fixed auto-increment values makes tests more fragile.
78
     * For example, you should never assume that IDs of first three records are always 1, 2 and 3.
79
     *
80
     * @throws \Doctrine\DBAL\DBALException
81
     */
82
    protected function resetAutoincrement(array $tables): void
83
    {
84
        $prefix     = $this->container->getParameter('mautic.db_table_prefix');
85
        $connection = $this->connection;
86
87
        foreach ($tables as $table) {
88
            $connection->query(sprintf('ALTER TABLE `%s%s` AUTO_INCREMENT=1', $prefix, $table));
89
        }
90
    }
91
92
    /**
93
     * @param $file
94
     *
95
     * @throws Exception
96
     */
97
    private function applySqlFromFile($file)
98
    {
99
        $connection = $this->connection;
100
        $password   = ($connection->getPassword()) ? " -p{$connection->getPassword()}" : '';
101
        $command    = "mysql -h{$connection->getHost()} -P{$connection->getPort()} -u{$connection->getUsername()}$password {$connection->getDatabase()} < {$file} 2>&1 | grep -v \"Using a password\" || true";
102
103
        $lastLine = system($command, $status);
104
105
        if (0 !== $status) {
106
            throw new Exception($command.' failed with status code '.$status.' and last line of "'.$lastLine.'"');
107
        }
108
    }
109
110
    /**
111
     * Reset each test using a SQL file if possible to prevent from having to run the fixtures over and over.
112
     *
113
     * @throws Exception
114
     */
115
    private function prepareDatabase()
116
    {
117
        if (!function_exists('system')) {
118
            $this->installDatabase();
119
120
            return;
121
        }
122
123
        $sqlDumpFile = $this->container->getParameter('kernel.cache_dir').'/fresh_db.sql';
124
125
        if (!file_exists($sqlDumpFile)) {
126
            $this->installDatabase();
127
            $this->dumpToFile($sqlDumpFile);
128
129
            return;
130
        }
131
132
        $this->applySqlFromFile($sqlDumpFile);
133
    }
134
135
    /**
136
     * @throws Exception
137
     */
138
    private function installDatabase()
139
    {
140
        $this->createDatabase();
141
        $this->applyMigrations();
142
        $this->installDatabaseFixtures([LeadFieldData::class, RoleData::class, LoadRoleData::class, LoadUserData::class]);
143
    }
144
145
    /**
146
     * @throws Exception
147
     */
148
    private function createDatabase()
149
    {
150
        $this->runCommand(
151
            'doctrine:database:drop',
152
            [
153
                '--env'   => 'test',
154
                '--force' => true,
155
            ]
156
        );
157
158
        $this->runCommand(
159
            'doctrine:database:create',
160
            [
161
                '--env' => 'test',
162
            ]
163
        );
164
165
        $this->runCommand(
166
            'doctrine:schema:create',
167
            [
168
                '--env' => 'test',
169
            ]
170
        );
171
    }
172
173
    /**
174
     * @throws Exception
175
     */
176
    private function dumpToFile(string $sqlDumpFile): void
177
    {
178
        $password   = ($this->connection->getPassword()) ? " -p{$this->connection->getPassword()}" : '';
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getPassword() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

178
        $password   = ($this->connection->getPassword()) ? " -p{/** @scrutinizer ignore-deprecated */ $this->connection->getPassword()}" : '';
Loading history...
179
        $command    = "mysqldump --add-drop-table --opt -h{$this->connection->getHost()} -P{$this->connection->getPort()} -u{$this->connection->getUsername()}$password {$this->connection->getDatabase()} > {$sqlDumpFile} 2>&1 | grep -v \"Using a password\" || true";
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getHost() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

179
        $command    = "mysqldump --add-drop-table --opt -h{/** @scrutinizer ignore-deprecated */ $this->connection->getHost()} -P{$this->connection->getPort()} -u{$this->connection->getUsername()}$password {$this->connection->getDatabase()} > {$sqlDumpFile} 2>&1 | grep -v \"Using a password\" || true";
Loading history...
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getPort() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

179
        $command    = "mysqldump --add-drop-table --opt -h{$this->connection->getHost()} -P{/** @scrutinizer ignore-deprecated */ $this->connection->getPort()} -u{$this->connection->getUsername()}$password {$this->connection->getDatabase()} > {$sqlDumpFile} 2>&1 | grep -v \"Using a password\" || true";
Loading history...
Deprecated Code introduced by
The function Doctrine\DBAL\Connection::getUsername() has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

179
        $command    = "mysqldump --add-drop-table --opt -h{$this->connection->getHost()} -P{$this->connection->getPort()} -u{/** @scrutinizer ignore-deprecated */ $this->connection->getUsername()}$password {$this->connection->getDatabase()} > {$sqlDumpFile} 2>&1 | grep -v \"Using a password\" || true";
Loading history...
180
181
        $lastLine = system($command, $status);
182
        if (0 !== $status) {
183
            throw new Exception($command.' failed with status code '.$status.' and last line of "'.$lastLine.'"');
184
        }
185
186
        $f         = fopen($sqlDumpFile, 'r');
187
        $firstLine = fgets($f);
188
        if (false !== strpos($firstLine, 'Using a password')) {
189
            $file = file($sqlDumpFile);
190
            unset($file[0]);
191
            file_put_contents($sqlDumpFile, $file);
192
        }
193
        fclose($f);
194
    }
195
}
196