packages/ztd-query-core/tests/Unit/Shadow/ForeignKeyIntegrityTest.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\Exception\ForeignKeyViolationException;
11use ZtdQuery\Schema\Key\ForeignKeyDefinition;
12use ZtdQuery\Schema\TableDefinition;
13use ZtdQuery\Schema\TableDefinitionRegistry;
14use ZtdQuery\Shadow\ForeignKeyEnds;
15use ZtdQuery\Shadow\ForeignKeyIntegrity;
16use ZtdQuery\Shadow\ParentKeyLookup;
17use ZtdQuery\Shadow\Row\RowMatch;
18use ZtdQuery\Shadow\ShadowStore;
19
20#[CoversClass(ForeignKeyIntegrity::class)]
21#[UsesClass(ForeignKeyEnds::class)]
22#[UsesClass(ParentKeyLookup::class)]
23#[UsesClass(RowMatch::class)]
24#[UsesClass(ShadowStore::class)]
25#[UsesClass(ForeignKeyDefinition::class)]
26#[UsesClass(TableDefinition::class)]
27#[UsesClass(TableDefinitionRegistry::class)]
28#[UsesClass(ForeignKeyViolationException::class)]
29#[UsesClass(\ZtdQuery\Schema\RowSet::class)]
30final class ForeignKeyIntegrityTest extends TestCase
31{
32    public function testAssertHoldsPassesAShadowInWhichEveryChildFindsItsParent(): void
33    {
34        $registry = new TableDefinitionRegistry();
35        $registry->register('children', new TableDefinition(['id', 'parent_id'], [], ['id'], [], [], foreignKeys: [
36            'fk' => new ForeignKeyDefinition(['parent_id'], 'parents', ['id']),
37        ]));
38        $store = new ShadowStore();
39        $store->set('parents', [['id' => 1]]);
40        $store->set('children', [['id' => 10, 'parent_id' => 1]]);
41
42        (new ForeignKeyIntegrity($registry, new ForeignKeyEnds($registry)))->assertHolds($store, 'INSERT');
43
44        $this->expectNotToPerformAssertions();
45    }
46
47    public function testAssertHoldsRefusesAShadowInWhichAChildReferencesNothing(): void
48    {
49        $registry = new TableDefinitionRegistry();
50        $registry->register('children', new TableDefinition(['id', 'parent_id'], [], ['id'], [], [], foreignKeys: [
51            'fk' => new ForeignKeyDefinition(['parent_id'], 'parents', ['id']),
52        ]));
53        $store = new ShadowStore();
54        $store->set('parents', []);
55        $store->set('children', [['id' => 10, 'parent_id' => 1]]);
56
57        $this->expectException(ForeignKeyViolationException::class);
58
59        (new ForeignKeyIntegrity($registry, new ForeignKeyEnds($registry)))->assertHolds($store, 'INSERT');
60    }
61
62    public function testAssertHoldsLeavesAKeyWithANullInItAlone(): void
63    {
64        $registry = new TableDefinitionRegistry();
65        $registry->register('children', new TableDefinition(['id', 'parent_id'], [], ['id'], [], [], foreignKeys: [
66            'fk' => new ForeignKeyDefinition(['parent_id'], 'parents', ['id']),
67        ]));
68        $store = new ShadowStore();
69        $store->set('parents', []);
70        $store->set('children', [['id' => 10, 'parent_id' => null]]);
71
72        (new ForeignKeyIntegrity($registry, new ForeignKeyEnds($registry)))->assertHolds($store, 'INSERT');
73
74        $this->expectNotToPerformAssertions();
75    }
76
77    public function testAssertHoldsChecksNothingThroughAKeyWhoseEndsDisagree(): void
78    {
79        $registry = new TableDefinitionRegistry();
80        $registry->register('children', new TableDefinition(['id', 'parent_id'], [], ['id'], [], [], foreignKeys: [
81            'fk' => new ForeignKeyDefinition(['parent_id'], 'parents', ['a', 'b']),
82        ]));
83        $store = new ShadowStore();
84        $store->set('parents', []);
85        $store->set('children', [['id' => 10, 'parent_id' => 1]]);
86
87        (new ForeignKeyIntegrity($registry, new ForeignKeyEnds($registry)))->assertHolds($store, 'INSERT');
88
89        $this->expectNotToPerformAssertions();
90    }
91
92    public function testAssertHoldsLeavesARowThatDoesNotCarryTheKeyColumnsAlone(): void
93    {
94        $registry = new TableDefinitionRegistry();
95        $registry->register('children', new TableDefinition(['id', 'parent_id'], [], ['id'], [], [], foreignKeys: [
96            'fk' => new ForeignKeyDefinition(['parent_id'], 'parents', ['id']),
97        ]));
98        $store = new ShadowStore();
99        $store->set('parents', []);
100        $store->set('children', [['id' => 10]]);
101
102        (new ForeignKeyIntegrity($registry, new ForeignKeyEnds($registry)))->assertHolds($store, 'INSERT');
103
104        $this->expectNotToPerformAssertions();
105    }
106}
107