Completed
Pull Request — master (#46)
by
unknown
05:21
created

TutuContext::responseShouldHaveFollowingHedaers()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 1
1
<?php
2
3
use Behat\Behat\Context\SnippetAcceptingContext;
4
use Behat\Behat\Hook\Scope\AfterScenarioScope;
5
use Behat\Gherkin\Node\PyStringNode;
6
use Behat\Gherkin\Node\TableNode;
7
use Behat\MinkExtension\Context\RawMinkContext;
8
use Behat\Testwork\Tester\Result\TestResult;
9
use GuzzleHttp\Exception\RequestException;
10
use Symfony\Component\Filesystem\Filesystem;
11
use Symfony\Component\Process\Process;
12
use Symfony\Component\Process\ProcessBuilder;
13
14
class TutuContext extends RawMinkContext implements SnippetAcceptingContext
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
15
{
16
    const MAX_PHP_SERVER_RESTARTS = 5;
17
18
    /**
19
     * @var Process
20
     */
21
    private $tutuProcess;
22
23
    /**
24
     * @var int
25
     */
26
    private $tutuPort;
27
28
    /**
29
     * @var string;
30
     */
31
    private $tutuHost;
32
33
    /**
34
     * @var string
35
     */
36
    private $webPath;
37
38
    /**
39
     * @var string
40
     */
41
    private $workDir;
42
43
    /**
44
     * @var int
45
     */
46
    private $httpCallAttemptsPerScenario = 0;
47
48
    public function __construct($webPath)
49
    {
50
        if (!file_exists($webPath)) {
51
            throw new \InvalidArgumentException(sprintf("Path %s does not exist", $webPath));
52
        }
53
        $this->webPath = $webPath;
54
        $this->headersToRemove = [];
55
    }
56
57
    /**
58
     * @BeforeScenario
59
     */
60
    public function createWorkDir()
61
    {
62
        $this->workDir = sprintf(
63
            '%s/%s',
64
            sys_get_temp_dir(),
65
            uniqid('TuTuContext')
66
        );
67
        $fs = new Filesystem();
68
        $fs->mkdir($this->workDir, 0777);
69
        chdir($this->workDir);
70
        $fs->mkdir($this->workDir . '/resources');
71
        $fs->mkdir($this->workDir . '/config');
72
        $fs->dumpFile($this->workDir . '/config/responses.yml', '');
73
    }
74
75
    /** @AfterScenario */
76
    public function printLastResponseWhenScenarioFail(AfterScenarioScope $scope)
77
    {
78
        if ($scope->getTestResult()->getResultCode() === TestResult::FAILED) {
79
            echo "Last response:\n";
80
            echo "Code " . $this->getSession()->getStatusCode() . "\n";
81
            $this->printLastResponse();
82
        }
83
    }
84
85
    /** @AfterScenario */
86
    public function resetHttpCallAttempts()
87
    {
88
        $this->httpCallAttemptsPerScenario = 0;
89
    }
90
91
    /**
92
     * @AfterScenario
93
     */
94
    public function removeWorkDir()
95
    {
96
        $fs = new Filesystem();
97
        $fs->remove($this->workDir);
98
    }
99
100
    /**
101
     * @AfterScenario
102
     */
103
    public function killEveryProcessRunningOnTuTuPort()
104
    {
105
        $killerProcess = new Process(sprintf("kill $(lsof -t -i:%d)", (int) $this->tutuPort));
106
        $killerProcess->run();
107
    }
108
109
    /**
110
     * @Given there is a empty responses config file :fileName
111
     */
112
    public function thereIsAEmptyFile($fileName)
113
    {
114
        $this->thereIsARoutingFileWithFollowingContent($fileName, new PyStringNode([], 0));
115
    }
116
117
    /**
118
     * @Given there is a resource file :fileName with following content
119
     */
120
    public function thereIsAResourceFileWithFollowingContent($fileName, PyStringNode $fileContent)
121
    {
122
        $fs = new Filesystem();
123
        $resourceFilePath = $this->workDir . '/resources/' . $fileName;
124
        if ($fs->exists($resourceFilePath)) {
125
            $fs->remove($resourceFilePath);
126
        }
127
128
        $fs->dumpFile($resourceFilePath, (string) $fileContent);
129
    }
130
131
    /**
132
     * @Given there is a :filePath file with following content
133
     */
134
    public function thereIsAFileWithFollowingContent($filePath, PyStringNode $fileContent)
135
    {
136
        $fs = new Filesystem();
137
        $fs->dumpFile($this->workDir . '/' . $filePath, (string) $fileContent);
138
    }
139
140
    /**
141
     * @Given there is a routing file :fileName with following content:
142
     * @Given there is a responses config file :fileName with following content:
143
     * @Given there is a config file :fileName with following content:
144
     */
145
    public function thereIsARoutingFileWithFollowingContent($fileName, PyStringNode $fileContent)
146
    {
147
        $fs = new Filesystem();
148
        $configFilePath = $this->workDir . '/config/' . $fileName;
149
        if ($fs->exists($configFilePath)) {
150
            $fs->remove($configFilePath);
151
        }
152
153
        $content = (string) $fileContent;
154
        $content = str_replace('%workDir%', $this->workDir, $content);
155
156
        $fs->dumpFile($configFilePath, $content);
157
    }
158
159
    /**
160
     * @Given TuTu is running on host :host at port :port
161
     */
162
    public function tutuIsRunningOnHostAtPort($host, $port)
163
    {
164
        $this->tutuPort = $port;
165
        $this->tutuHost = $host;
166
        $this->killEveryProcessRunningOnTuTuPort();
167
        $builder = new ProcessBuilder([
168
            PHP_BINARY,
169
            '-S',
170
            sprintf('%s:%s > /Users/norzechowicz/Workspace/PHP/coduo/TuTu/log.txt', $host, $port)
171
        ]);
172
173
        if (file_exists($this->workDir . '/config/config.yml')) {
174
            $builder->setEnv('tutu_config', $this->workDir . '/config/config.yml');
175
        }
176
177
        $builder->setEnv('tutu_responses', $this->workDir . '/config/responses.yml');
178
        $builder->setEnv('tutu_resources', $this->workDir . '/resources');
179
        $builder->setWorkingDirectory($this->webPath);
180
        $builder->setTimeout(null);
181
182
        $this->tutuProcess = $builder->getProcess();
183
        $this->tutuProcess->start();
184
        sleep(1);
185
    }
186
187
    /**
188
     * @Then print last response
189
     */
190
    public function printLastResponse()
191
    {
192
        $content = $this->getSession()->getDriver()->getContent();
193
194
        echo "\n\033[36m|  " . strtr($content, array("\n" => "\n|  ")) . "\033[0m\n\n";
195
    }
196
197
    /**
198
     * @Then response status code should be :expectedStatus
199
     */
200
    public function responseStatusCodeShouldBe($expectedStatus)
201
    {
202
        $status = $this->getSession()->getStatusCode();
203
        if ($status !== (int)$expectedStatus) {
204
            throw new \RuntimeException(sprintf("Status %d is not equal to %d.", $status, (int) $expectedStatus));
205
        }
206
    }
207
208
    /**
209
     * @Then the response content should be equal to:
210
     */
211
    public function theResponseContentShouldBeEqualTo(PyStringNode $expectedContent)
212
    {
213
        $content = $this->getSession()->getDriver()->getContent();
214
        if ((string) $content !== (string) $expectedContent) {
215
            throw new \RuntimeException("Content is different than expected.");
216
        }
217
    }
218
219
    /**
220
     * @Then the response content should match expression:
221
     */
222
    public function theResponseContentShouldMatchExpression(PyStringNode $pattern)
223
    {
224
        $content = $this->getSession()->getDriver()->getContent();
225
        expect($content)->toMatch('/'.$pattern.'/');
226
    }
227
228
    /**
229
     * @Then the response content should be empty
230
     */
231
    public function theResponseContentShouldBeEmpty()
232
    {
233
        $content = $this->getSession()->getDriver()->getContent();
234
        if (!empty($content)) {
235
            throw new \RuntimeException("Content is not empty.");
236
        }
237
    }
238
239
    /**
240
     * @Then response should have following hedaers:
241
     */
242
    public function responseShouldHaveFollowingHedaers(TableNode $responseHeaders)
243
    {
244
        $headers = $this->getSession()->getResponseHeaders();
245
246
        foreach ($responseHeaders->getHash() as $header) {
247
            if (!array_key_exists($header['Name'], $headers)) {
248
                throw new \RuntimeException(sprintf("There is no \"%s\" header in response.", $header['Name']));
249
            }
250
251
            if ($headers[$header['Name']][0] !== $header['Value']) {
252
                throw new \RuntimeException(sprintf(
253
                    "Header \"%s\" value \"%s\" is not equal to \"%s\".",
254
                    $header['Name'],
255
                    $headers[$header['Name']][0],
256
                    $header['Value']
257
                ));
258
            }
259
        }
260
    }
261
262
263
    /**
264
     * @When http client sends :method request on :url
265
     */
266
    public function httpClientSendRequestOn($method, $url)
267
    {
268
        $this->makeHttpCall($method, $url);
269
    }
270
271
    /**
272
     * @When http client sends :method request on :url with following parameters:
273
     */
274
    public function httpClientSendPostRequestOnWithFollowingParameters($method, $url, TableNode $parametersTable)
275
    {
276
        $parameters = [];
277
        foreach ($parametersTable->getHash() as $parameterData) {
278
            $parameters[$parameterData['Parameter']] = $parameterData['Value'];
279
        }
280
281
        $this->makeHttpCall($method, $url, $parameters);
282
    }
283
284
    /**
285
     * @When http client sends :method request on :url with following headers
286
     */
287
    public function httpClientSendGetRequestOnWithFollowingHeaders($method, $url, TableNode $headersTable)
288
    {
289
        $session = $this->getSession();
290
        $client = $session->getDriver()->getClient();
291
        $headers = [];
292
        foreach ($headersTable->getHash() as $headerData) {
293
            $client->setHeader($headerData['Header'], $headerData['Value']);
294
            $headers[] = $headerData['Header'];
295
        }
296
297
        $this->makeHttpCall($method, $url, [], $headers);
298
    }
299
300
    /**
301
     * @When http client sends :method request on :url with body
302
     */
303
    public function httpClientSendGetRequestOnWithBody($method, $url, PyStringNode $body)
304
    {
305
        $this->makeHttpCall($method, $url, [], [], (string) $body);
306
    }
307
308
    /**
309
     * @param $method
310
     * @param $url
311
     * @param array $parameters
312
     * @param null $body
313
     */
314
    private function makeHttpCall($method, $url, array $parameters = [], array $headers = [], $body = null)
315
    {
316
        $session = $this->getSession();
317
        $client = $session->getDriver()->getClient();
318
        $client->followRedirects(false);
319
        try {
320
            $client->request(
321
                $method,
322
                $url,
323
                $parameters, // parameters
324
                [], // files
325
                $headers, // $_SERVER
326
                $body
327
            );
328
        } catch (RequestException $exception) {
329
            if (strpos($exception->getMessage(), 'cURL error 7') !== false) {
330
                if ($this->httpCallAttemptsPerScenario < self::MAX_PHP_SERVER_RESTARTS) {
331
                    $this->httpCallAttemptsPerScenario++;
332
                    $this->tutuIsRunningOnHostAtPort($this->tutuHost, $this->tutuPort);
333
                    $this->makeHttpCall($method, $url, $parameters, $headers, $body);
334
                    return ;
335
                }
336
            }
337
338
            throw $exception;
339
        }
340
    }
341
}
342