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\Endpoint;
8use Container\MySqlRelease;
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\Shadow\Mutation\InsertMutation;
29use ZtdQuery\Shadow\Mutation\ShadowMutation;
30use ZtdQuery\Shadow\ShadowStore;
31
32#[CoversClass(MysqliResultProcessor::class)]
33#[UsesClass(MysqliResultStatement::class)]
34#[UsesClass(MysqliResultColumnExtractor::class)]
35#[UsesClass(ZtdMysqliException::class)]
36#[Large]
37final class MysqliResultProcessorTest extends TestCase
38{
39    public function testProcessCreatesAnEmptyWriteResult(): void
40    {
41        $store = new ShadowStore();
42        $executor = \Tests\Fake\QueryExecutorBuilder::create(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
43        $plan = new RewritePlan('DO 1', QueryKind::WRITE_SIMULATED, new InsertMutation('items'));
44        $result = (new MysqliResultProcessor())->process($executor, $plan, false, 0);
45        self::assertTrue($result->isSuccess());
46        self::assertSame(0, $result->rowCount());
47        self::assertSame([], $result->fetchAll());
48    }
49
50    public function testProcessUsesNativeRowsForTheShadowMutation(): void
51    {
52        $container = Testcontainers::run(MySqlRelease::fromEnvironment());
53        $endpoint = $container->getData(Endpoint::class);
54        try {
55            $connection = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
56            $connection->set_charset('utf8mb4');
57            $store = new ShadowStore();
58            $executor = \Tests\Fake\QueryExecutorBuilder::create(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($executor, $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(MySqlRelease::fromEnvironment());
75        $endpoint = $container->getData(Endpoint::class);
76        try {
77            $connection = new mysqli($endpoint->host, $endpoint->username, $endpoint->password, $endpoint->database, $endpoint->port);
78            $connection->set_charset('utf8mb4');
79            $store = new ShadowStore();
80            $executor = \Tests\Fake\QueryExecutorBuilder::create(self::createStub(SqlRewriter::class), $store, new ResultSelectRunner(), ZtdConfig::default(), self::createStub(ConnectionInterface::class), resultColumnTypeResolver: new MySqlResultColumnTypeResolver());
81            $result = $connection->query('SELECT 7 AS id');
82            self::assertInstanceOf(mysqli_result::class, $result);
83            $mutation = self::createStub(ShadowMutation::class);
84            $failure = new DatabaseException('result processing failed', 123, 123);
85            $mutation->method('apply')->willThrowException($failure);
86            $plan = new RewritePlan('SELECT 7 AS id', QueryKind::WRITE_SIMULATED, $mutation);
87            try {
88                (new MysqliResultProcessor())->process($executor, $plan, $result, 1);
89                self::fail('Expected the MySQLi exception.');
90            } catch (ZtdMysqliException $exception) {
91                self::assertSame($failure, $exception->getPrevious());
92                self::assertSame('result processing failed', $exception->getMessage());
93                self::assertSame(0, $exception->getCode());
94            } finally {
95                $connection->close();
96            }
97        } finally {
98            $container->stop();
99        }
100    }
101
102}
103