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