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