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