packages/ztd-query-core/tests/Unit/Shadow/Mutation/Row/UpdateMutationTest.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\Exception\DuplicateKeyException;
11use ZtdQuery\Exception\NotNullViolationException;
12use ZtdQuery\Schema\TableDefinition;
13use ZtdQuery\Shadow\Mutation\MutationRowIdentity;
14use ZtdQuery\Shadow\Mutation\Row\UpdateMutation;
15use ZtdQuery\Shadow\ShadowStore;
16
17#[UsesClass(DuplicateKeyException::class)]
18#[UsesClass(NotNullViolationException::class)]
19#[UsesClass(TableDefinition::class)]
20#[UsesClass(ShadowStore::class)]
21#[UsesClass(MutationRowIdentity::class)]
22#[CoversClass(UpdateMutation::class)]
23#[UsesClass(\ZtdQuery\Shadow\Mutation\RowConstraints::class)]
24#[UsesClass(\ZtdQuery\Shadow\Row\RowMatch::class)]
25#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
26final class UpdateMutationTest extends TestCase
27{
28 public function testTableNameApplyReplacesRowsByPrimaryKey(): void
29 {
30 $store = new ShadowStore();
31 $store->set('users', [
32 ['id' => 1, 'name' => 'Alice'],
33 ['id' => 2, 'name' => 'Bob'],
34 ]);
35
36 $mutation = new UpdateMutation('users', ['id']);
37 $mutation->apply($store, [
38 ['id' => 2, 'name' => 'Bobby'],
39 ]);
40
41 self::assertSame([
42 ['id' => 1, 'name' => 'Alice'],
43 ['id' => 2, 'name' => 'Bobby'],
44 ], $store->get('users'));
45 self::assertSame('users', $mutation->tableName());
46 }
47
48 public function testValidateNotNullThrowsException(): void
49 {
50 $tableDefinition = new TableDefinition(
51 ['id', 'name'],
52 ['id' => 'INT', 'name' => 'VARCHAR(255)'],
53 ['id'],
54 ['id', 'name'],
55 [],
56 );
57
58 $store = new ShadowStore();
59 $store->set('users', [['id' => 1, 'name' => 'Alice']]);
60
61 $mutation = new UpdateMutation(
62 'users',
63 ['id'],
64 $tableDefinition,
65 'UPDATE users SET name = NULL WHERE id = 1',
66 true
67 );
68
69 $this->expectException(NotNullViolationException::class);
70 $this->expectExceptionMessage("Column 'name' in table 'users' cannot be NULL");
71
72 $mutation->apply($store, [['id' => 1, 'name' => null]]);
73 }
74
75 public function testValidateUniqueThrowsException(): void
76 {
77 $tableDefinition = new TableDefinition(
78 ['id', 'email'],
79 ['id' => 'INT', 'email' => 'VARCHAR(255)'],
80 ['id'],
81 ['id'],
82 ['email_UNIQUE' => ['email']],
83 );
84
85 $store = new ShadowStore();
86 $store->set('users', [
87 ['id' => 1, 'email' => 'alice@example.com'],
88 ['id' => 2, 'email' => 'bob@example.com'],
89 ]);
90
91 $mutation = new UpdateMutation(
92 'users',
93 ['id'],
94 $tableDefinition,
95 'UPDATE users SET email = "alice@example.com" WHERE id = 2',
96 true
97 );
98
99 $this->expectException(DuplicateKeyException::class);
100 $this->expectExceptionMessageMatches("/Duplicate entry.*alice@example.com.*for key 'email_UNIQUE'/");
101
102 $mutation->apply($store, [['id' => 2, 'email' => 'alice@example.com']]);
103 }
104
105 public function testValidationDisabledByDefaultAllowsNullInNotNullColumn(): void
106 {
107 $tableDefinition = new TableDefinition(
108 ['id', 'name'],
109 ['id' => 'INT', 'name' => 'VARCHAR(255)'],
110 ['id'],
111 ['id', 'name'],
112 [],
113 );
114
115 $store = new ShadowStore();
116 $store->set('users', [['id' => 1, 'name' => 'Alice']]);
117
118 $mutation = new UpdateMutation('users', ['id'], $tableDefinition);
119 $mutation->apply($store, [['id' => 1, 'name' => null]]);
120
121 self::assertNull($store->get('users')[0]['name']);
122 }
123
124 public function testUpdateSameRowUniqueDoesNotThrow(): void
125 {
126 $tableDefinition = new TableDefinition(
127 ['id', 'email'],
128 ['id' => 'INT', 'email' => 'VARCHAR(255)'],
129 ['id'],
130 ['id'],
131 ['email_UNIQUE' => ['email']],
132 );
133
134 $store = new ShadowStore();
135 $store->set('users', [
136 ['id' => 1, 'email' => 'alice@example.com'],
137 ]);
138
139 $mutation = new UpdateMutation(
140 'users',
141 ['id'],
142 $tableDefinition,
143 'UPDATE users SET email = "alice@example.com" WHERE id = 1',
144 true
145 );
146
147 $mutation->apply($store, [['id' => 1, 'email' => 'alice@example.com']]);
148
149 self::assertSame('alice@example.com', $store->get('users')[0]['email']);
150 }
151 public function testApplyWritesTheNewRowOverTheRowTheOldKeyPointsAt(): void
152 {
153 $store = new ShadowStore();
154 $store->set('users', [['id' => 1, 'name' => 'a'], ['id' => 2, 'name' => 'b']]);
155
156 (new UpdateMutation('users', ['id']))->apply($store, [['id' => 3, '__ztd_original_id' => 1]]);
157
158 self::assertSame([['id' => 3], ['id' => 2, 'name' => 'b']], $store->get('users'));
159 }
160
161 public function testApplyLeavesATableTheOldKeyPointsAtNothingInAlone(): void
162 {
163 $store = new ShadowStore();
164 $store->set('users', [['id' => 1]]);
165
166 (new UpdateMutation('users', ['id']))->apply($store, [['id' => 9, '__ztd_original_id' => 8]]);
167
168 self::assertSame([['id' => 1]], $store->get('users'));
169 }
170
171}
172