packages/ztd-query-core/tests/Unit/Shadow/Mutation/Row/ReplaceMutationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Shadow\Mutation\Row;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use ZtdQuery\Schema\Key\CandidateKeyConflict;
11use ZtdQuery\Schema\Key\CandidateKeySet;
12use ZtdQuery\Schema\TableDefinition;
13use ZtdQuery\Shadow\Mutation\Row\ReplaceMutation;
14use ZtdQuery\Shadow\ShadowStore;
15
16#[UsesClass(ShadowStore::class)]
17#[UsesClass(CandidateKeyConflict::class)]
18#[UsesClass(CandidateKeySet::class)]
19#[UsesClass(TableDefinition::class)]
20#[CoversClass(ReplaceMutation::class)]
21#[UsesClass(\ZtdQuery\Schema\Key\CandidateKeyMatch::class)]
22#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
23final class ReplaceMutationTest extends TestCase
24{
25 public function testApplyReplacesExistingRowByPrimaryKey(): void
26 {
27 $store = new ShadowStore();
28 $store->set('users', [
29 ['id' => 1, 'name' => 'Alice'],
30 ['id' => 2, 'name' => 'Bob'],
31 ]);
32
33 $mutation = new ReplaceMutation('users', ['id']);
34 $mutation->apply($store, [['id' => 1, 'name' => 'Alice Updated']]);
35
36 $rows = $store->get('users');
37 self::assertCount(2, $rows);
38 self::assertSame('Bob', $rows[0]['name']);
39 self::assertSame('Alice Updated', $rows[1]['name']);
40 }
41
42 public function testApplyInsertsNewRowWhenNoDuplicate(): void
43 {
44 $store = new ShadowStore();
45 $store->set('users', [
46 ['id' => 1, 'name' => 'Alice'],
47 ]);
48
49 $mutation = new ReplaceMutation('users', ['id']);
50 $mutation->apply($store, [['id' => 2, 'name' => 'Bob']]);
51
52 $rows = $store->get('users');
53 self::assertCount(2, $rows);
54 self::assertSame('Alice', $rows[0]['name']);
55 self::assertSame('Bob', $rows[1]['name']);
56 }
57
58 public function testTableNameReturnsTableName(): void
59 {
60 $mutation = new ReplaceMutation('users', ['id']);
61
62 self::assertSame('users', $mutation->tableName());
63 }
64
65 public function testApplyWithoutCandidateKeysDoesNotReplaceEqualRows(): void
66 {
67 $store = new ShadowStore();
68 $store->set('users', [
69 ['id' => 1, 'name' => 'Alice'],
70 ['id' => 2, 'name' => 'Bob'],
71 ]);
72
73 $mutation = new ReplaceMutation('users');
74 $mutation->apply($store, [['id' => 1, 'name' => 'Alice']]);
75
76 $rows = $store->get('users');
77 self::assertCount(3, $rows);
78 }
79
80 public function testApplyRemovesEveryRowConflictingWithAnyCandidateKey(): void
81 {
82 $definition = new TableDefinition(
83 ['id', 'email', 'name'],
84 ['id' => 'INT', 'email' => 'TEXT', 'name' => 'TEXT'],
85 ['id'],
86 ['id'],
87 ['users_email' => ['email']],
88 );
89 $store = new ShadowStore();
90 $store->set('users', [
91 ['id' => 1, 'email' => 'alice@example.com', 'name' => 'Alice'],
92 ['id' => 2, 'email' => 'bob@example.com', 'name' => 'Bob'],
93 ]);
94
95 $mutation = new ReplaceMutation('users', ['id'], $definition->candidateKeys());
96 $mutation->apply($store, [['id' => 1, 'email' => 'bob@example.com', 'name' => 'Replacement']]);
97
98 self::assertSame([
99 ['id' => 1, 'email' => 'bob@example.com', 'name' => 'Replacement'],
100 ], $store->get('users'));
101 }
102
103 public function testApplyReplacesMultipleRows(): void
104 {
105 $store = new ShadowStore();
106 $store->set('users', [
107 ['id' => 1, 'name' => 'Alice'],
108 ['id' => 2, 'name' => 'Bob'],
109 ['id' => 3, 'name' => 'Carol'],
110 ]);
111
112 $mutation = new ReplaceMutation('users', ['id']);
113 $mutation->apply($store, [
114 ['id' => 1, 'name' => 'Alice Replaced'],
115 ['id' => 3, 'name' => 'Carol Replaced'],
116 ]);
117
118 $rows = $store->get('users');
119 self::assertCount(3, $rows);
120 self::assertSame('Bob', $rows[0]['name']);
121 self::assertSame('Alice Replaced', $rows[1]['name']);
122 self::assertSame('Carol Replaced', $rows[2]['name']);
123 }
124
125 public function testApplyWithCompositePrimaryKey(): void
126 {
127 $store = new ShadowStore();
128 $store->set('order_items', [
129 ['order_id' => 1, 'product_id' => 100, 'quantity' => 1],
130 ['order_id' => 1, 'product_id' => 200, 'quantity' => 2],
131 ]);
132
133 $mutation = new ReplaceMutation('order_items', ['order_id', 'product_id']);
134 $mutation->apply($store, [['order_id' => 1, 'product_id' => 100, 'quantity' => 5]]);
135
136 $rows = $store->get('order_items');
137 self::assertCount(2, $rows);
138 self::assertSame(2, $rows[0]['quantity']);
139 self::assertSame(5, $rows[1]['quantity']);
140 }
141
142 public function testApplyWithMissingPrimaryKeyDoesNotMatch(): void
143 {
144 $store = new ShadowStore();
145 $store->set('users', [
146 ['id' => 1, 'name' => 'Alice'],
147 ]);
148
149 $mutation = new ReplaceMutation('users', ['id']);
150 $mutation->apply($store, [['name' => 'Bob']]);
151
152 $rows = $store->get('users');
153 self::assertCount(2, $rows);
154 }
155}
156