packages/ztd-query-mysqli-adapter/tests/Unit/Session/MysqliResultProcessorTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Session;
6
7use Container\MySql80Container;
8use Container\MySql84Container;
9use mysqli;
10use mysqli_result;
11use PHPUnit\Framework\Attributes\CoversClass;
12use PHPUnit\Framework\Attributes\Large;
13use PHPUnit\Framework\Attributes\UsesClass;
14use PHPUnit\Framework\TestCase;
15use Testcontainers\Testcontainers;
16use ZtdQuery\Adapter\Mysqli\Driver\MysqliResultColumnExtractor;
17use ZtdQuery\Adapter\Mysqli\Driver\MysqliResultStatement;
18use ZtdQuery\Adapter\Mysqli\Session\MysqliResultProcessor;
19use ZtdQuery\Adapter\Mysqli\ZtdMysqliException;
20use ZtdQuery\Config\ZtdConfig;
21use ZtdQuery\Connection\ConnectionInterface;
22use ZtdQuery\Connection\Exception\DatabaseException;
23use ZtdQuery\Platform\MySql\MySqlResultColumnTypeResolver;
24use ZtdQuery\ResultSelectRunner;
25use ZtdQuery\Rewrite\QueryKind;
26use ZtdQuery\Rewrite\RewritePlan;
27use ZtdQuery\Rewrite\SqlRewriter;
28use ZtdQuery\Session;
29use ZtdQuery\Shadow\Mutation\InsertMutation;
30use ZtdQuery\Shadow\Mutation\ShadowMutation;
31use ZtdQuery\Shadow\ShadowStore;
32
33#[CoversClass(MysqliResultProcessor::class)]
34#[UsesClass(MysqliResultStatement::class)]
35#[UsesClass(MysqliResultColumnExtractor::class)]
36#[UsesClass(ZtdMysqliException::class)]
37#[Large]
38final class MysqliResultProcessorTest extends TestCase
39{
40    public function testProcessCreatesAnEmptyWriteResult(): void
41    {
42        $store = new ShadowStore();
43        $session = new Session(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
44        $plan = new RewritePlan('DO 1', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
45        $result = (new MysqliResultProcessor())->process($session, $plan, false, 0);
46        self::assertTrue($result->isSuccess());
47        self::assertSame(0, $result->rowCount());
48        self::assertSame([], $result->fetchAll());
49    }
50
51    public function testProcessUsesNativeRowsForTheShadowMutation(): void
52    {
53        $container = Testcontainers::run(getenv('MYSQL_VERSION') === '8.4.7' ? MySql84Container::class : MySql80Container::class);
54        try {
55            $connection = new mysqli(str_replace('localhost', '127.0.0.1', $container->getHost()), 'root', 'root', 'test', $container->getMappedPort(3306));
56            $connection->set_charset('utf8mb4');
57            $store = new ShadowStore();
58            $session = new Session(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
59            $result = $connection->query('SELECT 7 AS id');
60            self::assertInstanceOf(mysqli_result::class, $result);
61            $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
62            $processed = (new MysqliResultProcessor())->process($session, $plan, $result, 1);
63            self::assertTrue($processed->isSuccess());
64            self::assertSame(1, $processed->rowCount());
65            self::assertSame([['id' => '7']], $store->get('items'));
66            $connection->close();
67        } finally {
68            $container->stop();
69        }
70    }
71
72    public function testProcessPreservesTheOriginalDatabaseException(): void
73    {
74        $container = Testcontainers::run(getenv('MYSQL_VERSION') === '8.4.7' ? MySql84Container::class : MySql80Container::class);
75        try {
76            $connection = new mysqli(str_replace('localhost', '127.0.0.1', $container->getHost()), 'root', 'root', 'test', $container->getMappedPort(3306));
77            $connection->set_charset('utf8mb4');
78            $store = new ShadowStore();
79            $session = new Session(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
80            $result = $connection->query('SELECT 7 AS id');
81            self::assertInstanceOf(mysqli_result::class, $result);
82            $mutation = self::createStub(ShadowMutation::class);
83            $failure = new DatabaseException('result processing failed', 123, 123);
84            $mutation->method('apply')->willThrowException($failure);
85            $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, $mutation);
86            try {
87                (new MysqliResultProcessor())->process($session, $plan, $result, 1);
88                self::fail('Expected the MySQLi exception.');
89            } catch (ZtdMysqliException $exception) {
90                self::assertSame($failure, $exception->getPrevious());
91                self::assertSame('result processing failed', $exception->getMessage());
92                self::assertSame(0, $exception->getCode());
93            } finally {
94                $connection->close();
95            }
96        } finally {
97            $container->stop();
98        }
99    }
100
101}
102