packages/ztd-query-core/tests/Unit/Schema/Key/CandidateKeySetTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Schema\Key;
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;
12
13#[CoversClass(CandidateKeySet::class)]
14#[UsesClass(CandidateKeyConflict::class)]
15#[UsesClass(\ZtdQuery\Schema\Key\CandidateKeyMatch::class)]
16final class CandidateKeySetTest extends TestCase
17{
18    public function testFindsConflictAcrossPrimaryAndUniqueCandidateKeys(): void
19    {
20        $keys = CandidateKeySet::fromSchema(['id'], ['users_email' => ['email']]);
21        $rows = [
22            ['id' => 1, 'email' => 'alice@example.com'],
23            ['id' => 2, 'email' => 'bob@example.com'],
24        ];
25
26        $primaryConflict = $keys->findConflict(['id' => 1, 'email' => 'new@example.com'], $rows);
27        $uniqueConflict = $keys->findConflict(['id' => 3, 'email' => 'bob@example.com'], $rows);
28
29        self::assertInstanceOf(CandidateKeyConflict::class, $primaryConflict);
30        self::assertSame('PRIMARY', $primaryConflict->keyName);
31        self::assertSame(0, $primaryConflict->rowIndex);
32        self::assertInstanceOf(CandidateKeyConflict::class, $uniqueConflict);
33        self::assertSame('users_email', $uniqueConflict->keyName);
34        self::assertSame(1, $uniqueConflict->rowIndex);
35    }
36
37    public function testNullDoesNotConflictOnCompositeUniqueKey(): void
38    {
39        $keys = CandidateKeySet::fromSchema([], ['tenant_slug' => ['tenant_id', 'slug']]);
40        $rows = [['tenant_id' => 1, 'slug' => null]];
41
42        self::assertNull($keys->findConflict(['tenant_id' => 1, 'slug' => null], $rows));
43    }
44
45    public function testIncompletePrimaryKeyDoesNotPreventUniqueKeyConflictDetection(): void
46    {
47        $keys = CandidateKeySet::fromSchema(['id'], ['users_email' => ['email']]);
48        $rows = [['id' => 1, 'email' => 'alice@example.com']];
49
50        $conflict = $keys->findConflict(['id' => null, 'email' => 'alice@example.com'], $rows);
51
52        self::assertInstanceOf(CandidateKeyConflict::class, $conflict);
53        self::assertSame('users_email', $conflict->keyName);
54    }
55
56    public function testMissingCandidateKeyValueDoesNotConflictWithStoredNull(): void
57    {
58        $keys = CandidateKeySet::fromSchema([], ['tenant_slug' => ['tenant_id', 'slug']]);
59        $rows = [['tenant_id' => 1, 'slug' => null]];
60
61        self::assertNull($keys->findConflict(['tenant_id' => 1], $rows));
62    }
63
64    public function testMissingStoredKeyValueDoesNotConflict(): void
65    {
66        $keys = CandidateKeySet::fromSchema([], ['users_email' => ['email']]);
67
68        self::assertNull($keys->findConflict(['email' => 'alice@example.com'], [['id' => 1]]));
69    }
70
71    public function testEmptyKeySetNeverTreatsEqualRowsAsConflicting(): void
72    {
73        $keys = CandidateKeySet::fromSchema([]);
74        $row = ['name' => 'same'];
75
76        self::assertNull($keys->findConflict($row, [$row]));
77    }
78
79    public function testFromSchemaPutsThePrimaryKeyBeforeEveryUniqueOne(): void
80    {
81        $keys = CandidateKeySet::fromSchema(['id'], ['email' => ['email']]);
82
83        self::assertSame(['PRIMARY', 'email'], array_keys($keys->keys()));
84    }
85
86    public function testFromSchemaNamesOnlyTheUniqueKeysWhereThereIsNoPrimaryOne(): void
87    {
88        $keys = CandidateKeySet::fromSchema([], ['email' => ['email']]);
89
90        self::assertSame(['email' => ['email']], $keys->keys());
91    }
92
93    public function testKeysAnswersTheColumnsOfEveryKeyItWasBuiltFrom(): void
94    {
95        $keys = new CandidateKeySet(['PRIMARY' => ['id']]);
96
97        self::assertSame(['PRIMARY' => ['id']], $keys->keys());
98    }
99
100    public function testFindConflictIsNothingWhereNoRowCarriesTheSameKey(): void
101    {
102        $keys = CandidateKeySet::fromSchema(['id']);
103
104        self::assertNull($keys->findConflict(['id' => 2], [['id' => 1]]));
105    }
106}
107