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

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit;
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\FileFixtureProvider;
14use SqlFixture\FixtureGenerator;
15use SqlFixture\Hydrator\ReflectionHydrator;
16use SqlFixture\Platform\MySql\MySqlSchemaParser;
17use SqlFixture\Platform\MySql\MySqlTypeMapper;
18use SqlFixture\Platform\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\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)]
109final class FileFixtureProviderTest extends TestCase
110{
111    #[Test]
112    public function testLoadsSchemasFromDirectory(): void
113    {
114        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
115        mkdir($tempDir, 0755, true);
116        try {
117            file_put_contents(
118                $tempDir . '/users.sql',
119                'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
120            );
121
122            $faker = Factory::create();
123            $faker->seed(12345);
124            $provider = new FileFixtureProvider($faker, $tempDir);
125
126            self::assertTrue($provider->hasTable('users'));
127        } finally {
128            unlink($tempDir . '/users.sql');
129            rmdir($tempDir);
130        }
131    }
132
133    #[Test]
134    public function testFixtureGeneratesDataForLoadedTable(): void
135    {
136        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
137        mkdir($tempDir, 0755, true);
138        try {
139            file_put_contents(
140                $tempDir . '/users.sql',
141                'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255))'
142            );
143
144            $faker = Factory::create();
145            $faker->seed(12345);
146            $provider = new FileFixtureProvider($faker, $tempDir);
147            $data = $provider->fixture('users');
148
149            self::assertArrayHasKey('id', $data);
150            self::assertArrayHasKey('name', $data);
151        } finally {
152            unlink($tempDir . '/users.sql');
153            rmdir($tempDir);
154        }
155    }
156
157    #[Test]
158    public function testFixtureWithOverrides(): void
159    {
160        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
161        mkdir($tempDir, 0755, true);
162        try {
163            file_put_contents(
164                $tempDir . '/users.sql',
165                'CREATE TABLE users (id INT, name VARCHAR(255))'
166            );
167
168            $faker = Factory::create();
169            $faker->seed(12345);
170            $provider = new FileFixtureProvider($faker, $tempDir);
171            $data = $provider->fixture('users', ['name' => 'Test']);
172
173            self::assertSame('Test', $data['name']);
174        } finally {
175            unlink($tempDir . '/users.sql');
176            rmdir($tempDir);
177        }
178    }
179
180    #[Test]
181    public function testFixtureWithHydration(): void
182    {
183        $target = new class (0, '') {
184            public function __construct(
185                public readonly int $id,
186                public readonly string $name,
187            ) {
188            }
189        };
190        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
191        mkdir($tempDir, 0755, true);
192        try {
193            file_put_contents(
194                $tempDir . '/users.sql',
195                'CREATE TABLE users (id INT, name VARCHAR(255))'
196            );
197
198            $faker = Factory::create();
199            $faker->seed(12345);
200            $provider = new FileFixtureProvider($faker, $tempDir);
201            $user = $provider->fixture('users', ['id' => 1, 'name' => 'Test'], $target::class);
202
203            self::assertSame(1, $user->id);
204            self::assertSame('Test', $user->name);
205        } finally {
206            unlink($tempDir . '/users.sql');
207            rmdir($tempDir);
208        }
209    }
210
211    #[Test]
212    public function testThrowsExceptionForNonExistentTable(): void
213    {
214        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
215        mkdir($tempDir, 0755, true);
216        try {
217            $faker = Factory::create();
218            $faker->seed(12345);
219            $provider = new FileFixtureProvider($faker, $tempDir);
220
221            $this->expectException(RuntimeException::class);
222            $this->expectExceptionMessage('Schema not found');
223            $provider->fixture('nonexistent');
224        } finally {
225            rmdir($tempDir);
226        }
227    }
228
229    #[Test]
230    public function testThrowsExceptionForNonExistentDirectory(): void
231    {
232        $this->expectException(RuntimeException::class);
233        $this->expectExceptionMessage('not a directory');
234        $faker = Factory::create();
235        $faker->seed(12345);
236        new FileFixtureProvider($faker, '/nonexistent/path');
237    }
238
239    #[Test]
240    public function testHasTableReturnsFalseForNonExistent(): void
241    {
242        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
243        mkdir($tempDir, 0755, true);
244        try {
245            $faker = Factory::create();
246            $faker->seed(12345);
247            $provider = new FileFixtureProvider($faker, $tempDir);
248            self::assertFalse($provider->hasTable('nonexistent'));
249        } finally {
250            rmdir($tempDir);
251        }
252    }
253
254    #[Test]
255    public function testGetTableNames(): void
256    {
257        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
258        mkdir($tempDir, 0755, true);
259        try {
260            file_put_contents(
261                $tempDir . '/users.sql',
262                'CREATE TABLE users (id INT)'
263            );
264            file_put_contents(
265                $tempDir . '/posts.sql',
266                'CREATE TABLE posts (id INT)'
267            );
268
269            $faker = Factory::create();
270            $faker->seed(12345);
271            $provider = new FileFixtureProvider($faker, $tempDir);
272            $names = $provider->getTableNames();
273
274            self::assertContains('users', $names);
275            self::assertContains('posts', $names);
276        } finally {
277            unlink($tempDir . '/users.sql');
278            unlink($tempDir . '/posts.sql');
279            rmdir($tempDir);
280        }
281    }
282
283    #[Test]
284    public function testRegisterSchema(): void
285    {
286        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
287        mkdir($tempDir, 0755, true);
288        try {
289            $faker = Factory::create();
290            $faker->seed(12345);
291            $provider = new FileFixtureProvider($faker, $tempDir);
292            $provider->registerSchema('CREATE TABLE dynamic (id INT, value TEXT)');
293
294            self::assertTrue($provider->hasTable('dynamic'));
295            $data = $provider->fixture('dynamic');
296            self::assertArrayHasKey('id', $data);
297        } finally {
298            rmdir($tempDir);
299        }
300    }
301
302    #[Test]
303    public function testGetFixtureGenerator(): void
304    {
305        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
306        mkdir($tempDir, 0755, true);
307        try {
308            $faker = Factory::create();
309            $faker->seed(12345);
310            $provider = new FileFixtureProvider($faker, $tempDir);
311            self::assertSame(['id' => 7], $provider->getFixtureGenerator()->generate(new TableSchema('sample', ['id' => new ColumnDefinition('id', 'INT')]), ['id' => 7]));
312        } finally {
313            rmdir($tempDir);
314        }
315    }
316
317    #[Test]
318    public function testSkipsInvalidSqlFiles(): void
319    {
320        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
321        mkdir($tempDir, 0755, true);
322        try {
323            file_put_contents($tempDir . '/invalid.sql', 'NOT VALID SQL');
324            file_put_contents(
325                $tempDir . '/valid.sql',
326                'CREATE TABLE valid (id INT)'
327            );
328
329            $faker = Factory::create();
330            $faker->seed(12345);
331            $provider = new FileFixtureProvider($faker, $tempDir);
332
333            self::assertTrue($provider->hasTable('valid'));
334            self::assertFalse($provider->hasTable('invalid'));
335        } finally {
336            unlink($tempDir . '/invalid.sql');
337            unlink($tempDir . '/valid.sql');
338            rmdir($tempDir);
339        }
340    }
341
342    #[Test]
343    public function testHandlesCommentsInSqlFiles(): void
344    {
345        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
346        mkdir($tempDir, 0755, true);
347        try {
348            $sql = <<<'SQL'
349                -- This is a comment
350                /* Multi-line
351                   comment */
352                CREATE TABLE with_comments (id INT PRIMARY KEY)
353                SQL;
354
355            file_put_contents($tempDir . '/with_comments.sql', $sql);
356
357            $faker = Factory::create();
358            $faker->seed(12345);
359            $provider = new FileFixtureProvider($faker, $tempDir);
360            self::assertTrue($provider->hasTable('with_comments'));
361        } finally {
362            unlink($tempDir . '/with_comments.sql');
363            rmdir($tempDir);
364        }
365    }
366
367    #[Test]
368    public function testHandlesEmptySqlFile(): void
369    {
370        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
371        mkdir($tempDir, 0755, true);
372        try {
373            file_put_contents($tempDir . '/empty.sql', '');
374            file_put_contents(
375                $tempDir . '/valid.sql',
376                'CREATE TABLE valid (id INT)'
377            );
378
379            $faker = Factory::create();
380            $faker->seed(12345);
381            $provider = new FileFixtureProvider($faker, $tempDir);
382            self::assertTrue($provider->hasTable('valid'));
383        } finally {
384            unlink($tempDir . '/empty.sql');
385            unlink($tempDir . '/valid.sql');
386            rmdir($tempDir);
387        }
388    }
389
390    #[Test]
391    public function testHandlesCommentOnlySqlFile(): void
392    {
393        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
394        mkdir($tempDir, 0755, true);
395        try {
396            file_put_contents($tempDir . '/comments_only.sql', "-- just comments\n/* block */");
397            file_put_contents(
398                $tempDir . '/valid.sql',
399                'CREATE TABLE valid (id INT)'
400            );
401
402            $faker = Factory::create();
403            $faker->seed(12345);
404            $provider = new FileFixtureProvider($faker, $tempDir);
405            self::assertFalse($provider->hasTable('comments_only'));
406        } finally {
407            unlink($tempDir . '/comments_only.sql');
408            unlink($tempDir . '/valid.sql');
409            rmdir($tempDir);
410        }
411    }
412
413    #[Test]
414    public function testTableNameIsCaseInsensitive(): void
415    {
416        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
417        mkdir($tempDir, 0755, true);
418        try {
419            file_put_contents(
420                $tempDir . '/MyTable.sql',
421                'CREATE TABLE MyTable (id INT)'
422            );
423
424            $faker = Factory::create();
425            $faker->seed(12345);
426            $provider = new FileFixtureProvider($faker, $tempDir);
427
428            self::assertTrue($provider->hasTable('mytable'));
429            self::assertTrue($provider->hasTable('MYTABLE'));
430            self::assertTrue($provider->hasTable('MyTable'));
431        } finally {
432            unlink($tempDir . '/MyTable.sql');
433            rmdir($tempDir);
434        }
435    }
436
437    #[Test]
438    public function testFixtureWithMixedCaseTableName(): void
439    {
440        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
441        mkdir($tempDir, 0755, true);
442        try {
443            file_put_contents(
444                $tempDir . '/Users.sql',
445                'CREATE TABLE Users (id INT, name VARCHAR(255))'
446            );
447
448            $faker = Factory::create();
449            $faker->seed(12345);
450            $provider = new FileFixtureProvider($faker, $tempDir);
451            $data = $provider->fixture('USERS');
452
453            self::assertArrayHasKey('id', $data);
454            self::assertArrayHasKey('name', $data);
455        } finally {
456            unlink($tempDir . '/Users.sql');
457            rmdir($tempDir);
458        }
459    }
460
461    #[Test]
462    public function testRegisterSchemaWithMixedCaseFixture(): void
463    {
464        $tempDir = sys_get_temp_dir() . '/sql-fixture-test-' . uniqid();
465        mkdir($tempDir, 0755, true);
466        try {
467            $faker = Factory::create();
468            $faker->seed(12345);
469            $provider = new FileFixtureProvider($faker, $tempDir);
470            $provider->registerSchema('CREATE TABLE MyItems (id INT, value TEXT)');
471
472            $data = $provider->fixture('MYITEMS');
473
474            self::assertArrayHasKey('id', $data);
475            self::assertArrayHasKey('value', $data);
476        } finally {
477            rmdir($tempDir);
478        }
479    }
480}
481