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\Plan\FixturePlan;
14use SqlFixture\Provider\FixtureGenerator;
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\Fixture\Exception\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)]
109#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Choice\ChoiceValidation::class)]
110#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Plan\Choice\PlanContents::class)]
111#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Choice\RowChoices::class)]
112#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Choice\ResolvedRow::class)]
113#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Choice\InverseRelations::class)]
114#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Generation\RowBindings::class)]
115#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Generation\RelationValueException::class)]
116#[\PHPUnit\Framework\Attributes\UsesClass(\SqlFixture\Fixture\Generation\RecursiveRelationException::class)]
117final class RowMaterializerTest extends TestCase
118{
119 public function testMaterializeLinksEveryChildToTheGeneratedParent(): void
120 {
121 $faker = Factory::create();
122 $schemas = new StaticSchemaResolver([
123 new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
124 new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
125 ]);
126 $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
127 $plan = FixturePlan::from('a.id < b.a_id');
128 $relation = $plan->relations[0];
129 $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
130 $materializer->materialize($plan, 'a', [], 1, false, null, $run);
131 self::assertSame(['id' => 1], $run->toSet($plan)->row('a'));
132 self::assertSame([['a_id' => 1], ['a_id' => 1]], $run->toSet($plan)->rows('b'));
133 }
134
135 public function testMaterializeParentDoesNotWalkBackAlongTheArrivalRelation(): void
136 {
137 $faker = Factory::create();
138 $schemas = new StaticSchemaResolver([
139 new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
140 new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
141 ]);
142 $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
143 $plan = FixturePlan::from('a.id < b.a_id');
144 $relation = $plan->relations[0];
145 $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
146 self::assertSame(['id' => 1], $materializer->materializeParent($plan, 'a', false, $relation, $run));
147 self::assertSame([], $run->toSet($plan)->rows('b'));
148 }
149
150 public function testMaterializeRowPreservesExplicitKeys(): void
151 {
152 $faker = Factory::create();
153 $schemas = new StaticSchemaResolver([
154 new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
155 new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
156 ]);
157 $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
158 $plan = FixturePlan::from('a.id < b.a_id');
159 $relation = $plan->relations[0];
160 $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
161 $materializer->materializeRow($plan, $schemas->resolve('a'), [], RowSpec::from('a', ['id' => 9]), 0, false, null, $run);
162 self::assertSame(['id' => 9], $run->lastRow('a'));
163 self::assertSame(['a_id' => 9], $run->lastRow('b'));
164 }
165
166 public function testToParentProjectsGeneratedKeys(): void
167 {
168 $faker = Factory::create();
169 $schemas = new StaticSchemaResolver([
170 new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
171 new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
172 ]);
173 $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
174 $plan = FixturePlan::from('a.id < b.a_id');
175 $relation = $plan->relations[0];
176 $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
177 self::assertSame(['a_id' => 1], $materializer->toParent($plan, $relation, [], false, $run));
178 self::assertSame(['id' => 1], $run->lastRow('a'));
179 }
180
181 public function testToChildrenCopiesTheExistingParentKey(): void
182 {
183 $faker = Factory::create();
184 $schemas = new StaticSchemaResolver([
185 new TableSchema('a', ['id' => new ColumnDefinition('id', 'INT', autoIncrement: true)]),
186 new TableSchema('b', ['a_id' => new ColumnDefinition('a_id', 'INT')]),
187 ]);
188 $materializer = new Subject($schemas, new FixtureGenerator($faker), $faker);
189 $plan = FixturePlan::from('a.id < b.a_id');
190 $relation = $plan->relations[0];
191 $run = new GenerationRun(['b' => RowSpec::from('b', 2)]);
192 $materializer->toChildren($plan, $relation, ['id' => 9], false, $run);
193 self::assertSame([['a_id' => 9], ['a_id' => 9]], $run->toSet($plan)->rows('b'));
194 }
195
196 public function testMaterializeBoundsAnOptionalSelfRelationAfterAnotherTable(): void
197 {
198 $schemas = new StaticSchemaResolver([
199 new TableSchema('category', [
200 'id' => new ColumnDefinition('id', 'INT', autoIncrement: true),
201 'parent_id' => new ColumnDefinition('parent_id', 'INT'),
202 ]),
203 ]);
204 $faker = Factory::create();
205 $plan = FixturePlan::from('category.id <? category.parent_id');
206 $run = new GenerationRun(['category' => RowSpec::from('category', 1)]);
207 (new Subject($schemas, new FixtureGenerator($faker), $faker))->materialize($plan, 'category', [], 1, false, null, $run, ['unrelated']);
208 self::assertSame([1, 2, 3], array_column($run->toSet($plan)->rows('category'), 'id'));
209 }
210}
211