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