Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Adds response reason phrases test against IANA. #234

Merged
merged 10 commits into from Apr 6, 2017
16 changes: 8 additions & 8 deletions src/Response.php
Expand Up @@ -42,9 +42,9 @@ class Response implements ResponseInterface
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-status',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM used',
226 => 'IM Used',
// REDIRECTION CODES
300 => 'Multiple Choices',
301 => 'Moved Permanently',
Expand All @@ -64,15 +64,15 @@ class Response implements ResponseInterface
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Request Entity Too Large',
414 => 'Request-URI Too Large',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Requested range not satisfiable',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot',
421 => 'Misdirected Request',
Expand All @@ -92,8 +92,8 @@ class Response implements ResponseInterface
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out',
505 => 'HTTP Version not supported',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
Expand Down
116 changes: 116 additions & 0 deletions test/ResponseTest.php
Expand Up @@ -66,6 +66,122 @@ public function testReasonPhraseDefaultsToStandards()
$this->assertEquals('Unprocessable Entity', $response->getReasonPhrase());
}

protected function updateAndLoadIanaHttpStatusCodes()
{
set_error_handler(function ($errno, $errstr) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this block - phpunit has a flag for raising exceptions on notices

throw new \ErrorException($errstr, 0, $errno);
});

$ianaHttpStatusCodes = new \DOMDocument();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requires ext-xml in the dev dependencies

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requires ext-dom.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import DOMDocument. (I can do that at merge)

$validXml = false;
$errorMessage = null;
$httpStatus = 0;

try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be removed

$options = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be in a variable

'http' => [
'method' => 'GET',
'timeout' => 30,
],
];

$contents = file_get_contents(
'https://www.iana.org/assignments/http-status-codes/http-status-codes.xml',
false,
stream_context_create($options)
);

if ($http_response_header) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable wasn't defined before

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (preg_match('/^HTTP\/[0-9\.]+\s*([0-9]+)\s*.+$/i', $http_response_header[0], $matches) > 0) {
$httpStatus = $matches[1];
}
}

if ($httpStatus == 200) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (200 !== $httpStatus) { self::fail(...); }

$ianaHttpStatusCodes->loadXml($contents);
$validXml = $ianaHttpStatusCodes->relaxNGValidate(__DIR__ . '/TestAsset/http-status-codes.rng');

if ($validXml) {
file_put_contents(__DIR__ . '/TestAsset/http-status-codes.xml', $contents);
print 'IANA "http-status-codes.xml" updated successful' . "\n";
}
}
} catch (\Exception $e) {
$errorMessage = $e->getMessage();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this logic (the entire try/catch)

}


if (! $validXml) {
if ($errorMessage) {
print 'Error on IANA "http-status-codes.xml" update. Error: ' . $errorMessage . "\n";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self::fail() is sufficient :-\

}

try {
$ianaHttpStatusCodes->load(__DIR__ . '/TestAsset/http-status-codes.xml');
$validXml = $ianaHttpStatusCodes->relaxNGValidate(__DIR__ . '/TestAsset/http-status-codes.rng');
} catch (\Exception $e) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove - let it bubble up

$errorMessage = $e->getMessage();
}
}

restore_error_handler();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove


if (! $validXml) {
$this->markTestIncomplete(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self::fail() is better here.

'Invalid IANA "http-status-codes.xml". Error: ' . $errorMessage
);
$ianaHttpStatusCodes = null;
}

return $ianaHttpStatusCodes;
}

public function ianaCodesReasonPhrasesProvider()
{
$ianaHttpStatusCodes = $this->updateAndLoadIanaHttpStatusCodes();

if (! $ianaHttpStatusCodes) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method should never return null

return null;
}

$ianaCodesReasonPhrases = [];

$xpath = new \DomXPath($ianaHttpStatusCodes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import DOMXPath. (I can do that at merge)

$xpath->registerNamespace('ns', 'http://www.iana.org/assignments');

$records = $xpath->query('//ns:record');

foreach ($records as $record) {
$value = $xpath->query('.//ns:value', $record)->item(0)->nodeValue;
$description = $xpath->query('.//ns:description', $record)->item(0)->nodeValue;

if ($description === 'Unassigned' || $description === '(Unused)') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in_array()

continue;
}

$range = preg_match('/^([0-9]+)\s*\-\s*([0-9]+)$/', $value, $matches);

if (! $range) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap the conditional to avoid the negation. Also, $range variable is redundant

$ianaCodesReasonPhrases[] = [$value, $description];
} else {
for ($value = $matches[1]; $value <= $matches[2]; $value++) {
$ianaCodesReasonPhrases[] = [$value, $description];
}
}
}

return $ianaCodesReasonPhrases;
}

/**
* @dataProvider ianaCodesReasonPhrasesProvider
*/
public function testReasonPhraseDefaultsAgainstIana($code, $reasonPhrase)
{
$response = $this->response->withStatus($code);
$this->assertEquals($reasonPhrase, $response->getReasonPhrase());
}

public function testCanSetCustomReasonPhrase()
{
$response = $this->response->withStatus(422, 'Foo Bar!');
Expand Down
31 changes: 31 additions & 0 deletions test/TestAsset/http-status-codes.rng
@@ -0,0 +1,31 @@
<?xml version='1.0'?>
<grammar xmlns="http://relaxng.org/ns/structure/1.0"
datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes"
ns="http://www.iana.org/assignments">

<include href="iana-registry.rng"/>

<start>
<element name="registry">
<ref name="registryMeta"/>
<element name="registry">
<ref name="registryMeta"/>
<zeroOrMore>
<element name="record">
<optional>
<attribute name="date"><ref name="genericDate"/></attribute>
</optional>
<optional>
<attribute name="updated"><ref name="genericDate"/></attribute>
</optional>
<element name="value"><ref name="genericRange"/></element>
<element name="description"><text/></element>
<ref name="references"/>
</element>
</zeroOrMore>
</element>
<ref name="people"/>
</element>
</start>

</grammar>