Completed
Push — master ( 0ec757...511d0a )
by
unknown
89:40 queued 75:27
created

TestCase::insertDatabaseFixture()   B

Complexity

Conditions 7
Paths 14

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
nc 14
nop 1
dl 0
loc 47
rs 8.223
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File contains: eZ\Publish\Core\Persistence\Legacy\Tests\TestCase class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Persistence\Legacy\Tests;
10
11
use Doctrine\Common\EventManager as DoctrineEventManager;
12
use Doctrine\DBAL\Connection;
13
use Doctrine\DBAL\ConnectionException;
14
use Doctrine\DBAL\DBALException;
15
use eZ\Publish\API\Repository\Tests\LegacySchemaImporter;
16
use eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler;
17
use eZ\Publish\Core\Persistence\Database\SelectQuery;
18
use eZ\Publish\Core\Persistence\Legacy\SharedGateway;
19
use eZ\Publish\Core\Persistence\Tests\DatabaseConnectionFactory;
20
use EzSystems\DoctrineSchema\Database\DbPlatform\SqliteDbPlatform;
21
use PHPUnit\Framework\TestCase as BaseTestCase;
22
use InvalidArgumentException;
23
use ReflectionObject;
24
use PDOException;
25
use Exception;
26
use ReflectionProperty;
27
28
/**
29
 * Base test case for database related tests.
30
 */
31
abstract class TestCase extends BaseTestCase
32
{
33
    /**
34
     * DSN used for the DB backend.
35
     *
36
     * @var string
37
     */
38
    protected $dsn;
39
40
    /**
41
     * Name of the DB, extracted from DSN.
42
     *
43
     * @var string
44
     */
45
    protected $db;
46
47
    /**
48
     * Database handler -- to not be constructed twice for one test.
49
     *
50
     * @internal
51
     * @var \eZ\Publish\Core\Persistence\Database\DatabaseHandler
52
     */
53
    protected $handler;
54
55
    /**
56
     * Doctrine Database connection -- to not be constructed twice for one test.
57
     *
58
     * @internal
59
     * @var \Doctrine\DBAL\Connection
60
     */
61
    protected $connection;
62
63
    /** @var \eZ\Publish\Core\Persistence\Legacy\SharedGateway\Gateway */
64
    private $sharedGateway;
65
66
    /**
67
     * Get data source name.
68
     *
69
     * The database connection string is read from an optional environment
70
     * variable "DATABASE" and defaults to an in-memory SQLite database.
71
     *
72
     * @return string
73
     */
74
    protected function getDsn()
75
    {
76
        if (!$this->dsn) {
77
            $this->dsn = getenv('DATABASE');
78
            if (!$this->dsn) {
79
                $this->dsn = 'sqlite://:memory:';
80
            }
81
            $this->db = preg_replace('(^([a-z]+).*)', '\\1', $this->dsn);
82
        }
83
84
        return $this->dsn;
85
    }
86
87
    /**
88
     * Get a eZ Doctrine database connection handler.
89
     *
90
     * Get a ConnectionHandler, which can be used to interact with the configured
91
     * database. The database connection string is read from an optional
92
     * environment variable "DATABASE" and defaults to an in-memory SQLite
93
     * database.
94
     *
95
     * @return \eZ\Publish\Core\Persistence\Doctrine\ConnectionHandler
96
     */
97
    final public function getDatabaseHandler()
98
    {
99
        if (!$this->handler) {
100
            $this->handler = ConnectionHandler::createFromConnection($this->getDatabaseConnection());
101
            $this->db = $this->handler->getName();
102
        }
103
104
        return $this->handler;
105
    }
106
107
    /**
108
     * Get native Doctrine database connection.
109
     *
110
     * @throws \Doctrine\DBAL\DBALException
111
     */
112
    final public function getDatabaseConnection(): Connection
113
    {
114
        if (!$this->connection) {
115
            $eventManager = new DoctrineEventManager();
116
            $connectionFactory = new DatabaseConnectionFactory(
117
                [new SqliteDbPlatform()],
118
                $eventManager
119
            );
120
121
            $this->connection = $connectionFactory->createConnection($this->getDsn());
122
        }
123
124
        return $this->connection;
125
    }
126
127
    /**
128
     * @throws \Doctrine\DBAL\DBALException
129
     */
130
    final public function getSharedGateway(): SharedGateway\Gateway
131
    {
132
        if (!$this->sharedGateway) {
133
            $connection = $this->getDatabaseConnection();
134
            $factory = new SharedGateway\GatewayFactory(
135
                new SharedGateway\DatabasePlatform\FallbackGateway($connection),
136
                [
137
                    'sqlite' => new SharedGateway\DatabasePlatform\SqliteGateway($connection),
138
                ]
139
            );
140
141
            $this->sharedGateway = $factory->buildSharedGateway($connection);
142
        }
143
144
        return $this->sharedGateway;
145
    }
146
147
    /**
148
     * Resets the database on test setup, so we always operate on a clean
149
     * database.
150
     */
151
    protected function setUp(): void
152
    {
153
        try {
154
            $schemaImporter = new LegacySchemaImporter($this->getDatabaseConnection());
155
            $schemaImporter->importSchema(
156
                dirname(__DIR__, 5) .
157
                '/Bundle/EzPublishCoreBundle/Resources/config/storage/legacy/schema.yaml'
158
            );
159
160
            $this->resetSequences();
161
        } catch (PDOException | DBALException | ConnectionException $e) {
162
            self::fail(
163
                sprintf(
164
                    'PDO session could not be created: %s: %s',
165
                    get_class($e),
166
                    $e->getMessage()
167
                )
168
            );
169
        }
170
    }
171
172
    protected function tearDown(): void
173
    {
174
        unset($this->handler);
175
        unset($this->connection);
176
    }
177
178
    /**
179
     * Get a text representation of a result set.
180
     *
181
     * @param array $result
182
     *
183
     * @return string
184
     */
185
    protected static function getResultTextRepresentation(array $result)
186
    {
187
        return implode(
188
            "\n",
189
            array_map(
190
                function ($row) {
191
                    return implode(', ', $row);
192
                },
193
                $result
194
            )
195
        );
196
    }
197
198
    /**
199
     * Inserts database fixture from $file.
200
     *
201
     * @param string $file
202
     */
203
    protected function insertDatabaseFixture($file)
204
    {
205
        $data = require $file;
206
        $db = $this->getDatabaseHandler();
207
208
        foreach ($data as $table => $rows) {
209
            // Check that at least one row exists
210
            if (!isset($rows[0])) {
211
                continue;
212
            }
213
214
            $q = $db->createInsertQuery();
215
            $q->insertInto($db->quoteIdentifier($table));
216
217
            // Contains the bound parameters
218
            $values = [];
219
220
            // Binding the parameters
221
            foreach ($rows[0] as $col => $val) {
222
                $q->set(
223
                    $db->quoteIdentifier($col),
224
                    $q->bindParam($values[$col])
225
                );
226
            }
227
228
            $stmt = $q->prepare();
229
230
            foreach ($rows as $row) {
231
                try {
232
                    // This CANNOT be replaced by:
233
                    // $values = $row
234
                    // each $values[$col] is a PHP reference which should be
235
                    // kept for parameters binding to work
236
                    foreach ($row as $col => $val) {
237
                        $values[$col] = $val;
238
                    }
239
240
                    $stmt->execute();
241
                } catch (Exception $e) {
242
                    echo "$table ( ", implode(', ', $row), " )\n";
243
                    throw $e;
244
                }
245
            }
246
        }
247
248
        $this->resetSequences();
249
    }
250
251
    /**
252
     * Reset DB sequences.
253
     */
254
    public function resetSequences()
255
    {
256
        switch ($this->db) {
257
            case 'pgsql':
258
                // Update PostgreSQL sequences
259
                $handler = $this->getDatabaseHandler();
260
261
                $queries = array_filter(preg_split('(;\\s*$)m',
262
                    file_get_contents(__DIR__ . '/_fixtures/setval.pgsql.sql')));
263
                foreach ($queries as $query) {
264
                    $handler->exec($query);
265
                }
266
        }
267
    }
268
269
    /**
270
     * Assert query result as correct.
271
     *
272
     * Builds text representations of the asserted and fetched query result,
273
     * based on a eZ\Publish\Core\Persistence\Database\SelectQuery object. Compares them using classic diff for
274
     * maximum readability of the differences between expectations and real
275
     * results.
276
     *
277
     * The expectation MUST be passed as a two dimensional array containing
278
     * rows of columns.
279
     *
280
     * @param array $expectation
281
     * @param \eZ\Publish\Core\Persistence\Database\SelectQuery $query
282
     * @param string $message
283
     */
284
    public static function assertQueryResult(array $expectation, SelectQuery $query, $message = '')
285
    {
286
        $statement = $query->prepare();
287
        $statement->execute();
288
289
        $result = [];
290
        while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
291
            $result[] = $row;
292
        }
293
294
        return self::assertEquals(
295
            self::getResultTextRepresentation($expectation),
296
            self::getResultTextRepresentation($result),
297
            $message
298
        );
299
    }
300
301
    /**
302
     * Asserts correct property values on $object.
303
     *
304
     * Asserts that for all keys in $properties a corresponding property
305
     * exists in $object with the *same* value as in $properties.
306
     *
307
     * @param array $properties
308
     * @param object $object
309
     */
310
    protected function assertPropertiesCorrect(array $properties, $object)
311
    {
312
        if (!is_object($object)) {
313
            throw new InvalidArgumentException(
314
                'Received ' . gettype($object) . ' instead of object as second parameter'
315
            );
316
        }
317
        foreach ($properties as $propName => $propVal) {
318
            $this->assertSame(
319
                $propVal,
320
                $object->$propName,
321
                "Incorrect value for \${$propName}"
322
            );
323
        }
324
    }
325
326
    /**
327
     * Asserts $expStruct equals $actStruct in at least $propertyNames.
328
     *
329
     * Asserts that properties of $actStruct equal properties of $expStruct (not
330
     * vice versa!). If $propertyNames is null, all properties are checked.
331
     * Otherwise, $propertyNames provides a white list.
332
     *
333
     * @param object $expStruct
334
     * @param object $actStruct
335
     * @param array $propertyNames
336
     */
337
    protected function assertStructsEqual(
338
        $expStruct,
339
        $actStruct,
340
        array $propertyNames = null
341
    ) {
342
        if ($propertyNames === null) {
343
            $propertyNames = $this->getPublicPropertyNames($expStruct);
344
        }
345
        foreach ($propertyNames as $propName) {
346
            $this->assertEquals(
347
                $expStruct->$propName,
348
                $actStruct->$propName,
349
                "Properties \${$propName} not same"
350
            );
351
        }
352
    }
353
354
    /**
355
     * Returns public property names in $object.
356
     *
357
     * @param object $object
358
     *
359
     * @return array
360
     */
361
    protected function getPublicPropertyNames($object)
362
    {
363
        $refl = new ReflectionObject($object);
364
365
        return array_map(
366
            function ($prop) {
367
                return $prop->getName();
368
            },
369
            $refl->getProperties(ReflectionProperty::IS_PUBLIC)
370
        );
371
    }
372
373
    /**
374
     * @return string
375
     */
376
    protected static function getInstallationDir()
377
    {
378
        static $installDir = null;
379
        if ($installDir === null) {
380
            $config = require 'config.php';
381
            $installDir = $config['install_dir'];
382
        }
383
384
        return $installDir;
385
    }
386
}
387