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