packages/ztd-query-mysqli-adapter/tests/Integration/MysqliPlatformInjectionTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration;
6
7use Container\Endpoint;
8use Container\MySqlRelease;
9use mysqli;
10use mysqli_result;
11use PHPUnit\Framework\Attributes\Large;
12use PHPUnit\Framework\TestCase;
13use Testcontainers\Testcontainers;
14use ZtdQuery\Adapter\Mysqli\ZtdMysqli;
15use ZtdQuery\Platform\MySql\MySqlPlatform;
16
17#[\PHPUnit\Framework\Attributes\CoversClass(ZtdMysqli::class)]
18#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Session\ConnectionExecution::class)]
19#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\ZtdMysqliStatement::class)]
20#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Session\StatementExecution::class)]
21#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Driver\MysqliConnection::class)]
22#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Driver\MysqliResultStatement::class)]
23#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Driver\MysqliResultColumnExtractor::class)]
24#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Native\MysqliStatementBindingBridge::class)]
25#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\ZtdMysqliException::class)]
26#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Native\MysqliPropertyReader::class)]
27#[\PHPUnit\Framework\Attributes\CoversClass(\ZtdQuery\Adapter\Mysqli\Session\MysqliResultProcessor::class)]
28#[Large]
29final class MysqliPlatformInjectionTest extends TestCase
30{
31    public function testExplicitMySqlPlatformInjectionWorks(): void
32    {
33        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
34        $endpoint = $container->getData(Endpoint::class);
35        try {
36            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
37            $rawMysqli->set_charset('utf8mb4');
38            $table = 'prefix_' . bin2hex(random_bytes(8));
39            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
40            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
41            $platform = new MySqlPlatform();
42            $ztd = ZtdMysqli::fromMysqli($rawMysqli, null, $platform);
43
44            $result = $ztd->query(sprintf('SELECT * FROM `%s`', $table));
45            self::assertNotFalse($result);
46            self::assertInstanceOf(mysqli_result::class, $result);
47
48            $rows = $result->fetch_all(MYSQLI_ASSOC);
49            self::assertCount(0, $rows);
50        } finally {
51            $container->stop();
52        }
53    }
54
55    public function testInjectedPlatformInsertIsVisibleViaSelect(): void
56    {
57        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
58        $endpoint = $container->getData(Endpoint::class);
59        try {
60            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
61            $rawMysqli->set_charset('utf8mb4');
62            $table = 'prefix_' . bin2hex(random_bytes(8));
63            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
64            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
65            $platform = new MySqlPlatform();
66            $ztd = ZtdMysqli::fromMysqli($rawMysqli, null, $platform);
67
68            $ztd->query(sprintf(
69                "INSERT INTO `%s` (name) VALUES ('Charlie')",
70                $table
71            ));
72
73            $result = $ztd->query(sprintf('SELECT * FROM `%s` ORDER BY name', $table));
74            self::assertNotFalse($result);
75            self::assertInstanceOf(mysqli_result::class, $result);
76
77            $rows = $result->fetch_all(MYSQLI_ASSOC);
78            self::assertCount(1, $rows);
79            self::assertSame('Charlie', $rows[0]['name']);
80        } finally {
81            $container->stop();
82        }
83    }
84
85    public function testInjectedPlatformDoesNotModifyPhysicalDatabase(): void
86    {
87        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
88        $endpoint = $container->getData(Endpoint::class);
89        try {
90            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
91            $rawMysqli->set_charset('utf8mb4');
92            $table = 'prefix_' . bin2hex(random_bytes(8));
93            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
94            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
95            $platform = new MySqlPlatform();
96            $ztd = ZtdMysqli::fromMysqli($rawMysqli, null, $platform);
97
98            $ztd->query(sprintf(
99                "INSERT INTO `%s` (name) VALUES ('Charlie')",
100                $table
101            ));
102
103            $result = $rawMysqli->query(sprintf('SELECT * FROM `%s` ORDER BY name', $table));
104            self::assertNotFalse($result);
105            self::assertInstanceOf(mysqli_result::class, $result);
106
107            $rows = $result->fetch_all(MYSQLI_ASSOC);
108            self::assertCount(2, $rows);
109            self::assertSame('Alice', $rows[0]['name']);
110            self::assertSame('Bob', $rows[1]['name']);
111        } finally {
112            $container->stop();
113        }
114    }
115
116    public function testInjectedPlatformPreparedStatementWorks(): void
117    {
118        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
119        $endpoint = $container->getData(Endpoint::class);
120        try {
121            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
122            $rawMysqli->set_charset('utf8mb4');
123            $table = 'prefix_' . bin2hex(random_bytes(8));
124            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
125            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
126            $platform = new MySqlPlatform();
127            $ztd = ZtdMysqli::fromMysqli($rawMysqli, null, $platform);
128
129            $ztd->query(sprintf(
130                "INSERT INTO `%s` (name) VALUES ('Charlie')",
131                $table
132            ));
133
134            $stmt = $ztd->prepare(sprintf('SELECT * FROM `%s` WHERE name = ?', $table));
135            self::assertNotFalse($stmt);
136
137            $name = 'Charlie';
138            $stmt->bind_param('s', $name);
139            $stmt->execute();
140            $result = $stmt->get_result();
141            self::assertNotFalse($result);
142
143            $rows = $result->fetch_all(MYSQLI_ASSOC);
144            self::assertCount(1, $rows);
145            self::assertSame('Charlie', $rows[0]['name']);
146        } finally {
147            $container->stop();
148        }
149    }
150
151    public function testInjectedPlatformAffectedRowsTracking(): void
152    {
153        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
154        $endpoint = $container->getData(Endpoint::class);
155        try {
156            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
157            $rawMysqli->set_charset('utf8mb4');
158            $table = 'prefix_' . bin2hex(random_bytes(8));
159            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
160            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
161            $platform = new MySqlPlatform();
162            $ztd = ZtdMysqli::fromMysqli($rawMysqli, null, $platform);
163
164            $ztd->query(sprintf(
165                "INSERT INTO `%s` (name) VALUES ('Charlie')",
166                $table
167            ));
168
169            self::assertSame(1, $ztd->lastAffectedRows());
170        } finally {
171            $container->stop();
172        }
173    }
174
175    public function testDefaultPlatformBehaviorMatchesExplicitInjection(): void
176    {
177        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
178        $endpoint = $container->getData(Endpoint::class);
179        try {
180            $rawMysqli = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
181            $rawMysqli->set_charset('utf8mb4');
182            $table = 'prefix_' . bin2hex(random_bytes(8));
183            $rawMysqli->query(sprintf('CREATE TABLE `%s` (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL)', $table));
184            $rawMysqli->query(sprintf("INSERT INTO `%s` (name) VALUES ('Alice'), ('Bob')", $table));
185            $ztdDefault = ZtdMysqli::fromMysqli($rawMysqli);
186
187            $resultDefault = $ztdDefault->query(sprintf('SELECT * FROM `%s`', $table));
188            self::assertNotFalse($resultDefault);
189            self::assertInstanceOf(mysqli_result::class, $resultDefault);
190
191            $rowsDefault = $resultDefault->fetch_all(MYSQLI_ASSOC);
192            self::assertCount(0, $rowsDefault);
193        } finally {
194            $container->stop();
195        }
196    }
197}
198