Passed
Pull Request — master (#50)
by Joao
01:55
created

AbstractRequester::assertBodyContains()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
3
namespace ByJG\ApiTools;
4
5
use ByJG\ApiTools\Base\Schema;
6
use ByJG\ApiTools\Exception\InvalidRequestException;
7
use ByJG\ApiTools\Exception\NotMatchedException;
8
use ByJG\ApiTools\Exception\StatusCodeNotMatchedException;
9
use ByJG\Util\Psr7\MessageException;
10
use ByJG\Util\Psr7\Request;
11
use ByJG\Util\Psr7\Response;
12
use ByJG\Util\Uri;
13
use MintWare\Streams\MemoryStream;
14
use Psr\Http\Message\RequestInterface;
15
use Psr\Http\Message\ResponseInterface;
16
17
/**
18
 * Abstract baseclass for request handlers.
19
 *
20
 * The baseclass provides processing and verification of request and response.
21
 * It only delegates the actual message exchange to the derived class. For the
22
 * messages, it uses the PSR-7 implementation from Guzzle.
23
 *
24
 * This is an implementation of the Template Method Patttern
25
 * (https://en.wikipedia.org/wiki/Template_method_pattern).
26
 */
27
abstract class AbstractRequester
28
{
29
    /**
30
     * @var Schema
31
     */
32
    protected $schema = null;
33
34
    protected $statusExpected = 200;
35
    protected $assertHeader = [];
36
    protected $assertBody = null;
37
38
    /**
39
     * @var RequestInterface
40
     */
41
    protected $psr7Request;
42
43
    /**
44
     * AbstractRequester constructor.
45
     * @throws MessageException
46
     */
47
    public function __construct()
48
    {
49
        $this->withPsr7Request(Request::getInstance(new Uri("/"))->withMethod("get"));
50
    }
51
52
    /**
53
     * abstract function to be implemented by derived classes
54
     *
55
     * This function must be implemented by derived classes. It should process
56
     * the given request and return an according response.
57
     *
58
     * @param RequestInterface $request
59
     * @return ResponseInterface
60
     */
61
    abstract protected function handleRequest(RequestInterface $request);
62
63
    /**
64
     * @param Schema $schema
65
     * @return $this
66
     */
67
    public function withSchema($schema)
68
    {
69
        $this->schema = $schema;
70
71
        return $this;
72
    }
73
74
    /**
75
     * @return bool
76
     */
77
    public function hasSchema()
78
    {
79
        return !empty($this->schema);
80
    }
81
82
    /**
83
     * @param string $method
84
     * @return $this
85
     * @throws MessageException
86
     */
87
    public function withMethod($method)
88
    {
89
        $this->psr7Request->withMethod($method);
90
91
        return $this;
92
    }
93
94
    /**
95
     * @param string $path
96
     * @return $this
97
     */
98
    public function withPath($path)
99
    {
100
        $this->psr7Request->getUri()->withPath($path);
101
102
        return $this;
103
    }
104
105
    /**
106
     * @param array $requestHeader
107
     * @return $this
108
     */
109
    public function withRequestHeader($requestHeader)
110
    {
111
        foreach ((array)$requestHeader as $name => $value) {
112
            $this->psr7Request->withHeader($name, $value);
113
        }
114
115
        return $this;
116
    }
117
118
    /**
119
     * @param array $query
120
     * @return $this
121
     */
122
    public function withQuery($query = null)
123
    {
124
        if (is_null($query)) {
125
            $this->query = [];
0 ignored issues
show
Bug introduced by
The property query does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
126
            return $this;
127
        }
128
129
        $currentQuery = [];
130
        parse_str($this->psr7Request->getUri()->getQuery(), $currentQuery);
131
132
        $this->psr7Request->getUri()->withQuery(http_build_query(array_merge($currentQuery, $query)));
133
134
        return $this;
135
    }
136
137
    /**
138
     * @param null $requestBody
139
     * @return $this
140
     */
141
    public function withRequestBody($requestBody)
142
    {
143
        $contentType = $this->psr7Request->getHeader("Content-Type");
144
        if (is_array($requestBody) && (empty($contentType) || $contentType == "application/json")) {
145
            $requestBody = json_encode($requestBody);
146
        }
147
        $this->psr7Request->withBody(new MemoryStream($requestBody));
0 ignored issues
show
Bug introduced by
It seems like $requestBody can also be of type array; however, MintWare\Streams\MemoryStream::__construct() does only seem to accept string|null, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
148
149
        return $this;
150
    }
151
152
    public function withPsr7Request(RequestInterface $requesInterface)
153
    {
154
        $this->psr7Request = clone $requesInterface;
155
        $this->psr7Request->withHeader("Accept", "application/json");
156
    }
157
158
    public function assertResponseCode($code)
159
    {
160
        $this->statusExpected = $code;
161
162
        return $this;
163
    }
164
165
    public function assertHeaderContains($header, $contains)
166
    {
167
        $this->assertHeader[$header] = $contains;
168
169
        return $this;
170
    }
171
172
    public function assertBodyContains($contains)
173
    {
174
        $this->assertBody = $contains;
175
176
        return $this;
177
    }
178
179
    /**
180
     * @return Response|ResponseInterface
181
     * @throws Exception\DefinitionNotFoundException
182
     * @throws Exception\GenericSwaggerException
183
     * @throws Exception\HttpMethodNotFoundException
184
     * @throws Exception\InvalidDefinitionException
185
     * @throws Exception\PathNotFoundException
186
     * @throws NotMatchedException
187
     * @throws StatusCodeNotMatchedException
188
     * @throws MessageException
189
     * @throws InvalidRequestException
190
     */
191
    public function send()
192
    {
193
        // Process URI based on the OpenAPI schema
194
        $uriSchema = new Uri($this->schema->getServerUrl());
195
196
        $this->psr7Request->getUri()
197
            ->withScheme($uriSchema->getScheme())
198
            ->withHost($uriSchema->getHost())
199
            ->withPort($uriSchema->getPort())
200
            ->withPath($uriSchema->getPath() . $this->psr7Request->getUri()->getPath());
201
202
        if (!preg_match("~^{$this->schema->getBasePath()}~",  $this->psr7Request->getUri()->getPath())) {
203
            $this->psr7Request->getUri()->withPath($this->schema->getBasePath() . $this->psr7Request->getUri()->getPath());
204
        }
205
206
        // Prepare Body to Match Against Specification
207
        $requestBody = $this->psr7Request->getBody();
208
        if (!empty($requestBody)) {
209
            $requestBody = $requestBody->getContents();
210
211
            $contentType = $this->psr7Request->getHeader("content-type");
212
            if (empty($contentType) || $contentType == "application/json") {
213
                $requestBody = json_decode($requestBody, true);
214
            } else {
215
                throw new InvalidRequestException("Cannot handle Content Type '$contentType'");
216
            }
217
        }
218
219
        // Check if the body is the expected before request
220
        $bodyRequestDef = $this->schema->getRequestParameters($this->psr7Request->getUri()->getPath(), $this->psr7Request->getMethod());
221
        $bodyRequestDef->match($requestBody);
222
223
        // Handle Request
224
        $response = $this->handleRequest($this->psr7Request);
225
        $responseHeader = $response->getHeaders();
226
        $responseBodyStr = (string) $response->getBody();
227
        $responseBody = json_decode($responseBodyStr, true);
228
        $statusReturned = $response->getStatusCode();
229
230
        // Assert results
231
        if ($this->statusExpected != $statusReturned) {
232
            throw new StatusCodeNotMatchedException(
233
                "Status code not matched: Expected {$this->statusExpected}, got {$statusReturned}",
234
                $responseBody
235
            );
236
        }
237
238
        $bodyResponseDef = $this->schema->getResponseParameters(
239
            $this->psr7Request->getUri()->getPath(),
240
            $this->psr7Request->getMethod(),
241
            $this->statusExpected
242
        );
243
        $bodyResponseDef->match($responseBody);
244
245
        foreach ($this->assertHeader as $key => $value) {
246
            if (!isset($responseHeader[$key]) || strpos($responseHeader[$key][0], $value) === false) {
247
                throw new NotMatchedException(
248
                    "Does not exists header '$key' with value '$value'",
249
                    $responseHeader
250
                );
251
            }
252
        }
253
254
        if (!empty($this->assertBody) && strpos($responseBodyStr, $this->assertBody) === false) {
255
            throw new NotMatchedException("Body does not contain '{$this->assertBody}'");
256
        }
257
258
        return $response;
259
    }
260
}
261