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