packages/sql-fixture/tests/Unit/Provider/PlatformFactoryTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Provider;
6
7use Container\MySqlRelease;
8use Container\PostgreSqlRelease;
9use PDO;
10use PHPUnit\Framework\Attributes\CoversClass;
11use PHPUnit\Framework\Attributes\Test;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14use SqlFixture\Platform\MySql\MySqlSchemaFetcher;
15use SqlFixture\Platform\MySql\MySqlSchemaParser;
16use SqlFixture\Platform\MySql\MySqlTypeMapper;
17use SqlFixture\Platform\PostgreSql\PostgreSqlSchemaFetcher;
18use SqlFixture\Platform\PostgreSql\PostgreSqlSchemaParser;
19use SqlFixture\Platform\PostgreSql\PostgreSqlTypeMapper;
20use SqlFixture\Platform\Sqlite\SqliteSchemaFetcher;
21use SqlFixture\Platform\Sqlite\SqliteSchemaParser;
22use SqlFixture\Platform\Sqlite\SqliteTypeMapper;
23use SqlFixture\Provider\PlatformFactory;
24
25#[CoversClass(PlatformFactory::class)]
26#[UsesClass(SqliteSchemaFetcher::class)]
27#[UsesClass(PostgreSqlSchemaFetcher::class)]
28#[UsesClass(MySqlSchemaFetcher::class)]
29#[UsesClass(MySqlSchemaParser::class)]
30#[UsesClass(MySqlTypeMapper::class)]
31#[UsesClass(PostgreSqlSchemaParser::class)]
32#[UsesClass(PostgreSqlTypeMapper::class)]
33#[UsesClass(SqliteSchemaParser::class)]
34#[UsesClass(SqliteTypeMapper::class)]
35#[UsesClass(\SqlFixture\Schema\ColumnDefinition::class)]
36#[UsesClass(\SqlFixture\Schema\SchemaFetcherInterface::class)]
37#[UsesClass(\SqlFixture\Schema\SchemaParseException::class)]
38#[UsesClass(\SqlFixture\Schema\SchemaParserInterface::class)]
39#[UsesClass(\SqlFixture\Schema\TableSchema::class)]
40#[UsesClass(\SqlFixture\TypeMapper\TypeMapperInterface::class)]
41#[UsesClass(\SqlFixture\Platform\MySql\Schema\ColumnParser::class)]
42#[UsesClass(\SqlFixture\Platform\MySql\Schema\CreateTableQuery::class)]
43#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefaultExpression::class)]
44#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefinitionIntegrity::class)]
45#[UsesClass(\SqlFixture\Platform\MySql\Schema\IdentifierQuoter::class)]
46#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinition::class)]
47#[UsesClass(\SqlFixture\Platform\MySql\Schema\TypeParameters::class)]
48#[UsesClass(\SqlFixture\Platform\MySql\Value\ColumnGenerator::class)]
49#[UsesClass(\SqlFixture\Platform\MySql\Value\DecimalGenerator::class)]
50#[UsesClass(\SqlFixture\Platform\MySql\Value\GeometryGenerator::class)]
51#[UsesClass(\SqlFixture\Platform\MySql\Value\IntegerGenerator::class)]
52#[UsesClass(\SqlFixture\Platform\MySql\Value\NumericGenerator::class)]
53#[UsesClass(\SqlFixture\Platform\MySql\Value\SpatialGenerator::class)]
54#[UsesClass(\SqlFixture\Platform\MySql\Value\StringGenerator::class)]
55#[UsesClass(\SqlFixture\Platform\MySql\Value\TextGenerator::class)]
56#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogColumn::class)]
57#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogDdl::class)]
58#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogQuery::class)]
59#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogSchema::class)]
60#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\ColumnParser::class)]
61#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefaultExpression::class)]
62#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefinitionList::class)]
63#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TableSyntax::class)]
64#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TypeDeclaration::class)]
65#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\ColumnGenerator::class)]
66#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\DecimalGenerator::class)]
67#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\NumericGenerator::class)]
68#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StringGenerator::class)]
69#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StructuredGenerator::class)]
70#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\TemporalGenerator::class)]
71#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\ColumnParser::class)]
72#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\CreateTableQuery::class)]
73#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefaultExpression::class)]
74#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefinitionList::class)]
75#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaColumn::class)]
76#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaSchema::class)]
77#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TableSyntax::class)]
78#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TypeDeclaration::class)]
79#[UsesClass(\SqlFixture\Platform\Sqlite\Value\ColumnGenerator::class)]
80#[UsesClass(\SqlFixture\Platform\Sqlite\Value\TypeAffinity::class)]
81#[UsesClass(\SqlFixture\Schema\DefinitionSegments::class)]
82#[UsesClass(\SqlFixture\Schema\TypeShape::class)]
83#[UsesClass(\SqlFixture\TypeMapper\ParagraphGenerator::class)]
84#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinitionInput::class)]
85#[UsesClass(\SqlFixture\Schema\Exception\InvalidSqlException::class)]
86#[UsesClass(\SqlFixture\Schema\Exception\ExpectedCreateTableException::class)]
87#[UsesClass(\SqlFixture\Schema\Exception\MissingColumnDefinitionsException::class)]
88#[UsesClass(\SqlFixture\TypeMapper\IntegerRange::class)]
89#[UsesClass(\SqlFixture\TypeMapper\IntegerWidth::class)]
90#[UsesClass(\SqlFixture\TypeMapper\DecimalRange::class)]
91#[UsesClass(\SqlFixture\Version\ServerVersion::class)]
92#[UsesClass(\SqlFixture\Version\Releases::class)]
93#[UsesClass(\SqlFixture\Version\ReleaseNumber::class)]
94#[UsesClass(\SqlFixture\Version\UnsupportedVersionException::class)]
95#[\PHPUnit\Framework\Attributes\Medium]
96final class PlatformFactoryTest extends TestCase
97{
98    #[Test]
99    public function testCreateSchemaParserForMysql(): void
100    {
101        $parser = PlatformFactory::createSchemaParser(PlatformFactory::DRIVER_MYSQL);
102
103        self::assertInstanceOf(MySqlSchemaParser::class, $parser);
104    }
105
106    #[Test]
107    public function testCreateSchemaParserForSqlite(): void
108    {
109        $parser = PlatformFactory::createSchemaParser(PlatformFactory::DRIVER_SQLITE);
110
111        self::assertInstanceOf(SqliteSchemaParser::class, $parser);
112    }
113
114    #[Test]
115    public function testCreateSchemaParserForPgsql(): void
116    {
117        $parser = PlatformFactory::createSchemaParser(PlatformFactory::DRIVER_PGSQL);
118
119        self::assertInstanceOf(PostgreSqlSchemaParser::class, $parser);
120    }
121
122    #[Test]
123    public function testCreateTypeMapperForMysql(): void
124    {
125        $mapper = PlatformFactory::createTypeMapper(PlatformFactory::DRIVER_MYSQL);
126
127        self::assertInstanceOf(MySqlTypeMapper::class, $mapper);
128    }
129
130    #[Test]
131    public function testCreateTypeMapperForSqlite(): void
132    {
133        $mapper = PlatformFactory::createTypeMapper(PlatformFactory::DRIVER_SQLITE);
134
135        self::assertInstanceOf(SqliteTypeMapper::class, $mapper);
136    }
137
138    #[Test]
139    public function testCreateTypeMapperForPgsql(): void
140    {
141        $mapper = PlatformFactory::createTypeMapper(PlatformFactory::DRIVER_PGSQL);
142
143        self::assertInstanceOf(PostgreSqlTypeMapper::class, $mapper);
144    }
145
146    #[Test]
147    public function testCreateSchemaFetcherForMysql(): void
148    {
149        $fetcher = PlatformFactory::createSchemaFetcher(PlatformFactory::DRIVER_MYSQL);
150
151        self::assertInstanceOf(MySqlSchemaFetcher::class, $fetcher);
152    }
153
154    #[Test]
155    public function testCreateSchemaFetcherForSqlite(): void
156    {
157        $fetcher = PlatformFactory::createSchemaFetcher(PlatformFactory::DRIVER_SQLITE);
158
159        self::assertInstanceOf(SqliteSchemaFetcher::class, $fetcher);
160    }
161
162    #[Test]
163    public function testCreateSchemaFetcherForPgsql(): void
164    {
165        $fetcher = PlatformFactory::createSchemaFetcher(PlatformFactory::DRIVER_PGSQL);
166
167        self::assertInstanceOf(PostgreSqlSchemaFetcher::class, $fetcher);
168    }
169
170    #[Test]
171    public function testDetectDriverForSqlite(): void
172    {
173        $pdo = new PDO('sqlite::memory:');
174
175        $driver = PlatformFactory::detectDriver($pdo);
176
177        self::assertSame(PlatformFactory::DRIVER_SQLITE, $driver);
178    }
179
180    #[Test]
181    public function testGetSupportedDrivers(): void
182    {
183        $drivers = PlatformFactory::getSupportedDrivers();
184
185        self::assertContains(PlatformFactory::DRIVER_MYSQL, $drivers);
186        self::assertContains(PlatformFactory::DRIVER_SQLITE, $drivers);
187        self::assertContains(PlatformFactory::DRIVER_PGSQL, $drivers);
188    }
189
190    #[\PHPUnit\Framework\Attributes\DataProvider('providerDatabaseDrivers')]
191    public function testDetectDriverReadsTheNativeConnection(string $prefix, string $expected): void
192    {
193        $pdo = new PDO(
194            (string) getenv($prefix . '_DSN'),
195            (string) getenv($prefix . '_USER'),
196            (string) getenv($prefix . '_PASSWORD'),
197            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false],
198        );
199        self::assertSame($expected, PlatformFactory::detectDriver($pdo));
200    }
201
202    /**
203     * @return iterable<string, array{string, string}>
204     */
205    public static function providerDatabaseDrivers(): iterable
206    {
207        yield 'mysql' => ['SQL_FIXTURE_MYSQL', 'mysql'];
208        yield 'postgres' => ['SQL_FIXTURE_PGSQL', 'pgsql'];
209    }
210
211    #[Test]
212    public function testResolveVersionNamesTheDefaultOfTheDriver(): void
213    {
214        self::assertSame('mysql-8.4.7', PlatformFactory::resolveVersion(PlatformFactory::DRIVER_MYSQL)->tag);
215        self::assertSame('pg-17.2', PlatformFactory::resolveVersion(PlatformFactory::DRIVER_PGSQL)->tag);
216        self::assertSame('sqlite-3.47.2', PlatformFactory::resolveVersion(PlatformFactory::DRIVER_SQLITE)->tag);
217        self::assertSame('5.6.51', PlatformFactory::resolveVersion(PlatformFactory::DRIVER_MYSQL, 'mysql-5.6.51')->number);
218    }
219
220    #[Test]
221    public function testResolveVersionRejectsAnUnsupportedTag(): void
222    {
223        $this->expectException(\SqlFixture\Version\UnsupportedVersionException::class);
224        PlatformFactory::resolveVersion(PlatformFactory::DRIVER_MYSQL, 'mysql-5.5.62');
225    }
226
227    #[Test]
228    public function testCreateSchemaParserRejectsATagOfAnotherDriver(): void
229    {
230        $this->expectException(\SqlFixture\Version\UnsupportedVersionException::class);
231        $this->expectExceptionMessage('Unsupported sqlite version: mysql-8.4.7');
232        PlatformFactory::createSchemaParser(PlatformFactory::DRIVER_SQLITE, 'mysql-8.4.7');
233    }
234
235    #[Test]
236    public function testCreateSchemaFetcherRejectsAnUnsupportedTag(): void
237    {
238        $this->expectException(\SqlFixture\Version\UnsupportedVersionException::class);
239        PlatformFactory::createSchemaFetcher(PlatformFactory::DRIVER_PGSQL, 'pg-9.6.0');
240    }
241
242    #[Test]
243    public function testDetectVersionMatchesTheLinkedSqlite(): void
244    {
245        $pdo = new PDO('sqlite::memory:');
246
247        $version = PlatformFactory::detectVersion($pdo);
248
249        self::assertSame(PlatformFactory::DRIVER_SQLITE, $version->dialect);
250        self::assertSame('sqlite-3.47.2', $version->tag);
251    }
252
253    #[Test]
254    public function testDetectVersionResolvesAnExplicitTagAgainstTheDetectedDriver(): void
255    {
256        $pdo = new PDO('sqlite::memory:');
257
258        self::assertSame('sqlite-3.47.2', PlatformFactory::detectVersion($pdo, 'sqlite-3.47.2')->tag);
259
260        $this->expectException(\SqlFixture\Version\UnsupportedVersionException::class);
261        PlatformFactory::detectVersion($pdo, 'mysql-8.4.7');
262    }
263
264    #[\PHPUnit\Framework\Attributes\DataProvider('providerDatabaseVersions')]
265    public function testDetectVersionReadsTheNativeServer(string $prefix, string $driver, string $expected): void
266    {
267        $pdo = new PDO(
268            (string) getenv($prefix . '_DSN'),
269            (string) getenv($prefix . '_USER'),
270            (string) getenv($prefix . '_PASSWORD'),
271            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false],
272        );
273        $version = PlatformFactory::detectVersion($pdo);
274        self::assertSame($driver, $version->dialect);
275        self::assertSame($expected, $version->tag);
276    }
277
278    /**
279     * @return iterable<string, array{string, string, string}>
280     */
281    public static function providerDatabaseVersions(): iterable
282    {
283        $mysql = getenv('SQL_FIXTURE_MYSQL_VERSION');
284        $postgres = getenv('SQL_FIXTURE_PGSQL_VERSION');
285        yield 'mysql' => ['SQL_FIXTURE_MYSQL', 'mysql', 'mysql-' . ($mysql === false || $mysql === '' ? MySqlRelease::DEFAULT : $mysql)];
286        yield 'postgres' => ['SQL_FIXTURE_PGSQL', 'pgsql', 'pg-' . ($postgres === false || $postgres === '' ? PostgreSqlRelease::DEFAULT : $postgres)];
287    }
288
289    #[Test]
290    public function testGetSupportedVersionsListsTheTagsOfADriver(): void
291    {
292        self::assertSame(['pg-16.6', 'pg-17.2'], PlatformFactory::getSupportedVersions(PlatformFactory::DRIVER_PGSQL));
293        self::assertContains('mysql-5.6.51', PlatformFactory::getSupportedVersions(PlatformFactory::DRIVER_MYSQL));
294        self::assertContains('mysql-9.1.0', PlatformFactory::getSupportedVersions(PlatformFactory::DRIVER_MYSQL));
295    }
296}
297