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