packages/sql-fixture/tests/Unit/Fixture/Generation/RowMaterializerTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Fixture\Generation;
6
7use Faker\Factory;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\TestCase;
10use SqlFixture\Fixture\Generation\RowMaterializer as Subject;
11use SqlFixture\Fixture\GenerationRun;
12use SqlFixture\Fixture\RowSpec;
13use SqlFixture\FixtureGenerator;
14use SqlFixture\Plan\FixturePlan;
15use SqlFixture\Schema\ColumnDefinition;
16use SqlFixture\Schema\StaticSchemaResolver;
17use SqlFixture\Schema\TableSchema;
18
19#[CoversClass(Subject::class)]
20#[\PHPUnit\Framework\Attributes\UsesClass(FixtureGenerator::class)]
21#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\FixtureSet::class)]
22#[\PHPUnit\Framework\Attributes\UsesClass(GenerationRun::class)]
23#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Generation\RelationCounts::class)]
24#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Generation\RelationProjection::class)]
25#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\OverrideRows::class)]
26#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\PlanSchemaException::class)]
27#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\RowGeneration::class)]
28#[\PHPUnit\Framework\Attributes\UsesClass(RowSpec::class)]
29#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\TableOverrides::class)]
30#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\HydrationException::class)]
31#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\HydratorInterface::class)]
32#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\ReflectionHydrator::class)]
33#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\ColumnRef::class)]
34#[\PHPUnit\Framework\Attributes\UsesClass(FixturePlan::class)]
35#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\PlanParser::class)]
36#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\PlanPrinter::class)]
37#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\PlanSyntaxException::class)]
38#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Relation::class)]
39#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\RelationKind::class)]
40#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\RelationSide::class)]
41#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\MySqlSchemaParser::class)]
42#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\MySqlTypeMapper::class)]
43#[\PHPUnit\Framework\Attributes\UsesClass(ColumnDefinition::class)]
44#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\SchemaNotFoundException::class)]
45#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\SchemaParseException::class)]
46#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\SchemaParserInterface::class)]
47#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\SchemaResolverInterface::class)]
48#[\PHPUnit\Framework\Attributes\UsesClass(StaticSchemaResolver::class)]
49#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\TableIdentifier::class)]
50#[\PHPUnit\Framework\Attributes\UsesClass(TableSchema::class)]
51#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\TypeMapper\TypeMapperInterface::class)]
52#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Validation\OverrideValidator::class)]
53#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Reflection\ConstructorHydration::class)]
54#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Reflection\PropertyHydration::class)]
55#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Reflection\PropertyNames::class)]
56#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Reflection\ValueConversion::class)]
57#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\InvalidOverrideException::class)]
58#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Parsing\PlanStatements::class)]
59#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Parsing\RelationCursor::class)]
60#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Parsing\RelationReader::class)]
61#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\PlanStructureException::class)]
62#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Printing\PlanTables::class)]
63#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Printing\RelationGroups::class)]
64#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Printing\StatementPrinter::class)]
65#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Validation\PlanValidation::class)]
66#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Validation\TableName::class)]
67#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\ColumnParser::class)]
68#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\DefaultExpression::class)]
69#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\DefinitionIntegrity::class)]
70#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinition::class)]
71#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\TypeParameters::class)]
72#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\ColumnGenerator::class)]
73#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\DecimalGenerator::class)]
74#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\GeometryGenerator::class)]
75#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\IntegerGenerator::class)]
76#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\NumericGenerator::class)]
77#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\SpatialGenerator::class)]
78#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\StringGenerator::class)]
79#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Value\TextGenerator::class)]
80#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\TypeShape::class)]
81#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\TypeMapper\ParagraphGenerator::class)]
82#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Platform\MySql\Schema\TableDefinitionInput::class)]
83#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Exception\ClassNotFoundException::class)]
84#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Exception\MissingConstructorArgumentException::class)]
85#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\UnknownOverrideColumnException::class)]
86#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\NullOverrideException::class)]
87#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnOverrideException::class)]
88#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\GeneratedColumnReferenceException::class)]
89#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\MissingRelationValueException::class)]
90#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Exception\UnknownPlanColumnException::class)]
91#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\EmptyPlanException::class)]
92#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\EmptyTableNameException::class)]
93#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\MissingEndpointColumnsException::class)]
94#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\InvalidTableNameException::class)]
95#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\UnbalancedBracketsException::class)]
96#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\UnexpectedPlanTokenException::class)]
97#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\UnsupportedManyToManyException::class)]
98#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\CompositeArityMismatchException::class)]
99#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\DuplicateColumnBindingException::class)]
100#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\CyclicDependencyException::class)]
101#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Exception\UnboundedSelfReferenceException::class)]
102#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\Exception\InvalidSqlException::class)]
103#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\Exception\ExpectedCreateTableException::class)]
104#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Schema\Exception\MissingColumnDefinitionsException::class)]
105#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\TypeMapper\IntegerRange::class)]
106#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\TypeMapper\IntegerWidth::class)]
107#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\TypeMapper\DecimalRange::class)]
108#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Hydrator\Reflection\ConversionTarget::class)]
109final class RowMaterializerTest extends TestCase
110{
111    public function testMaterializeLinksEveryChildToTheGeneratedParent(): void
112    {
113        $faker = Factory::create();
114        $schemas = new StaticSchemaResolver([
115            new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
116            new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
117        ]);
118        $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
119        $plan = FixturePlan::from('a.id < b.a_id');
120        $relation = $plan->relations[0];
121        $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
122        $materializer->materialize($plan, 'a', [], 1, false, null, $run);
123        self::assertSame(['id' => 1], $run->toSet($plan)->row('a'));
124        self::assertSame([['a_id' => 1], ['a_id' => 1]], $run->toSet($plan)->rows('b'));
125    }
126
127    public function testMaterializeParentDoesNotWalkBackAlongTheArrivalRelation(): void
128    {
129        $faker = Factory::create();
130        $schemas = new StaticSchemaResolver([
131            new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
132            new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
133        ]);
134        $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
135        $plan = FixturePlan::from('a.id < b.a_id');
136        $relation = $plan->relations[0];
137        $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
138        self::assertSame(['id' => 1], $materializer->materializeParent($plan, 'a', false, $relation, $run));
139        self::assertSame([], $run->toSet($plan)->rows('b'));
140    }
141
142    public function testMaterializeRowPreservesExplicitKeys(): void
143    {
144        $faker = Factory::create();
145        $schemas = new StaticSchemaResolver([
146            new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
147            new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
148        ]);
149        $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
150        $plan = FixturePlan::from('a.id < b.a_id');
151        $relation = $plan->relations[0];
152        $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
153        $materializer->materializeRow($plan, $schemas->resolve('a'), [], RowSpec::from('a', ['id' => 9]), 0, false, null, $run);
154        self::assertSame(['id' => 9], $run->lastRow('a'));
155        self::assertSame(['a_id' => 9], $run->lastRow('b'));
156    }
157
158    public function testToParentProjectsGeneratedKeys(): void
159    {
160        $faker = Factory::create();
161        $schemas = new StaticSchemaResolver([
162            new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
163            new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
164        ]);
165        $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
166        $plan = FixturePlan::from('a.id < b.a_id');
167        $relation = $plan->relations[0];
168        $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
169        self::assertSame(['a_id' => 1], $materializer->toParent($plan, $relation, [], false, $run));
170        self::assertSame(['id' => 1], $run->lastRow('a'));
171    }
172
173    public function testToChildrenCopiesTheExistingParentKey(): void
174    {
175        $faker = Factory::create();
176        $schemas = new StaticSchemaResolver([
177            new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
178            new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
179        ]);
180        $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
181        $plan = FixturePlan::from('a.id < b.a_id');
182        $relation = $plan->relations[0];
183        $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
184        $materializer->toChildren($plan, $relation, ['id' => 9], false, $run);
185        self::assertSame([['a_id' => 9], ['a_id' => 9]], $run->toSet($plan)->rows('b'));
186    }
187}
188