packages/sql-fixture/tests/Unit/FixtureProviderTest.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 SqlFixture\FixtureGenerator;
13use SqlFixture\FixtureProvider;
14use SqlFixture\Hydrator\ReflectionHydrator;
15use SqlFixture\Platform\MySql\MySqlSchemaParser;
16use SqlFixture\Platform\MySql\MySqlTypeMapper;
17use SqlFixture\Platform\PlatformFactory;
18use SqlFixture\Platform\PostgreSql\PostgreSqlTypeMapper;
19use SqlFixture\Platform\Sqlite\SqliteSchemaParser;
20use SqlFixture\Platform\Sqlite\SqliteTypeMapper;
21use SqlFixture\Schema\ColumnDefinition;
22use SqlFixture\Schema\StaticSchemaResolver;
23use SqlFixture\Schema\TableSchema;
24
25#[CoversClass(FixtureProvider::class)]
26#[UsesClass(FixtureGenerator::class)]
27#[UsesClass(PlatformFactory::class)]
28#[UsesClass(MySqlSchemaParser::class)]
29#[UsesClass(MySqlTypeMapper::class)]
30#[UsesClass(ColumnDefinition::class)]
31#[UsesClass(TableSchema::class)]
32#[UsesClass(StaticSchemaResolver::class)]
33#[UsesClass(ReflectionHydrator::class)]
34#[UsesClass(PostgreSqlTypeMapper::class)]
35#[UsesClass(SqliteSchemaParser::class)]
36#[UsesClass(SqliteTypeMapper::class)]
37#[CoversClass(\SqlFixture\Provider\SqlSchemaProvider::class)]
38#[UsesClass(\SqlFixture\Fixture\FixtureSet::class)]
39#[UsesClass(\SqlFixture\Fixture\GenerationRun::class)]
40#[UsesClass(\SqlFixture\Fixture\OverrideRows::class)]
41#[UsesClass(\SqlFixture\Fixture\PlanGenerator::class)]
42#[UsesClass(\SqlFixture\Fixture\PlanSchemaException::class)]
43#[UsesClass(\SqlFixture\Fixture\PlanSchemaValidator::class)]
44#[UsesClass(\SqlFixture\Fixture\RowSpec::class)]
45#[UsesClass(\SqlFixture\Fixture\TableOverrides::class)]
46#[UsesClass(\SqlFixture\Hydrator\HydrationException::class)]
47#[UsesClass(\SqlFixture\Hydrator\HydratorInterface::class)]
48#[UsesClass(\SqlFixture\Plan\ColumnRef::class)]
49#[UsesClass(\SqlFixture\Plan\FixturePlan::class)]
50#[UsesClass(\SqlFixture\Plan\PlanParser::class)]
51#[UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
52#[UsesClass(\SqlFixture\Plan\PlanSyntaxException::class)]
53#[UsesClass(\SqlFixture\Plan\Relation::class)]
54#[UsesClass(\SqlFixture\Plan\RelationKind::class)]
55#[UsesClass(\SqlFixture\Plan\RelationSide::class)]
56#[UsesClass(\SqlFixture\Platform\MySql\MySqlSchemaFetcher::class)]
57#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaFetcher::class)]
58#[UsesClass(\SqlFixture\Platform\PostgreSql\PostgreSqlSchemaParser::class)]
59#[UsesClass(\SqlFixture\Platform\Sqlite\SqliteSchemaFetcher::class)]
60#[UsesClass(\SqlFixture\Schema\SchemaFetcherInterface::class)]
61#[UsesClass(\SqlFixture\Schema\SchemaNotFoundException::class)]
62#[UsesClass(\SqlFixture\Schema\SchemaParseException::class)]
63#[UsesClass(\SqlFixture\Schema\SchemaParserInterface::class)]
64#[UsesClass(\SqlFixture\Schema\SchemaResolverInterface::class)]
65#[UsesClass(\SqlFixture\Schema\TableIdentifier::class)]
66#[UsesClass(\SqlFixture\TypeMapper\TypeMapperInterface::class)]
67#[UsesClass(\SqlFixture\Fixture\Generation\ConnectedTables::class)]
68#[UsesClass(\SqlFixture\Fixture\Generation\OverrideSpecs::class)]
69#[UsesClass(\SqlFixture\Fixture\Generation\RelationCounts::class)]
70#[UsesClass(\SqlFixture\Fixture\Generation\RelationProjection::class)]
71#[UsesClass(\SqlFixture\Fixture\Generation\RowMaterializer::class)]
72#[UsesClass(\SqlFixture\Fixture\RowGeneration::class)]
73#[UsesClass(\SqlFixture\Fixture\Validation\EndpointValidator::class)]
74#[UsesClass(\SqlFixture\Fixture\Validation\OverrideValidator::class)]
75#[UsesClass(\SqlFixture\Hydrator\Reflection\ConstructorHydration::class)]
76#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyHydration::class)]
77#[UsesClass(\SqlFixture\Hydrator\Reflection\PropertyNames::class)]
78#[UsesClass(\SqlFixture\Hydrator\Reflection\ValueConversion::class)]
79#[UsesClass(\SqlFixture\InvalidOverrideException::class)]
80#[UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
81#[UsesClass(\SqlFixture\Plan\Parsing\RelationCursor::class)]
82#[UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
83#[UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
84#[UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
85#[UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
86#[UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
87#[UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
88#[UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
89#[UsesClass(\SqlFixture\Platform\MySql\Schema\ColumnParser::class)]
90#[UsesClass(\SqlFixture\Platform\MySql\Schema\CreateTableQuery::class)]
91#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefaultExpression::class)]
92#[UsesClass(\SqlFixture\Platform\MySql\Schema\DefinitionIntegrity::class)]
93#[UsesClass(\SqlFixture\Platform\MySql\Schema\IdentifierQuoter::class)]
94#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinition::class)]
95#[UsesClass(\SqlFixture\Platform\MySql\Schema\TypeParameters::class)]
96#[UsesClass(\SqlFixture\Platform\MySql\Value\ColumnGenerator::class)]
97#[UsesClass(\SqlFixture\Platform\MySql\Value\DecimalGenerator::class)]
98#[UsesClass(\SqlFixture\Platform\MySql\Value\GeometryGenerator::class)]
99#[UsesClass(\SqlFixture\Platform\MySql\Value\IntegerGenerator::class)]
100#[UsesClass(\SqlFixture\Platform\MySql\Value\NumericGenerator::class)]
101#[UsesClass(\SqlFixture\Platform\MySql\Value\SpatialGenerator::class)]
102#[UsesClass(\SqlFixture\Platform\MySql\Value\StringGenerator::class)]
103#[UsesClass(\SqlFixture\Platform\MySql\Value\TextGenerator::class)]
104#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogColumn::class)]
105#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogDdl::class)]
106#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogQuery::class)]
107#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\CatalogSchema::class)]
108#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\ColumnParser::class)]
109#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefaultExpression::class)]
110#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\DefinitionList::class)]
111#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TableSyntax::class)]
112#[UsesClass(\SqlFixture\Platform\PostgreSql\Schema\TypeDeclaration::class)]
113#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\ColumnGenerator::class)]
114#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\DecimalGenerator::class)]
115#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\NumericGenerator::class)]
116#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StringGenerator::class)]
117#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\StructuredGenerator::class)]
118#[UsesClass(\SqlFixture\Platform\PostgreSql\Value\TemporalGenerator::class)]
119#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\ColumnParser::class)]
120#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\CreateTableQuery::class)]
121#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefaultExpression::class)]
122#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\DefinitionList::class)]
123#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaColumn::class)]
124#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\PragmaSchema::class)]
125#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TableSyntax::class)]
126#[UsesClass(\SqlFixture\Platform\Sqlite\Schema\TypeDeclaration::class)]
127#[UsesClass(\SqlFixture\Platform\Sqlite\Value\ColumnGenerator::class)]
128#[UsesClass(\SqlFixture\Platform\Sqlite\Value\TypeAffinity::class)]
129#[UsesClass(\SqlFixture\Schema\DefinitionSegments::class)]
130#[UsesClass(\SqlFixture\Schema\TypeShape::class)]
131#[UsesClass(\SqlFixture\TypeMapper\ParagraphGenerator::class)]
132#[UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinitionInput::class)]
133#[UsesClass(\SqlFixture\Hydrator\Exception\ClassNotFoundException::class)]
134#[UsesClass(\SqlFixture\Hydrator\Exception\MissingConstructorArgumentException::class)]
135#[UsesClass(\SqlFixture\Fixture\Exception\UnknownOverrideColumnException::class)]
136#[UsesClass(\SqlFixture\Fixture\Exception\NullOverrideException::class)]
137#[UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnOverrideException::class)]
138#[UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnReferenceException::class)]
139#[UsesClass(\SqlFixture\Fixture\Exception\MissingRelationValueException::class)]
140#[UsesClass(\SqlFixture\Fixture\Exception\UnknownPlanColumnException::class)]
141#[UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
142#[UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
143#[UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
144#[UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
145#[UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
146#[UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
147#[UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
148#[UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
149#[UsesClass(\SqlFixture\Plan\Exception\DuplicateColumnBindingException::class)]
150#[UsesClass(\SqlFixture\Plan\Exception\CyclicDependencyException::class)]
151#[UsesClass(\SqlFixture\Plan\Exception\UnboundedSelfReferenceException::class)]
152#[UsesClass(\SqlFixture\Schema\Exception\InvalidSqlException::class)]
153#[UsesClass(\SqlFixture\Schema\Exception\ExpectedCreateTableException::class)]
154#[UsesClass(\SqlFixture\Schema\Exception\MissingColumnDefinitionsException::class)]
155#[UsesClass(\SqlFixture\TypeMapper\IntegerRange::class)]
156#[UsesClass(\SqlFixture\TypeMapper\IntegerWidth::class)]
157#[UsesClass(\SqlFixture\TypeMapper\DecimalRange::class)]
158#[UsesClass(\SqlFixture\Hydrator\Reflection\ConversionTarget::class)]
159final class FixtureProviderTest extends TestCase
160{
161 #[Test]
162 public function testFixtureReturnsArray(): void
163 {
164 $faker = Factory::create();
165 $faker->seed(12345);
166 $data = (new FixtureProvider($faker))->fixture(
167 'CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255) NOT NULL)',
168 );
169
170 self::assertArrayHasKey('name', $data);
171 self::assertIsString($data['name']);
172 }
173
174 #[Test]
175 public function testFixtureWithOverrides(): void
176 {
177 $faker = Factory::create();
178 $faker->seed(12345);
179 $data = (new FixtureProvider($faker))->fixture(
180 'CREATE TABLE users (id INT, name VARCHAR(255))',
181 ['name' => 'Overridden'],
182 );
183
184 self::assertSame('Overridden', $data['name']);
185 }
186
187 #[Test]
188 public function testFixtureHydratesClass(): void
189 {
190 $target = new class (0, '') {
191 public function __construct(
192 public readonly int $id,
193 public readonly string $name,
194 ) {
195 }
196 };
197 $faker = Factory::create();
198 $faker->seed(12345);
199 $user = (new FixtureProvider($faker))->fixture(
200 'CREATE TABLE users (id INT, name VARCHAR(255))',
201 ['id' => 1, 'name' => 'Test User'],
202 $target::class,
203 );
204
205 self::assertSame(1, $user->id);
206 self::assertSame('Test User', $user->name);
207 }
208
209 #[Test]
210 public function testFixtureSkipsAutoIncrement(): void
211 {
212 $faker = Factory::create();
213 $faker->seed(12345);
214 $data = (new FixtureProvider($faker))->fixture(
215 'CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))',
216 );
217
218 self::assertArrayNotHasKey('id', $data);
219 self::assertArrayHasKey('name', $data);
220 }
221
222 #[Test]
223 public function testFixtureCanOverrideAutoIncrement(): void
224 {
225 $faker = Factory::create();
226 $faker->seed(12345);
227 $data = (new FixtureProvider($faker))->fixture(
228 'CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255))',
229 ['id' => 42],
230 );
231
232 self::assertSame(42, $data['id']);
233 }
234
235 #[Test]
236 public function testFixtureWithAllNumericTypes(): void
237 {
238 $faker = Factory::create();
239 $faker->seed(12345);
240 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
241 CREATE TABLE numbers (
242 col_tinyint TINYINT NOT NULL,
243 col_smallint SMALLINT NOT NULL,
244 col_mediumint MEDIUMINT NOT NULL,
245 col_int INT NOT NULL,
246 col_bigint BIGINT NOT NULL,
247 col_float FLOAT NOT NULL,
248 col_double DOUBLE NOT NULL,
249 col_decimal DECIMAL(10,2) NOT NULL
250 )
251 SQL);
252
253 self::assertIsInt($data['col_tinyint']);
254 self::assertIsInt($data['col_smallint']);
255 self::assertIsInt($data['col_mediumint']);
256 self::assertIsInt($data['col_int']);
257 self::assertIsInt($data['col_bigint']);
258 self::assertIsFloat($data['col_float']);
259 self::assertIsFloat($data['col_double']);
260 self::assertIsFloat($data['col_decimal']);
261 }
262
263 #[Test]
264 public function testFixtureWithStringTypes(): void
265 {
266 $faker = Factory::create();
267 $faker->seed(12345);
268 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
269 CREATE TABLE strings (
270 col_char CHAR(10) NOT NULL,
271 col_varchar VARCHAR(100) NOT NULL,
272 col_text TEXT NOT NULL,
273 col_mediumtext MEDIUMTEXT NOT NULL
274 )
275 SQL);
276
277 self::assertIsString($data['col_char']);
278 self::assertSame(10, strlen($data['col_char']));
279 self::assertIsString($data['col_varchar']);
280 self::assertLessThanOrEqual(100, strlen($data['col_varchar']));
281 self::assertIsString($data['col_text']);
282 self::assertIsString($data['col_mediumtext']);
283 }
284
285 #[Test]
286 public function testFixtureWithDateTypes(): void
287 {
288 $faker = Factory::create();
289 $faker->seed(12345);
290 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
291 CREATE TABLE dates (
292 col_date DATE NOT NULL,
293 col_time TIME NOT NULL,
294 col_datetime DATETIME NOT NULL,
295 col_timestamp TIMESTAMP NOT NULL,
296 col_year YEAR NOT NULL
297 )
298 SQL);
299
300 self::assertIsString($data['col_date']);
301 self::assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}$/', $data['col_date']);
302 self::assertIsString($data['col_time']);
303 self::assertMatchesRegularExpression('/^\d{2}:\d{2}:\d{2}$/', $data['col_time']);
304 self::assertIsString($data['col_datetime']);
305 self::assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', $data['col_datetime']);
306 self::assertIsInt($data['col_year']);
307 }
308
309 #[Test]
310 public function testFixtureWithEnum(): void
311 {
312 $faker = Factory::create();
313 $faker->seed(12345);
314 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
315 CREATE TABLE statuses (
316 status ENUM('active','inactive','pending') NOT NULL
317 )
318 SQL);
319
320 self::assertContains($data['status'], ['active', 'inactive', 'pending']);
321 }
322
323 #[Test]
324 public function testFixtureWithSet(): void
325 {
326 $faker = Factory::create();
327 $faker->seed(12345);
328 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
329 CREATE TABLE permissions (
330 perms SET('read','write','delete') NOT NULL
331 )
332 SQL);
333
334 self::assertIsString($data['perms']);
335 $parts = explode(',', $data['perms']);
336 array_walk($parts, static function (string $part): void {
337 self::assertContains($part, ['read', 'write', 'delete']);
338 });
339 }
340
341 #[Test]
342 public function testFixtureWithJson(): void
343 {
344 $faker = Factory::create();
345 $faker->seed(12345);
346 $data = (new FixtureProvider($faker))->fixture('CREATE TABLE jsons (data JSON NOT NULL)');
347
348 self::assertIsString($data['data']);
349 $decoded = json_decode($data['data'], true);
350 self::assertIsArray($decoded);
351 }
352
353 #[Test]
354 public function testFixtureWithSpatialTypes(): void
355 {
356 $faker = Factory::create();
357 $faker->seed(12345);
358 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
359 CREATE TABLE geo (
360 col_point POINT NOT NULL,
361 col_linestring LINESTRING NOT NULL,
362 col_polygon POLYGON NOT NULL
363 )
364 SQL);
365
366 self::assertIsString($data['col_point']);
367 self::assertStringStartsWith('POINT(', $data['col_point']);
368 self::assertIsString($data['col_linestring']);
369 self::assertStringStartsWith('LINESTRING(', $data['col_linestring']);
370 self::assertIsString($data['col_polygon']);
371 self::assertStringStartsWith('POLYGON((', $data['col_polygon']);
372 }
373
374 #[Test]
375 public function testFixtureResultIsReproducibleWithSeed(): void
376 {
377 $faker1 = Factory::create();
378 $faker1->seed(99999);
379 $data1 = (new FixtureProvider($faker1))->fixture('CREATE TABLE test (name VARCHAR(255))');
380 $faker2 = Factory::create();
381 $faker2->seed(99999);
382 $data2 = (new FixtureProvider($faker2))->fixture('CREATE TABLE test (name VARCHAR(255))');
383
384 self::assertSame($data1['name'], $data2['name']);
385 }
386
387 #[Test]
388 public function testFixtureCachesSchema(): void
389 {
390 $faker = Factory::create();
391 $faker->seed(12345);
392 $provider = new FixtureProvider($faker);
393 $sql = 'CREATE TABLE cache_test (id INT, name VARCHAR(255))';
394
395 $data1 = $provider->fixture($sql, ['name' => 'First']);
396 $data2 = $provider->fixture($sql, ['name' => 'Second']);
397
398 self::assertSame('First', $data1['name']);
399 self::assertSame('Second', $data2['name']);
400 }
401
402 #[Test]
403 public function testGetFixtureGenerator(): void
404 {
405 $faker = Factory::create();
406 $faker->seed(12345);
407 $generator = (new FixtureProvider($faker))->getFixtureGenerator();
408 self::assertSame(['id' => 7], $generator->generate(new TableSchema('sample', ['id' => new ColumnDefinition('id', 'INT')]), ['id' => 7]));
409 }
410
411 #[Test]
412 public function testGetDialectDefaultsToMysql(): void
413 {
414 $faker = Factory::create();
415 $faker->seed(12345);
416 self::assertSame('mysql', (new FixtureProvider($faker))->getDialect());
417 }
418
419 #[Test]
420 #[\PHPUnit\Framework\Attributes\DataProvider('providerNullableSeeds')]
421 public function testFixtureWithNullableColumns(int $seed): void
422 {
423 $faker = Factory::create();
424 $faker->seed($seed);
425 $provider = new FixtureProvider($faker);
426 $sql = 'CREATE TABLE test (id INT NOT NULL, name VARCHAR(255) DEFAULT NULL, notes TEXT)';
427
428 $data = $provider->fixture($sql);
429 self::assertArrayHasKey('id', $data);
430 self::assertIsInt($data['id']);
431 }
432
433 #[Test]
434 public function testFixtureWithBinaryColumns(): void
435 {
436 $faker = Factory::create();
437 $faker->seed(12345);
438 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
439 CREATE TABLE bins (
440 col_binary BINARY(16) NOT NULL,
441 col_varbinary VARBINARY(100) NOT NULL,
442 col_blob BLOB NOT NULL
443 )
444 SQL);
445
446 self::assertIsString($data['col_binary']);
447 self::assertSame(16, strlen($data['col_binary']));
448 self::assertIsString($data['col_varbinary']);
449 self::assertLessThanOrEqual(100, strlen($data['col_varbinary']));
450 self::assertIsString($data['col_blob']);
451 }
452
453 #[Test]
454 public function testFixtureWithBooleanType(): void
455 {
456 $faker = Factory::create();
457 $faker->seed(12345);
458 $data = (new FixtureProvider($faker))->fixture('CREATE TABLE test (active BOOLEAN NOT NULL)');
459 self::assertIsBool($data['active']);
460 }
461
462 #[Test]
463 public function testFixtureWithBitType(): void
464 {
465 $faker = Factory::create();
466 $faker->seed(12345);
467 $data = (new FixtureProvider($faker))->fixture('CREATE TABLE test (flags BIT(8) NOT NULL)');
468 self::assertIsInt($data['flags']);
469 self::assertGreaterThanOrEqual(0, $data['flags']);
470 self::assertLessThanOrEqual(255, $data['flags']);
471 }
472
473 #[Test]
474 public function testFixtureWithGeneratedColumnSkipped(): void
475 {
476 $faker = Factory::create();
477 $faker->seed(12345);
478 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
479 CREATE TABLE test (
480 a INT,
481 b INT,
482 c INT GENERATED ALWAYS AS (a + b) STORED
483 )
484 SQL);
485
486 self::assertArrayHasKey('a', $data);
487 self::assertArrayHasKey('b', $data);
488 self::assertArrayNotHasKey('c', $data);
489 }
490
491 #[Test]
492 public function testFixtureWithUnsignedTypes(): void
493 {
494 $faker = Factory::create();
495 $faker->seed(12345);
496 $data = (new FixtureProvider($faker))->fixture(<<<'SQL'
497 CREATE TABLE test (
498 col_uint INT UNSIGNED NOT NULL,
499 col_utinyint TINYINT UNSIGNED NOT NULL
500 )
501 SQL);
502
503 self::assertIsInt($data['col_uint']);
504 self::assertGreaterThanOrEqual(0, $data['col_uint']);
505 self::assertIsInt($data['col_utinyint']);
506 self::assertGreaterThanOrEqual(0, $data['col_utinyint']);
507 self::assertLessThanOrEqual(255, $data['col_utinyint']);
508 }
509
510 #[Test]
511 public function testFixtureWithDialectOverride(): void
512 {
513 $faker = Factory::create();
514 $faker->seed(12345);
515 $provider = new FixtureProvider($faker, dialect: 'mysql');
516
517 $data = $provider->fixture(
518 'CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)',
519 [],
520 null,
521 'sqlite',
522 );
523
524 self::assertArrayHasKey('name', $data);
525 }
526
527 #[Test]
528 public function testFixtureWithSqliteDialect(): void
529 {
530 $faker = Factory::create();
531 $faker->seed(12345);
532 $provider = new FixtureProvider($faker, dialect: 'sqlite');
533
534 self::assertSame('sqlite', $provider->getDialect());
535
536 $data = $provider->fixture(
537 'CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)',
538 );
539
540 self::assertArrayHasKey('name', $data);
541 }
542
543 #[Test]
544 public function testFixtureWithCustomTypeMapper(): void
545 {
546 $faker = Factory::create();
547 $faker->seed(12345);
548 $typeMapper = new MySqlTypeMapper();
549 $provider = new FixtureProvider($faker, typeMapper: $typeMapper);
550
551 $data = $provider->fixture('CREATE TABLE test (id INT, name VARCHAR(255))');
552
553 self::assertArrayHasKey('id', $data);
554 }
555
556 #[Test]
557 public function testFixtureWithCustomSchemaParser(): void
558 {
559 $faker = Factory::create();
560 $faker->seed(12345);
561 $parser = new MySqlSchemaParser();
562 $provider = new FixtureProvider($faker, schemaParser: $parser);
563
564 $data = $provider->fixture('CREATE TABLE test (id INT, name VARCHAR(255))');
565
566 self::assertArrayHasKey('id', $data);
567 }
568
569 #[Test]
570 public function testSchemaCacheUsesDialectInKey(): void
571 {
572 $faker = Factory::create();
573 $faker->seed(12345);
574 $provider = new FixtureProvider($faker, dialect: 'mysql');
575
576 $sql = 'CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)';
577 $data1 = $provider->fixture($sql, [], null, 'sqlite');
578 $data2 = $provider->fixture($sql, [], null, 'sqlite');
579
580 self::assertArrayHasKey('name', $data1);
581 self::assertArrayHasKey('name', $data2);
582 }
583
584 #[Test]
585 public function testSchemaCacheDistinguishesDifferentDialects(): void
586 {
587 $faker = Factory::create();
588 $faker->seed(12345);
589 $provider = new FixtureProvider($faker, dialect: 'mysql');
590
591 $sql = 'CREATE TABLE test (id INTEGER NOT NULL, name TEXT NOT NULL)';
592
593 $faker->seed(12345);
594 $dataMysql = $provider->fixture($sql);
595
596 $faker->seed(12345);
597 $dataSqlite = $provider->fixture($sql, [], null, 'sqlite');
598
599 self::assertArrayHasKey('name', $dataMysql);
600 self::assertArrayHasKey('name', $dataSqlite);
601 }
602
603 #[Test]
604 public function testDefaultDialectIsUsedWhenNullDialectPassed(): void
605 {
606 $faker = Factory::create();
607 $faker->seed(12345);
608 $provider = new FixtureProvider($faker, dialect: 'sqlite');
609
610 $data = $provider->fixture(
611 'CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT NOT NULL)',
612 [],
613 null,
614 null,
615 );
616
617 self::assertArrayHasKey('name', $data);
618 self::assertSame('sqlite', $provider->getDialect());
619 }
620
621 #[Test]
622 public function testFixtureSameDialectUsesInternalParser(): void
623 {
624 $faker = Factory::create();
625 $faker->seed(12345);
626 $provider = new FixtureProvider($faker, dialect: 'mysql');
627
628 $data = $provider->fixture(
629 'CREATE TABLE test (id INT, name VARCHAR(255))',
630 [],
631 null,
632 'mysql',
633 );
634
635 self::assertArrayHasKey('name', $data);
636 }
637
638 #[Test]
639 public function testCacheDistinguishesDifferentSqlSameDialect(): void
640 {
641 $faker = Factory::create();
642 $faker->seed(12345);
643 $provider = new FixtureProvider($faker, dialect: 'mysql');
644
645 $data1 = $provider->fixture(
646 'CREATE TABLE test1 (id INT NOT NULL, name VARCHAR(255) NOT NULL)',
647 );
648 $data2 = $provider->fixture(
649 'CREATE TABLE test2 (age INT NOT NULL, email VARCHAR(100) NOT NULL)',
650 );
651
652 self::assertArrayHasKey('name', $data1);
653 self::assertArrayNotHasKey('email', $data1);
654 self::assertArrayHasKey('email', $data2);
655 self::assertArrayNotHasKey('name', $data2);
656 }
657
658 #[Test]
659 public function testCacheKeyIncludesDialect(): void
660 {
661 $faker = Factory::create();
662 $faker->seed(12345);
663 $provider = new FixtureProvider($faker, dialect: 'mysql');
664
665 $sql = 'CREATE TABLE test (id INTEGER NOT NULL, name TEXT NOT NULL)';
666
667 $dataMysql = $provider->fixture($sql);
668 $dataSqlite = $provider->fixture($sql, [], null, 'sqlite');
669
670 self::assertArrayHasKey('id', $dataMysql);
671 self::assertArrayHasKey('id', $dataSqlite);
672 self::assertArrayHasKey('name', $dataMysql);
673 self::assertArrayHasKey('name', $dataSqlite);
674 }
675
676 #[Test]
677 public function testDialectOverrideUsesOverrideNotDefault(): void
678 {
679 $faker = Factory::create();
680 $faker->seed(12345);
681 $provider = new FixtureProvider($faker, dialect: 'mysql');
682
683 $sql = 'CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)';
684
685 $data = $provider->fixture($sql, [], null, 'sqlite');
686
687 self::assertArrayNotHasKey('id', $data);
688 self::assertArrayHasKey('name', $data);
689 }
690
691 #[Test]
692 public function testCachedSchemaDistinguishesDialects(): void
693 {
694 $faker = Factory::create();
695 $faker->seed(12345);
696 $provider = new FixtureProvider($faker, dialect: 'mysql');
697
698 $sql = 'CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)';
699
700 $provider->fixture($sql, ['name' => 'first'], null, 'sqlite');
701
702 $data2 = $provider->fixture($sql, ['name' => 'second'], null, 'sqlite');
703
704 self::assertArrayNotHasKey('id', $data2);
705 self::assertSame('second', $data2['name']);
706 }
707
708 #[Test]
709 public function testCacheSeparatesDefaultAndOverrideDialect(): void
710 {
711 $faker = Factory::create();
712 $faker->seed(12345);
713 $provider = new FixtureProvider($faker, dialect: 'sqlite');
714
715 $sql = 'CREATE TABLE test (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL)';
716
717 $dataSqlite = $provider->fixture($sql);
718 self::assertArrayHasKey('id', $dataSqlite);
719
720 $dataMysql = $provider->fixture($sql, [], null, 'mysql');
721 self::assertArrayNotHasKey('id', $dataMysql);
722 }
723
724 #[Test]
725 public function testCustomTypeMapperIsPreserved(): void
726 {
727 $faker = Factory::create();
728 $faker->seed(12345);
729 $provider = new FixtureProvider($faker, typeMapper: new PostgreSqlTypeMapper());
730
731 $data = $provider->fixture('CREATE TABLE test (val YEAR NOT NULL)');
732
733 self::assertIsString($data['val']);
734 }
735
736 #[Test]
737 public function testCustomSchemaParserIsPreserved(): void
738 {
739 $faker = Factory::create();
740 $faker->seed(12345);
741 $parser = new SqliteSchemaParser();
742 $provider = new FixtureProvider($faker, schemaParser: $parser);
743
744 $data = $provider->fixture('CREATE TABLE test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL)');
745
746 self::assertArrayNotHasKey('id', $data);
747 self::assertArrayHasKey('name', $data);
748 }
749
750 #[Test]
751 public function testRegisterSchemaReturnsTheParsedTable(): void
752 {
753 $faker = Factory::create();
754 $faker->seed(12345);
755 $provider = new FixtureProvider($faker);
756
757 $schema = $provider->registerSchema('CREATE TABLE test (id INT NOT NULL, name VARCHAR(255) NOT NULL)');
758
759 self::assertSame('test', $schema->tableName);
760 self::assertCount(2, $schema->columns);
761 }
762
763 #[Test]
764 public function testRegisterSchemaARegisteredSchemaBecomesAvailableToFixtures(): void
765 {
766 $faker = Factory::create();
767 $faker->seed(12345);
768 $provider = new FixtureProvider($faker);
769 $provider->registerSchema('CREATE TABLE orders (id INT AUTO_INCREMENT PRIMARY KEY, status VARCHAR(20) NOT NULL)');
770
771 self::assertTrue($provider->getSchemaResolver()->has('orders'));
772 self::assertSame('orders', $provider->getSchemaResolver()->resolve('orders')->tableName);
773 }
774
775 #[Test]
776 public function testGetSchemaResolverGeneratingAFixtureAlsoMakesItAvailableToFixtures(): void
777 {
778 $faker = Factory::create();
779 $faker->seed(12345);
780 $provider = new FixtureProvider($faker);
781 $provider->fixture('CREATE TABLE orders (id INT AUTO_INCREMENT PRIMARY KEY, status VARCHAR(20) NOT NULL)');
782
783 self::assertTrue($provider->getSchemaResolver()->has('orders'));
784 }
785 public function testFixturesGeneratesLinkedRowsFromRegisteredSchemas(): void
786 {
787 $provider = new FixtureProvider(Factory::create());
788 $provider->registerSchema('CREATE TABLE users (id INT PRIMARY KEY)');
789 $provider->registerSchema('CREATE TABLE posts (user_id INT)');
790 $fixtures = $provider->fixtures('users.id < posts.user_id', ['users' => ['id' => 9], 'posts' => 2]);
791 self::assertSame([['user_id' => 9], ['user_id' => 9]], $fixtures->rows('posts'));
792 }
793 /**
794 * @return list<array{int}>
795 */
796 public static function providerNullableSeeds(): array
797 {
798 return array_map(static fn (int $seed): array => [$seed], range(1, 20));
799 }
800}
801