packages/ztd-query-core/tests/Contract/QueryClassifierContractTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Contract;
6
7use PHPUnit\Framework\TestCase;
8use ZtdQuery\Exception\UnsupportedSqlException;
9use ZtdQuery\Rewrite\QueryKind;
10
11/**
12 * Abstract contract test for QueryClassifier / QueryGuard implementations.
13 *
14 * Although QueryClassifier has no Core interface (each platform uses its own AST types),
15 * this contract test enforces behavioral consistency across platforms.
16 * See quality-standards.md Section 5.7.
17 */
18abstract class QueryClassifierContractTest extends TestCase
19{
20 /**
21 * Classify a SQL statement into a QueryKind, or return null if unrecognized.
22 */
23 /**
24 * @throws UnsupportedSqlException When the statement is not one ZTD can classify at all
25 */
26 abstract protected function classify(string $sql): ?QueryKind;
27
28 /**
29 * Answers a SELECT this dialect accepts.
30 *
31 * @return string The statement
32 */
33 abstract protected function selectSql(): string;
34
35 /**
36 * Answers an INSERT this dialect accepts.
37 *
38 * @return string The statement
39 */
40 abstract protected function insertSql(): string;
41
42 /**
43 * Answers an UPDATE this dialect accepts.
44 *
45 * @return string The statement
46 */
47 abstract protected function updateSql(): string;
48
49 /**
50 * Answers a DELETE this dialect accepts.
51 *
52 * @return string The statement
53 */
54 abstract protected function deleteSql(): string;
55
56 /**
57 * Answers a CREATE TABLE this dialect accepts.
58 *
59 * @return string The statement
60 */
61 abstract protected function createTableSql(): string;
62
63 /**
64 * Answers a DROP TABLE this dialect accepts.
65 *
66 * @return string The statement
67 */
68 abstract protected function dropTableSql(): string;
69
70 /**
71 * SELECT must classify as READ.
72 */
73 public function testSelectClassifiesAsRead(): void
74 {
75 $kind = $this->classify($this->selectSql());
76
77 self::assertSame(QueryKind::READ, $kind);
78 }
79
80 /**
81 * INSERT must classify as WRITE_SIMULATED.
82 */
83 public function testInsertClassifiesAsWriteSimulated(): void
84 {
85 $kind = $this->classify($this->insertSql());
86
87 self::assertSame(QueryKind::WRITE_SIMULATED, $kind);
88 }
89
90 /**
91 * UPDATE must classify as WRITE_SIMULATED.
92 */
93 public function testUpdateClassifiesAsWriteSimulated(): void
94 {
95 $kind = $this->classify($this->updateSql());
96
97 self::assertSame(QueryKind::WRITE_SIMULATED, $kind);
98 }
99
100 /**
101 * DELETE must classify as WRITE_SIMULATED.
102 */
103 public function testDeleteClassifiesAsWriteSimulated(): void
104 {
105 $kind = $this->classify($this->deleteSql());
106
107 self::assertSame(QueryKind::WRITE_SIMULATED, $kind);
108 }
109
110 /**
111 * CREATE TABLE must classify as DDL_SIMULATED.
112 */
113 public function testCreateTableClassifiesAsDdlSimulated(): void
114 {
115 $kind = $this->classify($this->createTableSql());
116
117 self::assertSame(QueryKind::DDL_SIMULATED, $kind);
118 }
119
120 /**
121 * DROP TABLE must classify as DDL_SIMULATED.
122 */
123 public function testDropTableClassifiesAsDdlSimulated(): void
124 {
125 $kind = $this->classify($this->dropTableSql());
126
127 self::assertSame(QueryKind::DDL_SIMULATED, $kind);
128 }
129
130 /**
131 * Garbage input must return null or be refused as unclassifiable.
132 */
133 public function testNullOrExceptionOnGarbageInput(): void
134 {
135 try {
136 self::assertNull(
137 $this->classify('NOT VALID SQL %%% @@@'),
138 'Garbage input should return null if it is not refused',
139 );
140 } catch (UnsupportedSqlException $refusal) {
141 self::assertNotSame('', $refusal->getMessage());
142 }
143 }
144}
145