Skip to content

Commit d01a1ee

Browse files
committed
added HttpAssert
1 parent b8ac5d7 commit d01a1ee

File tree

3 files changed

+476
-0
lines changed

3 files changed

+476
-0
lines changed

src/Framework/HttpAssert.php

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
<?php
2+
3+
/**
4+
* This file is part of the Nette Tester.
5+
* Copyright (c) 2009 David Grudl (https://davidgrudl.com)
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace Tester;
11+
12+
use function curl_error, curl_exec, curl_getinfo, curl_init, curl_setopt, explode, is_int, is_string, rtrim, str_contains, strtoupper, substr, trim;
13+
14+
15+
/**
16+
* HTTP testing helpers.
17+
*/
18+
class HttpAssert
19+
{
20+
private function __construct(
21+
private string $body,
22+
private int $code,
23+
private array $headers,
24+
) {
25+
}
26+
27+
28+
/**
29+
* Creates HTTP request, executes it and returns HttpTest instance for chaining expectations.
30+
*/
31+
public static function fetch(
32+
string $url,
33+
string $method = 'GET',
34+
array $headers = [],
35+
array $cookies = [],
36+
bool $follow = false,
37+
?string $body = null,
38+
): self
39+
{
40+
$ch = curl_init();
41+
curl_setopt($ch, CURLOPT_URL, $url);
42+
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
43+
curl_setopt($ch, CURLOPT_HEADER, true);
44+
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $follow);
45+
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method));
46+
47+
if ($headers) {
48+
$headerList = [];
49+
foreach ($headers as $key => $value) {
50+
if (is_int($key)) {
51+
$headerList[] = $value;
52+
} else {
53+
$headerList[] = "$key: $value";
54+
}
55+
}
56+
curl_setopt($ch, CURLOPT_HTTPHEADER, $headerList);
57+
}
58+
59+
if ($body !== null) {
60+
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
61+
}
62+
63+
if ($cookies) {
64+
$cookieString = '';
65+
foreach ($cookies as $name => $value) {
66+
$cookieString .= "$name=$value; ";
67+
}
68+
curl_setopt($ch, CURLOPT_COOKIE, rtrim($cookieString, '; '));
69+
}
70+
71+
$response = curl_exec($ch);
72+
if ($response === false) {
73+
throw new \Exception('HTTP request failed: ' . curl_error($ch));
74+
}
75+
76+
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
77+
$res = new self(
78+
substr($response, $headerSize),
79+
curl_getinfo($ch, CURLINFO_HTTP_CODE),
80+
[],
81+
);
82+
83+
$headerString = substr($response, 0, $headerSize);
84+
foreach (explode("\r\n", $headerString) as $line) {
85+
if (str_contains($line, ':')) {
86+
[$name, $value] = explode(':', $line, 2);
87+
$res->headers[strtolower(trim($name))] = trim($value);
88+
}
89+
}
90+
91+
return $res;
92+
}
93+
94+
95+
/**
96+
* Asserts HTTP response code matches expectation.
97+
*/
98+
public function expectCode(int|\Closure $expected): self
99+
{
100+
if ($expected instanceof \Closure) {
101+
Assert::true($expected($this->code), 'HTTP status code validation failed');
102+
} else {
103+
Assert::same($expected, $this->code, 'HTTP status code validation failed');
104+
}
105+
106+
return $this;
107+
}
108+
109+
110+
/**
111+
* Asserts HTTP response code does not match expectation.
112+
*/
113+
public function denyCode(int|\Closure $expected): self
114+
{
115+
if ($expected instanceof \Closure) {
116+
Assert::false($expected($this->code), 'HTTP status code validation failed');
117+
} else {
118+
Assert::notSame($expected, $this->code, 'HTTP status code validation failed');
119+
}
120+
121+
return $this;
122+
}
123+
124+
125+
/**
126+
* Asserts HTTP response header matches expectation.
127+
*/
128+
public function expectHeader(
129+
string $name,
130+
string|\Closure|null $expected = null,
131+
?string $contains = null,
132+
?string $matches = null,
133+
): self
134+
{
135+
$headerValue = $this->headers[strtolower($name)] ?? null;
136+
if (!isset($headerValue)) {
137+
Assert::fail("Header '$name' should exist");
138+
} elseif (is_string($expected)) {
139+
Assert::same($expected, $headerValue, "Header '$name' validation failed");
140+
} elseif ($expected instanceof \Closure) {
141+
Assert::true($expected($headerValue), "Header '$name' validation failed");
142+
} elseif ($contains !== null) {
143+
Assert::contains($contains, $headerValue, "Header '$name' validation failed");
144+
} elseif ($matches !== null) {
145+
Assert::match($matches, $headerValue, "Header '$name' validation failed");
146+
}
147+
148+
return $this;
149+
}
150+
151+
152+
/**
153+
* Asserts HTTP response header does not match expectation.
154+
*/
155+
public function denyHeader(
156+
string $name,
157+
string|\Closure|null $expected = null,
158+
?string $contains = null,
159+
?string $matches = null,
160+
): self
161+
{
162+
$headerValue = $this->headers[strtolower($name)] ?? null;
163+
if (!isset($headerValue)) {
164+
return $this;
165+
}
166+
167+
if (is_string($expected)) {
168+
Assert::notSame($expected, $headerValue, "Header '$name' validation failed");
169+
} elseif ($expected instanceof \Closure) {
170+
Assert::falsey($expected($headerValue), "Header '$name' validation failed");
171+
} elseif ($contains !== null) {
172+
Assert::notContains($contains, $headerValue, "Header '$name' validation failed");
173+
} elseif ($matches !== null) {
174+
Assert::notMatch($matches, $headerValue, "Header '$name' validation failed");
175+
} else {
176+
Assert::fail("Header '$name' should not exist");
177+
}
178+
179+
return $this;
180+
}
181+
182+
183+
/**
184+
* Asserts HTTP response body matches expectation.
185+
*/
186+
public function expectBody(
187+
string|\Closure|null $expected = null,
188+
?string $contains = null,
189+
?string $matches = null,
190+
): self
191+
{
192+
if (is_string($expected)) {
193+
Assert::same($expected, $this->body, 'Body validation failed');
194+
} elseif ($expected instanceof \Closure) {
195+
Assert::true($expected($this->body), 'Body validation failed');
196+
} elseif ($contains !== null) {
197+
Assert::contains($contains, $this->body, 'Body validation failed');
198+
} elseif ($matches !== null) {
199+
Assert::match($matches, $this->body, 'Body validation failed');
200+
}
201+
202+
return $this;
203+
}
204+
205+
206+
/**
207+
* Asserts HTTP response body does not match expectation.
208+
*/
209+
public function denyBody(
210+
string|\Closure|null $expected = null,
211+
?string $contains = null,
212+
?string $matches = null,
213+
): self
214+
{
215+
if (is_string($expected)) {
216+
Assert::notSame($expected, $this->body, 'Body validation failed');
217+
} elseif ($expected instanceof \Closure) {
218+
Assert::falsey($expected($this->body), 'Body validation failed');
219+
} elseif ($contains !== null) {
220+
Assert::notContains($contains, $this->body, 'Body validation failed');
221+
} elseif ($matches !== null) {
222+
Assert::notMatch($matches, $this->body, 'Body validation failed');
223+
}
224+
return $this;
225+
}
226+
}

src/bootstrap.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
require __DIR__ . '/Framework/TestCase.php';
1717
require __DIR__ . '/Framework/FileMutator.php';
1818
require __DIR__ . '/Framework/Expect.php';
19+
require __DIR__ . '/Framework/HttpAssert.php';
1920
require __DIR__ . '/CodeCoverage/Collector.php';
2021
require __DIR__ . '/Runner/Job.php';
2122

0 commit comments

Comments
 (0)