packages/ztd-query-core/tests/Unit/Shadow/TableTransitionsTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Schema\TableDefinition;
11use ZtdQuery\Schema\TableDefinitionRegistry;
12use ZtdQuery\Shadow\Mutation\MutationRowIdentity;
13use ZtdQuery\Shadow\Mutation\Row\DeleteMutation;
14use ZtdQuery\Shadow\Mutation\Row\UpdateMutation;
15use ZtdQuery\Shadow\Row\RowMatch;
16use ZtdQuery\Shadow\Row\RowPairing;
17use ZtdQuery\Shadow\ShadowStore;
18use ZtdQuery\Shadow\TableTransitions;
19
20#[CoversClass(TableTransitions::class)]
21#[UsesClass(RowMatch::class)]
22#[UsesClass(RowPairing::class)]
23#[UsesClass(MutationRowIdentity::class)]
24#[UsesClass(ShadowStore::class)]
25#[UsesClass(TableDefinition::class)]
26#[UsesClass(TableDefinitionRegistry::class)]
27#[UsesClass(DeleteMutation::class)]
28#[UsesClass(UpdateMutation::class)]
29#[UsesClass(\ZtdQuery\Shadow\Row\TableTransition::class)]
30#[UsesClass(\ZtdQuery\Shadow\Row\RowChange::class)]
31#[UsesClass(\ZtdQuery\Shadow\Mutation\RowConstraints::class)]
32#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
33final class TableTransitionsTest extends TestCase
34{
35 public function testBetweenReadsARowThatIsNoLongerThereAsDeleted(): void
36 {
37 $transition = (new TableTransitions(new TableDefinitionRegistry()))
38 ->between('order', [['id' => 1], ['id' => 2]], [['id' => 1]], ['id'], []);
39
40 self::assertSame([['id' => 2]], $transition->deleted);
41 self::assertSame([], $transition->updated);
42 }
43
44 public function testBetweenReadsARowThatChangedAsUpdated(): void
45 {
46 $transition = (new TableTransitions(new TableDefinitionRegistry()))
47 ->between('order', [['id' => 1, 'total' => 1]], [['id' => 1, 'total' => 2]], ['id'], []);
48
49 self::assertSame([], $transition->deleted);
50 self::assertCount(1, $transition->updated);
51 self::assertSame(['id' => 1, 'total' => 2], $transition->updated[0]->after);
52 }
53
54 public function testBetweenPairsARowByTheOldKeyTheResultCarries(): void
55 {
56 $transition = (new TableTransitions(new TableDefinitionRegistry()))->between(
57 'order',
58 [['id' => 1]],
59 [['id' => 2]],
60 ['id'],
61 [['id' => 2, '__ztd_original_id' => 1]],
62 );
63
64 self::assertSame([], $transition->deleted);
65 self::assertSame(['id' => 1], $transition->updated[0]->before);
66 self::assertSame(['id' => 2], $transition->updated[0]->after);
67 }
68
69 public function testBetweenPairsOnlyIdenticalRowsWhereTheTableDeclaresNoKey(): void
70 {
71 $transition = (new TableTransitions(new TableDefinitionRegistry()))
72 ->between('log', [['note' => 'a'], ['note' => 'b']], [['note' => 'a']], [], []);
73
74 self::assertSame([['note' => 'b']], $transition->deleted);
75 self::assertSame([], $transition->updated);
76 }
77
78 public function testBetweenPairsTwoRowsSharingAKeyOneToOne(): void
79 {
80 $transition = (new TableTransitions(new TableDefinitionRegistry()))
81 ->between('order', [['id' => 1], ['id' => 1]], [['id' => 1]], ['id'], []);
82
83 self::assertSame([['id' => 1]], $transition->deleted);
84 }
85
86 public function testUnmatchedAnswersTheRowsNothingWasPairedWith(): void
87 {
88 $rows = [['id' => 1], ['id' => 2], ['id' => 3]];
89
90 self::assertSame(
91 [['id' => 2]],
92 (new TableTransitions(new TableDefinitionRegistry()))->unmatched($rows, [0, 2]),
93 );
94 }
95
96 public function testOfLeavesOutATableNothingHappenedTo(): void
97 {
98 $registry = new TableDefinitionRegistry();
99 $registry->register('order', new TableDefinition(['id'], [], ['id'], [], []));
100 $before = new ShadowStore();
101 $before->set('order', [['id' => 1]]);
102 $after = $before->snapshot();
103
104 $transitions = (new TableTransitions($registry))
105 ->of($before, $after, new DeleteMutation('order', ['id']), []);
106
107 self::assertSame([], $transitions);
108 }
109
110 public function testOfAnswersOneEntryPerTableSomethingHappenedTo(): void
111 {
112 $registry = new TableDefinitionRegistry();
113 $registry->register('order', new TableDefinition(['id'], [], ['id'], [], []));
114 $before = new ShadowStore();
115 $before->set('order', [['id' => 1]]);
116 $after = $before->snapshot();
117 $after->set('order', []);
118
119 $transitions = (new TableTransitions($registry))
120 ->of($before, $after, new DeleteMutation('order', ['id']), []);
121
122 self::assertCount(1, $transitions);
123 self::assertSame('order', $transitions[0]->table);
124 self::assertSame([['id' => 1]], $transitions[0]->deleted);
125 }
126
127 public function testOfReadsTheResultRowsOnlyForTheTableAnUpdateNames(): void
128 {
129 $registry = new TableDefinitionRegistry();
130 $registry->register('order', new TableDefinition(['id'], [], ['id'], [], []));
131 $before = new ShadowStore();
132 $before->set('order', [['id' => 1]]);
133 $after = $before->snapshot();
134 $after->set('order', [['id' => 2]]);
135
136 $transitions = (new TableTransitions($registry))->of(
137 $before,
138 $after,
139 new UpdateMutation('order', ['id']),
140 [['id' => 2, '__ztd_original_id' => 1]],
141 );
142
143 self::assertSame([], $transitions[0]->deleted);
144 self::assertCount(1, $transitions[0]->updated);
145 }
146
147 public function testPairIdenticalPairsOffOnlyRowsThatAreWhatTheyWere(): void
148 {
149 $transitions = new TableTransitions(new TableDefinitionRegistry());
150 $pairing = new RowPairing();
151
152 $transitions->pairIdentical($pairing, [['id' => 1], ['id' => 2]], [['id' => 2], ['id' => 3]]);
153
154 self::assertSame([[1], [0], []], [
155 $pairing->beforePositions(),
156 $pairing->afterPositions(),
157 $pairing->changes(),
158 ]);
159 }
160
161 public function testPairByIdentityTakesThePairsAnUpdateToldUsAbout(): void
162 {
163 $transitions = new TableTransitions(new TableDefinitionRegistry());
164 $pairing = new RowPairing();
165 $identity = new MutationRowIdentity();
166
167 $transitions->pairByIdentity(
168 $pairing,
169 [['id' => 1]],
170 [['id' => 2]],
171 ['id'],
172 [['id' => 2, $identity->column('id') => 1]],
173 );
174
175 self::assertSame([[0], [0], 1], [
176 $pairing->beforePositions(),
177 $pairing->afterPositions(),
178 count($pairing->changes()),
179 ]);
180 }
181
182 public function testPairByIdentityLeavesARowNoResultRowSpeaksFor(): void
183 {
184 $transitions = new TableTransitions(new TableDefinitionRegistry());
185 $pairing = new RowPairing();
186 $identity = new MutationRowIdentity();
187
188 $transitions->pairByIdentity(
189 $pairing,
190 [['id' => 1]],
191 [['id' => 2]],
192 ['id'],
193 [['id' => 9, $identity->column('id') => 8]],
194 );
195
196 self::assertSame([], $pairing->beforePositions());
197 }
198
199 public function testPairByKeyPairsOffWhateverTheKeyStillMatches(): void
200 {
201 $transitions = new TableTransitions(new TableDefinitionRegistry());
202 $pairing = new RowPairing();
203
204 $transitions->pairByKey($pairing, [['id' => 1, 'a' => 1]], [['id' => 1, 'a' => 2]], ['id']);
205
206 self::assertSame([[0], [0], 1], [
207 $pairing->beforePositions(),
208 $pairing->afterPositions(),
209 count($pairing->changes()),
210 ]);
211 }
212
213 public function testPairByKeyLeavesARowAlreadyPairedOff(): void
214 {
215 $transitions = new TableTransitions(new TableDefinitionRegistry());
216 $pairing = new RowPairing();
217 $pairing->pair(0, 0, ['id' => 1], ['id' => 1]);
218
219 $transitions->pairByKey($pairing, [['id' => 1]], [['id' => 1]], ['id']);
220
221 self::assertSame([0], $pairing->beforePositions());
222 }
223
224 public function testPairByKeyLeavesARowTheKeyNoLongerMatches(): void
225 {
226 $transitions = new TableTransitions(new TableDefinitionRegistry());
227 $pairing = new RowPairing();
228
229 $transitions->pairByKey($pairing, [['id' => 1]], [['id' => 2]], ['id']);
230
231 self::assertSame([], $pairing->beforePositions());
232 }
233}
234