ClientMiddlewareTest   A
last analyzed

Complexity

Total Complexity 4

Size/Duplication

Total Lines 134
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 56
c 4
b 0
f 0
dl 0
loc 134
rs 10
wmc 4

10 Methods

Rating   Name   Duplication   Size   Complexity  
A hp$0 ➔ createBrokenConnectionMiddleware() 0 17 2
createBrokenConnectionMiddleware() 0 17 ?
A testRetry() 0 18 1
A testThrowOnBrokenConnection() 0 12 1
A testAuthenticationRetrySucceeds() 0 11 1
A testReconnectOnBrokenConnection() 0 10 1
A testAuthenticationRetryFails() 0 12 1
A testNoRetriesOnUnexpectedResponse() 0 9 1
A hp$0 ➔ process() 0 12 2
A testSpaceInheritsMiddleware() 0 19 1
1
<?php
2
3
/**
4
 * This file is part of the tarantool/client package.
5
 *
6
 * (c) Eugene Leonovich <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Tarantool\Client\Tests\Integration;
15
16
use Tarantool\Client\Client;
17
use Tarantool\Client\Exception\CommunicationFailed;
18
use Tarantool\Client\Exception\RequestFailed;
19
use Tarantool\Client\Exception\UnexpectedResponse;
20
use Tarantool\Client\Handler\Handler;
21
use Tarantool\Client\Middleware\Middleware;
22
use Tarantool\Client\Middleware\RetryMiddleware;
23
use Tarantool\Client\Request\PingRequest;
24
use Tarantool\Client\Request\Request;
25
use Tarantool\Client\Response;
26
use Tarantool\Client\Schema\Criteria;
27
use Tarantool\Client\Schema\Space;
28
use Tarantool\Client\Tests\SpyMiddleware;
29
30
final class ClientMiddlewareTest extends TestCase
31
{
32
    public function testSpaceInheritsMiddleware() : void
33
    {
34
        $middleware = SpyMiddleware::fromTraceId(1);
35
36
        // Cache the space
37
        $this->client->getSpaceById(Space::VSPACE_ID)->select(Criteria::key([]));
0 ignored issues
show
Bug introduced by
The method getSpaceById() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

37
        $this->client->/** @scrutinizer ignore-call */ 
38
                       getSpaceById(Space::VSPACE_ID)->select(Criteria::key([]));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
38
39
        $clientWithMiddleware = $this->client->withMiddleware($middleware);
40
41
        $clientWithMiddleware->ping();
42
        self::assertSame([1], $middleware->getTraceLogArray());
43
44
        $spaceWithMiddleware = $clientWithMiddleware->getSpaceById(Space::VSPACE_ID);
45
46
        $spaceWithMiddleware->select(Criteria::key([]));
47
        self::assertSame([1, 1], $middleware->getTraceLogArray());
48
49
        $spaceWithMiddleware->select(Criteria::key([]));
50
        self::assertSame([1, 1, 1], $middleware->getTraceLogArray());
51
    }
52
53
    /**
54
     * @doesNotPerformAssertions
55
     *
56
     * @lua fiber = require('fiber')
57
     * @lua function test() try_drop_user('foobar') fiber.sleep(.5) create_user('foobar', '') end
58
     * @lua fiber.create(test)
59
     */
60
    public function testAuthenticationRetrySucceeds() : void
61
    {
62
        $client = Client::fromOptions([
63
            'uri' => ClientBuilder::createFromEnv()->getUri(),
64
            'username' => 'foobar',
65
            'max_retries' => 5,
66
            // 5 linear retries with a difference of 100ms and equal jitter give
67
            // at worst 100/2 + 200/2 + 300/2 + 400/2 + 800/2 = 900ms in total (> 0.5)
68
        ]);
69
70
        $client->ping();
71
    }
72
73
    public function testAuthenticationRetryFails() : void
74
    {
75
        $client = Client::fromOptions([
76
            'uri' => ClientBuilder::createFromEnv()->getUri(),
77
            'username' => 'ghost',
78
            'max_retries' => 2,
79
        ]);
80
81
        $this->expectException(RequestFailed::class);
82
        $this->expectExceptionMessageMatches("/(User 'ghost' is not found|User not found or supplied credentials are invalid)/");
83
84
        $client->ping();
85
    }
86
87
    public function testRetry() : void
88
    {
89
        $clientBuilder = ClientBuilder::createFromEnv();
90
        $client = $clientBuilder->build();
91
        $retryableClient = $clientBuilder->setOptions(['max_retries' => 1])->build();
92
93
        $client->evaluate($luaInit = 'create_space("connection_retry")');
94
        // Trigger an error only on the first call
95
        $retryableClient->evaluate($luaCall = '
96
            if box.space.connection_retry then
97
                box.space.connection_retry:drop()
98
                box.error{code = 42, reason = "Foobar"}
99
            end
100
        ');
101
102
        $client->evaluate($luaInit);
103
        $this->expectException(RequestFailed::class);
104
        $client->evaluate($luaCall);
105
    }
106
107
    public function testNoRetriesOnUnexpectedResponse() : void
108
    {
109
        $clientBuilder = ClientBuilder::createFromEnv();
110
        $client = $clientBuilder->setOptions(['max_retries' => 5])->build();
111
112
        self::triggerUnexpectedResponse($client->getHandler(), new PingRequest());
113
114
        $this->expectException(UnexpectedResponse::class);
115
        $client->ping();
116
    }
117
118
    /**
119
     * @doesNotPerformAssertions
120
     */
121
    public function testReconnectOnBrokenConnection() : void
122
    {
123
        $clientBuilder = ClientBuilder::createFromEnv();
124
        $client = $clientBuilder->setConnectionOptions(['socket_timeout' => 1])->build();
125
126
        $client = $client->withMiddleware(RetryMiddleware::constant(1));
127
        $client = $client->withMiddleware(self::createBrokenConnectionMiddleware());
128
129
        $client->ping();
130
        $client->ping();
131
    }
132
133
    public function testThrowOnBrokenConnection() : void
134
    {
135
        $clientBuilder = ClientBuilder::createFromEnv();
136
        $client = $clientBuilder->setConnectionOptions(['socket_timeout' => 1])->build();
137
138
        $client = $client->withMiddleware(self::createBrokenConnectionMiddleware());
139
140
        $client->ping();
141
142
        $this->expectException(CommunicationFailed::class);
143
        $this->expectExceptionMessageMatches('/Error writing request:.+Send of .+ bytes failed/i');
144
        $client->ping();
145
    }
146
147
    private static function createBrokenConnectionMiddleware() : Middleware
148
    {
149
        return new class() implements Middleware {
150
            private $count = 0;
151
152
            public function process(Request $request, Handler $handler) : Response
153
            {
154
                if (1 === $this->count) {
155
                    $connection = $handler->getConnection();
156
                    stream_socket_shutdown(TestCase::getRawStream($connection), STREAM_SHUT_WR);
157
                }
158
159
                ++$this->count;
160
161
                // Suppress PHPUnit\Framework\Error\Notice:
162
                // fwrite(): send of 15 bytes failed with errno=32 Broken pipe
163
                return @$handler->handle($request);
164
            }
165
        };
166
    }
167
}
168