packages/ztd-query-core/tests/Contract/IdentifierQuoterContractTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Contract;
6
7use PHPUnit\Framework\TestCase;
8use ZtdQuery\Platform\IdentifierQuoter;
9
10/**
11 * Abstract contract test for IdentifierQuoter implementations.
12 *
13 * Enforces contracts defined in quality-standards.md Section 1.8 and properties P-IQ-1 through P-IQ-5.
14 */
15abstract class IdentifierQuoterContractTest extends TestCase
16{
17 /**
18 * Answers the quoter this dialect names things with.
19 *
20 * @return IdentifierQuoter The quoter under test
21 */
22 abstract protected function createQuoter(): IdentifierQuoter;
23
24 /**
25 * Return the expected quote character for this platform.
26 * MySQL: backtick (`), PostgreSQL/SQLite: double quote (").
27 *
28 * @return non-empty-string
29 */
30 abstract protected function quoteCharacter(): string;
31
32 /**
33 * quote() must return a non-empty string (P-IQ-1).
34 */
35 public function testQuoteReturnsNonEmptyString(): void
36 {
37 $quoter = $this->createQuoter();
38
39 $result = $quoter->quote('users');
40
41 self::assertNotEmpty($result);
42 }
43
44 /**
45 * Output must start and end with the platform's quoting character (P-IQ-2).
46 */
47 public function testQuoteWrapsIdentifier(): void
48 {
49 $quoter = $this->createQuoter();
50 $char = $this->quoteCharacter();
51
52 $result = $quoter->quote('table_name');
53
54 self::assertStringStartsWith($char, $result);
55 self::assertStringEndsWith($char, $result);
56 }
57
58 /**
59 * If the identifier contains the quoting character, the output must still be valid (P-IQ-5).
60 */
61 public function testQuoteEscapesQuoteCharacterInIdentifier(): void
62 {
63 $quoter = $this->createQuoter();
64 $char = $this->quoteCharacter();
65
66 $identifier = 'col' . $char . 'name';
67 $result = $quoter->quote($identifier);
68
69 self::assertNotEmpty($result);
70 self::assertStringStartsWith($char, $result);
71 self::assertStringEndsWith($char, $result);
72
73 $simpleQuoted = $char . $identifier . $char;
74 self::assertGreaterThanOrEqual(
75 strlen($simpleQuoted),
76 strlen($result),
77 'Escaped identifier should be at least as long as non-escaped form'
78 );
79 }
80
81 /**
82 * quote() must produce exact expected output for a simple identifier (P-IQ-2).
83 */
84 public function testQuoteProducesExactResult(): void
85 {
86 $quoter = $this->createQuoter();
87 $char = $this->quoteCharacter();
88
89 $result = $quoter->quote('users');
90
91 self::assertSame($char . 'users' . $char, $result);
92 }
93
94 /**
95 * quote() with embedded quote character must double-escape it.
96 */
97 public function testQuoteEscapesEmbeddedQuoteExactly(): void
98 {
99 $quoter = $this->createQuoter();
100 $char = $this->quoteCharacter();
101
102 $result = $quoter->quote('col' . $char . 'name');
103
104 $expected = $char . 'col' . $char . $char . 'name' . $char;
105 self::assertSame($expected, $result);
106 }
107
108 /**
109 * Same input must produce identical output (P-IQ-4).
110 */
111 public function testQuoteIsDeterministic(): void
112 {
113 $quoter = $this->createQuoter();
114
115 $result1 = $quoter->quote('my_table');
116 $result2 = $quoter->quote('my_table');
117
118 self::assertSame($result1, $result2);
119 }
120
121 /**
122 * The original identifier name must be recoverable from the output (P-IQ-3).
123 */
124 public function testQuotedIdentifierContainsOriginalName(): void
125 {
126 $quoter = $this->createQuoter();
127
128 $identifier = 'column_name';
129 $result = $quoter->quote($identifier);
130
131 self::assertStringContainsString($identifier, $result);
132 }
133}
134