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

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Provider;
6
7use Faker\Factory;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\Test;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use RuntimeException;
13use SqlFixture\Hydrator\ReflectionHydrator;
14use SqlFixture\Platform\MySql\MySqlSchemaParser;
15use SqlFixture\Platform\MySql\MySqlTypeMapper;
16use SqlFixture\Provider\FileFixtureProvider;
17use SqlFixture\Provider\FixtureGenerator;
18use SqlFixture\Provider\PlatformFactory;
19use SqlFixture\Schema\ColumnDefinition;
20use SqlFixture\Schema\SchemaParseException;
21use SqlFixture\Schema\TableSchema;
22
23#[CoversClass(FileFixtureProvider::class)]
24#[UsesClass(FixtureGenerator::class)]
25#[UsesClass(PlatformFactory::class)]
26#[UsesClass(MySqlSchemaParser::class)]
27#[UsesClass(ColumnDefinition::class)]
28#[UsesClass(TableSchema::class)]
29#[UsesClass(SchemaParseException::class)]
30#[UsesClass(MySqlTypeMapper::class)]
31#[UsesClass(ReflectionHydrator::class)]
32#[CoversClass(\SqlFixture\Provider\DdlDirectory::class)]
33#[CoversClass(\SqlFixture\Provider\DdlFile::class)]
34#[UsesClass(\SqlFixture\Hydrator\HydrationException::class)]
35#[UsesClass(\SqlFixture\Hydrator\HydratorInterface::class)]
36#[UsesClass(\SqlFixture\Platform\MySql\MySqlSchemaFetcher::class)]
37#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaFetcher::class)]
38#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaParser::class)]
39#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlTypeMapper::class)]
40#[UsesClass(\SqlFixture\Platform\Sqlite\SqliteSchemaFetcher::class)]
41#[UsesClass(\SqlFixture\Platform\Sqlite\SqliteSchemaParser::class)]
42#[UsesClass(\SqlFixture\Platform\Sqlite\SqliteTypeMapper::class)]
43#[UsesClass(\SqlFixture\Schema\SchemaFetcherInterface::class)]
44#[UsesClass(\SqlFixture\Schema\SchemaParserInterface::class)]
45#[UsesClass(\SqlFixture\TypeMapper\TypeMapperInterface::class)]
46#[UsesClass(\SqlFixture\Fixture\RowGeneration::class)]
47#[UsesClass(\SqlFixture\Fixture\Validation\OverrideValidator::class)]
48#[UsesClass(\SqlFixture\Hydrator\Reflection\ConstructorHydration::class)]
49#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyHydration::class)]
50#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyNames::class)]
51#[UsesClass(\SqlFixture\Hydrator\Reflection\ValueConversion::class)]
52#[UsesClass(\SqlFixture\Fixture\Exception\InvalidOverrideException::class)]
53#[UsesClass(\SqlFixture\Platform\MySql\Schema\ColumnParser::class)]
54#[UsesClass(\SqlFixture\Platform\MySql\Schema\CreateTableQuery::class)]
55#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefaultExpression::class)]
56#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefinitionIntegrity::class)]
57#[UsesClass(\SqlFixture\Platform\MySql\Schema\IdentifierQuoter::class)]
58#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinition::class)]
59#[UsesClass(\SqlFixture\Platform\MySql\Schema\TypeParameters::class)]
60#[UsesClass(\SqlFixture\Platform\MySql\Value\ColumnGenerator::class)]
61#[UsesClass(\SqlFixture\Platform\MySql\Value\DecimalGenerator::class)]
62#[UsesClass(\SqlFixture\Platform\MySql\Value\GeometryGenerator::class)]
63#[UsesClass(\SqlFixture\Platform\MySql\Value\IntegerGenerator::class)]
64#[UsesClass(\SqlFixture\Platform\MySql\Value\NumericGenerator::class)]
65#[UsesClass(\SqlFixture\Platform\MySql\Value\SpatialGenerator::class)]
66#[UsesClass(\SqlFixture\Platform\MySql\Value\StringGenerator::class)]
67#[UsesClass(\SqlFixture\Platform\MySql\Value\TextGenerator::class)]
68#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogColumn::class)]
69#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogDdl::class)]
70#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogQuery::class)]
71#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogSchema::class)]
72#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\ColumnParser::class)]
73#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefaultExpression::class)]
74#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefinitionList::class)]
75#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TableSyntax::class)]
76#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TypeDeclaration::class)]
77#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\ColumnGenerator::class)]
78#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\DecimalGenerator::class)]
79#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\NumericGenerator::class)]
80#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StringGenerator::class)]
81#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StructuredGenerator::class)]
82#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\TemporalGenerator::class)]
83#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\ColumnParser::class)]
84#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\CreateTableQuery::class)]
85#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefaultExpression::class)]
86#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefinitionList::class)]
87#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaColumn::class)]
88#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaSchema::class)]
89#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TableSyntax::class)]
90#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TypeDeclaration::class)]
91#[UsesClass(\SqlFixture\Platform\Sqlite\Value\ColumnGenerator::class)]
92#[UsesClass(\SqlFixture\Platform\Sqlite\Value\TypeAffinity::class)]
93#[UsesClass(\SqlFixture\Schema\DefinitionSegments::class)]
94#[UsesClass(\SqlFixture\Schema\TypeShape::class)]
95#[UsesClass(\SqlFixture\TypeMapper\ParagraphGenerator::class)]
96#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinitionInput::class)]
97#[UsesClass(\SqlFixture\Hydrator\Exception\ClassNotFoundException::class)]
98#[UsesClass(\SqlFixture\Hydrator\Exception\MissingConstructorArgumentException::class)]
99#[UsesClass(\SqlFixture\Fixture\Exception\UnknownOverrideColumnException::class)]
100#[UsesClass(\SqlFixture\Fixture\Exception\NullOverrideException::class)]
101#[UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnOverrideException::class)]
102#[UsesClass(\SqlFixture\Schema\Exception\InvalidSqlException::class)]
103#[UsesClass(\SqlFixture\Schema\Exception\ExpectedCreateTableException::class)]
104#[UsesClass(\SqlFixture\Schema\Exception\MissingColumnDefinitionsException::class)]
105#[UsesClass(\SqlFixture\TypeMapper\IntegerRange::class)]
106#[UsesClass(\SqlFixture\TypeMapper\IntegerWidth::class)]
107#[UsesClass(\SqlFixture\TypeMapper\DecimalRange::class)]
108#[UsesClass(\SqlFixture\Hydrator\Reflection\ConversionTarget::class)]
109#[UsesClass(\SqlFixture\Fixture\RowGenerator::class)]
110#[UsesClass(\SqlFixture\Version\ServerVersion::class)]
111#[UsesClass(\SqlFixture\Version\Releases::class)]
112#[UsesClass(\SqlFixture\Version\ReleaseNumber::class)]
113#[UsesClass(\SqlFixture\Version\UnsupportedVersionException::class)]
114final class FileFixtureProviderTest extends TestCase
115{
116    #[Test]
117    public function testLoadsSchemasFromDirectory(): void
118    {
119        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
120        mkdir($tempDir, 0755, true);
121        try {
122            file_put_contents(
123                $tempDir . '/users.sql',
124                'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
125            );
126
127            $faker = Factory::create();
128            $faker->seed(12345);
129            $provider = new FileFixtureProvider($faker, $tempDir);
130
131            self::assertTrue($provider->hasTable('users'));
132        } finally {
133            unlink($tempDir . '/users.sql');
134            rmdir($tempDir);
135        }
136    }
137
138    #[Test]
139    public function testFixtureGeneratesDataForLoadedTable(): void
140    {
141        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
142        mkdir($tempDir, 0755, true);
143        try {
144            file_put_contents(
145                $tempDir . '/users.sql',
146                'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
147            );
148
149            $faker = Factory::create();
150            $faker->seed(12345);
151            $provider = new FileFixtureProvider($faker, $tempDir);
152            $data = $provider->fixture('users');
153
154            self::assertArrayHasKey('id', $data);
155            self::assertArrayHasKey('name', $data);
156        } finally {
157            unlink($tempDir . '/users.sql');
158            rmdir($tempDir);
159        }
160    }
161
162    #[Test]
163    public function testFixtureWithOverrides(): void
164    {
165        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
166        mkdir($tempDir, 0755, true);
167        try {
168            file_put_contents(
169                $tempDir . '/users.sql',
170                'CREATE TABLE users (id INT, name VARCHAR(255))'
171            );
172
173            $faker = Factory::create();
174            $faker->seed(12345);
175            $provider = new FileFixtureProvider($faker, $tempDir);
176            $data = $provider->fixture('users', ['name' => 'Test']);
177
178            self::assertSame('Test', $data['name']);
179        } finally {
180            unlink($tempDir . '/users.sql');
181            rmdir($tempDir);
182        }
183    }
184
185    #[Test]
186    public function testFixtureWithHydration(): void
187    {
188        $target = new class (0, '') {
189            public function __construct(
190                public readonly int $id,
191                public readonly string $name,
192            ) {
193            }
194        };
195        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
196        mkdir($tempDir, 0755, true);
197        try {
198            file_put_contents(
199                $tempDir . '/users.sql',
200                'CREATE TABLE users (id INT, name VARCHAR(255))'
201            );
202
203            $faker = Factory::create();
204            $faker->seed(12345);
205            $provider = new FileFixtureProvider($faker, $tempDir);
206            $user = $provider->fixture('users', ['id' => 1, 'name' => 'Test'], $target::class);
207
208            self::assertSame(1, $user->id);
209            self::assertSame('Test', $user->name);
210        } finally {
211            unlink($tempDir . '/users.sql');
212            rmdir($tempDir);
213        }
214    }
215
216    #[Test]
217    public function testThrowsExceptionForNonExistentTable(): void
218    {
219        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
220        mkdir($tempDir, 0755, true);
221        try {
222            $faker = Factory::create();
223            $faker->seed(12345);
224            $provider = new FileFixtureProvider($faker, $tempDir);
225
226            $this->expectException(RuntimeException::class);
227            $this->expectExceptionMessage('Schema not found');
228            $provider->fixture('nonexistent');
229        } finally {
230            rmdir($tempDir);
231        }
232    }
233
234    #[Test]
235    public function testThrowsExceptionForNonExistentDirectory(): void
236    {
237        $this->expectException(RuntimeException::class);
238        $this->expectExceptionMessage('not a directory');
239        $faker = Factory::create();
240        $faker->seed(12345);
241        new FileFixtureProvider($faker, '/nonexistent/path');
242    }
243
244    #[Test]
245    public function testHasTableReturnsFalseForNonExistent(): void
246    {
247        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
248        mkdir($tempDir, 0755, true);
249        try {
250            $faker = Factory::create();
251            $faker->seed(12345);
252            $provider = new FileFixtureProvider($faker, $tempDir);
253            self::assertFalse($provider->hasTable('nonexistent'));
254        } finally {
255            rmdir($tempDir);
256        }
257    }
258
259    #[Test]
260    public function testGetTableNames(): void
261    {
262        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
263        mkdir($tempDir, 0755, true);
264        try {
265            file_put_contents(
266                $tempDir . '/users.sql',
267                'CREATE TABLE users (id INT)'
268            );
269            file_put_contents(
270                $tempDir . '/posts.sql',
271                'CREATE TABLE posts (id INT)'
272            );
273
274            $faker = Factory::create();
275            $faker->seed(12345);
276            $provider = new FileFixtureProvider($faker, $tempDir);
277            $names = $provider->getTableNames();
278
279            self::assertContains('users', $names);
280            self::assertContains('posts', $names);
281        } finally {
282            unlink($tempDir . '/users.sql');
283            unlink($tempDir . '/posts.sql');
284            rmdir($tempDir);
285        }
286    }
287
288    #[Test]
289    public function testRegisterSchema(): void
290    {
291        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
292        mkdir($tempDir, 0755, true);
293        try {
294            $faker = Factory::create();
295            $faker->seed(12345);
296            $provider = new FileFixtureProvider($faker, $tempDir);
297            $provider->registerSchema('CREATE TABLE dynamic (id INT, value TEXT)');
298
299            self::assertTrue($provider->hasTable('dynamic'));
300            $data = $provider->fixture('dynamic');
301            self::assertArrayHasKey('id', $data);
302        } finally {
303            rmdir($tempDir);
304        }
305    }
306
307    #[Test]
308    public function testGetFixtureGenerator(): void
309    {
310        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
311        mkdir($tempDir, 0755, true);
312        try {
313            $faker = Factory::create();
314            $faker->seed(12345);
315            $provider = new FileFixtureProvider($faker, $tempDir);
316            self::assertSame(['id' => 7], $provider->getFixtureGenerator()->generate(new TableSchema('sample', ['id' => new ColumnDefinition('id', 'INT')]), ['id' => 7]));
317        } finally {
318            rmdir($tempDir);
319        }
320    }
321
322    #[Test]
323    public function testSkipsInvalidSqlFiles(): void
324    {
325        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
326        mkdir($tempDir, 0755, true);
327        try {
328            file_put_contents($tempDir . '/invalid.sql', 'NOT VALID SQL');
329            file_put_contents(
330                $tempDir . '/valid.sql',
331                'CREATE TABLE valid (id INT)'
332            );
333
334            $faker = Factory::create();
335            $faker->seed(12345);
336            $provider = new FileFixtureProvider($faker, $tempDir);
337
338            self::assertTrue($provider->hasTable('valid'));
339            self::assertFalse($provider->hasTable('invalid'));
340        } finally {
341            unlink($tempDir . '/invalid.sql');
342            unlink($tempDir . '/valid.sql');
343            rmdir($tempDir);
344        }
345    }
346
347    #[Test]
348    public function testHandlesCommentsInSqlFiles(): void
349    {
350        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
351        mkdir($tempDir, 0755, true);
352        try {
353            $sql = <<<'SQL'
354                -- This is a comment
355                /* Multi-line
356                   comment */
357                CREATE TABLE with_comments (id INT PRIMARY KEY)
358                SQL;
359
360            file_put_contents($tempDir . '/with_comments.sql', $sql);
361
362            $faker = Factory::create();
363            $faker->seed(12345);
364            $provider = new FileFixtureProvider($faker, $tempDir);
365            self::assertTrue($provider->hasTable('with_comments'));
366        } finally {
367            unlink($tempDir . '/with_comments.sql');
368            rmdir($tempDir);
369        }
370    }
371
372    #[Test]
373    public function testHandlesEmptySqlFile(): void
374    {
375        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
376        mkdir($tempDir, 0755, true);
377        try {
378            file_put_contents($tempDir . '/empty.sql', '');
379            file_put_contents(
380                $tempDir . '/valid.sql',
381                'CREATE TABLE valid (id INT)'
382            );
383
384            $faker = Factory::create();
385            $faker->seed(12345);
386            $provider = new FileFixtureProvider($faker, $tempDir);
387            self::assertTrue($provider->hasTable('valid'));
388        } finally {
389            unlink($tempDir . '/empty.sql');
390            unlink($tempDir . '/valid.sql');
391            rmdir($tempDir);
392        }
393    }
394
395    #[Test]
396    public function testHandlesCommentOnlySqlFile(): void
397    {
398        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
399        mkdir($tempDir, 0755, true);
400        try {
401            file_put_contents($tempDir . '/comments_only.sql', "-- just comments\n/* block */");
402            file_put_contents(
403                $tempDir . '/valid.sql',
404                'CREATE TABLE valid (id INT)'
405            );
406
407            $faker = Factory::create();
408            $faker->seed(12345);
409            $provider = new FileFixtureProvider($faker, $tempDir);
410            self::assertFalse($provider->hasTable('comments_only'));
411        } finally {
412            unlink($tempDir . '/comments_only.sql');
413            unlink($tempDir . '/valid.sql');
414            rmdir($tempDir);
415        }
416    }
417
418    #[Test]
419    public function testTableNameIsCaseInsensitive(): void
420    {
421        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
422        mkdir($tempDir, 0755, true);
423        try {
424            file_put_contents(
425                $tempDir . '/MyTable.sql',
426                'CREATE TABLE MyTable (id INT)'
427            );
428
429            $faker = Factory::create();
430            $faker->seed(12345);
431            $provider = new FileFixtureProvider($faker, $tempDir);
432
433            self::assertTrue($provider->hasTable('mytable'));
434            self::assertTrue($provider->hasTable('MYTABLE'));
435            self::assertTrue($provider->hasTable('MyTable'));
436        } finally {
437            unlink($tempDir . '/MyTable.sql');
438            rmdir($tempDir);
439        }
440    }
441
442    #[Test]
443    public function testFixtureWithMixedCaseTableName(): void
444    {
445        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
446        mkdir($tempDir, 0755, true);
447        try {
448            file_put_contents(
449                $tempDir . '/Users.sql',
450                'CREATE TABLE Users (id INT, name VARCHAR(255))'
451            );
452
453            $faker = Factory::create();
454            $faker->seed(12345);
455            $provider = new FileFixtureProvider($faker, $tempDir);
456            $data = $provider->fixture('USERS');
457
458            self::assertArrayHasKey('id', $data);
459            self::assertArrayHasKey('name', $data);
460        } finally {
461            unlink($tempDir . '/Users.sql');
462            rmdir($tempDir);
463        }
464    }
465
466    #[Test]
467    public function testRegisterSchemaWithMixedCaseFixture(): void
468    {
469        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
470        mkdir($tempDir, 0755, true);
471        try {
472            $faker = Factory::create();
473            $faker->seed(12345);
474            $provider = new FileFixtureProvider($faker, $tempDir);
475            $provider->registerSchema('CREATE TABLE MyItems (id INT, value TEXT)');
476
477            $data = $provider->fixture('MYITEMS');
478
479            self::assertArrayHasKey('id', $data);
480            self::assertArrayHasKey('value', $data);
481        } finally {
482            rmdir($tempDir);
483        }
484    }
485
486    public function testGetDialectAndGetVersionNameTheSelectedRelease(): void
487    {
488        $tempDir = sys_get_temp_dir() . '/sql_fixture_versions_' . bin2hex(random_bytes(4));
489        mkdir($tempDir);
490        file_put_contents($tempDir . '/users.sql', 'CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT NOT NULL)');
491
492        try {
493            $provider = new FileFixtureProvider(Factory::create(), $tempDir, dialect: PlatformFactory::DRIVER_PGSQL, version: 'pg-17.2');
494
495            self::assertSame('pgsql', $provider->getDialect());
496            self::assertSame('pg-17.2', $provider->getVersion());
497            self::assertSame('mysql-8.4.7', (new FileFixtureProvider(Factory::create(), $tempDir))->getVersion());
498            self::assertArrayHasKey('name', $provider->fixture('users'));
499        } finally {
500            unlink($tempDir . '/users.sql');
501            rmdir($tempDir);
502        }
503    }
504
505    public function testGetVersionRejectsAnUnsupportedTag(): void
506    {
507        $tempDir = sys_get_temp_dir() . '/sql_fixture_versions_' . bin2hex(random_bytes(4));
508        mkdir($tempDir);
509
510        try {
511            $this->expectException(\SqlFixture\Version\UnsupportedVersionException::class);
512            new FileFixtureProvider(Factory::create(), $tempDir, version: 'mysql-5.5.62');
513        } finally {
514            rmdir($tempDir);
515        }
516    }
517}
518