get($file_url); Assert::same($file_url, $file->url); Assert::null($file->next_url); Assert::null($file->etag); Assert::same(file_get_contents(__FILE__), $file->body); } function testGet() { $cache = new GitHubCache(); $cache->connection = new MockConnection(); $one = $cache->get('one.html'); Assert::same('https://api.github.com/one.html', $one->url); Assert::null($one->next_url); Assert::same("One\n", $one->body); $two = $cache->get('two.html'); Assert::same('https://api.github.com/two.html', $two->url); // TODO: Should resolve the link header. Assert::same('two2.html', $two->next_url); Assert::same("[1]", $two->body); } function testGetWithEtag() { $cache = new GitHubCache(); $mock_connection = new MockConnection(); $cache->connection = $mock_connection; $one = $cache->get('one.html'); Assert::same(array(), $mock_connection->last_headers); Assert::same("One\n", $one->body); $one = $cache->get('one.html'); Assert::same(array('If-None-Match: abcdefg'), $mock_connection->last_headers); Assert::same("One\n", $one->body); } function testGetWithRedirect() { $cache = new GitHubCache(); $mock_connection = new MockConnection(); $cache->connection = $mock_connection; $one = $cache->get('one_redirect.html'); Assert::same('https://api.github.com/one.html', $one->url); Assert::same("One\n", $one->body); Assert::exception(function() use($cache) { $cache->get('redirect_loop1'); }, 'RuntimeException', '#redirect#'); Assert::exception(function() use($cache) { $cache->get('redirect_loop2'); }, 'RuntimeException', '#redirect#'); Assert::exception(function() use($cache) { $cache->get('redirect_loop3'); }, 'RuntimeException', '#redirect#'); } function testGet404() { $cache = new GitHubCache(); $cache->connection = new MockConnection(); Assert::Exception(function() use($cache) { $cache->get('non-existant.html'); }, 'RuntimeException'); } function testGetJson() { $cache = new GitHubCache(); $cache->connection = new MockConnection(); Assert::equal((object) array('thing' => 10), $cache->getJson('json')); Assert::null($cache->getJson('json_null')); Assert::exception(function() use($cache) { $cache->getJson('one.html'); }, 'RuntimeException'); } function testIterator() { $cache = new GitHubCache(); $cache->connection = new MockConnection(); $values = iterator_to_array($cache->iterate('two.html')); Assert::same(array(1,2), $values); } function testResolveUrl() { $cache = new GitHubCache(); Assert::same('http://www.example.com/', $cache->resolveUrl('http://www.example.com/')); Assert::same('https://www.example.com/', $cache->resolveUrl('//www.example.com/')); Assert::same('https://www.example.com/foobar.html', $cache->resolveUrl('//www.example.com/foobar.html')); Assert::same('https://www.example.com/foobar.html?a=b', $cache->resolveUrl('//www.example.com/foobar.html?a=b')); Assert::same('http://api.github.com/thing.html', $cache->resolveUrl('http:thing.html')); Assert::same('http://api.github.com/thing.html', $cache->resolveUrl('http:/thing.html')); Assert::same('https://api.github.com/foobar.html', $cache->resolveUrl('/foobar.html')); Assert::same('https://api.github.com/foo/bar.html', $cache->resolveUrl('foo/bar.html')); Assert::same('https://api.github.com/foobar.html?a=b', $cache->resolveUrl('foobar.html?a=b')); Assert::same('https://api.github.com/.', $cache->resolveUrl('.')); } } class MockConnection { var $responses = array( 'https://api.github.com/one_redirect.html' => array( 'code' => 301, 'reason_phrase' => 'Moved', 'headers' => array('location' => 'https://api.github.com/one.html'), 'body' => "", ), 'https://api.github.com/redirect_loop1' => array( 'code' => 301, 'headers' => array('location' => 'https://api.github.com/redirect_loop1'), ), 'https://api.github.com/redirect_loop2' => array( 'code' => 301, 'headers' => array('location' => 'https://api.github.com/redirect_loop1'), ), 'https://api.github.com/redirect_loop3' => array( 'code' => 301, 'headers' => array('location' => 'https://api.github.com/redirect_loop3a'), ), 'https://api.github.com/redirect_loop3a' => array( 'code' => 301, 'headers' => array('location' => 'https://api.github.com/redirect_loop3'), ), 'https://api.github.com/one.html' => array( 'code' => 200, 'reason_phrase' => 'blah blah blah', 'headers' => array('etag' => 'abcdefg'), 'body' => "One\n", ), 'https://api.github.com/two.html' => array( 'code' => 200, 'reason_phrase' => 'blah blah blah', 'headers' => array('link' => array(array('url' => 'two2.html', 'rel' => 'next'))), 'body' => "[1]", ), 'https://api.github.com/two2.html' => array( 'code' => 200, 'reason_phrase' => 'blah blah blah', 'headers' => array(), 'body' => "[2]", ), 'https://api.github.com/json' => array( 'code' => '200', 'reason_phrase' => 'Success', 'headers' => array(), 'body' => '{"thing":10}', ), 'https://api.github.com/json_null' => array( 'code' => '200', 'reason_phrase' => 'Success', 'headers' => array(), 'body' => 'null', ), 404 => array( 'code' => 404, 'reason_phrase' => 'not found', 'headers' => array(), 'body' => 'not found', ), ); var $last_headers; function get($url, $headers = array()) { $this->last_headers = $headers; $response_values = array_get($this->responses, $url) ?: $this->responses[404]; if (!empty($response_values['headers']['etag'])) { $etag = $response_values['headers']['etag']; foreach($headers as $header) { if (preg_match('@^If-None-Match:\s*(.*)$@', $header, $match)) { if ($match[1] === $etag) { $response = new GitHubCache_Response(); $response->code = 304; return $response; } } } } $response = new GitHubCache_Response(); foreach($response_values as $key => $value) { $response->{$key} = $value; } return $response; } } class GitHubCache_ConnectionTest extends TestBase { function testGet() { $connection = new GitHubCache_Connection(); $response = $connection->get('file://'.__FILE__); Assert::same('200', $response->code); Assert::same(file_get_contents(__FILE__), $response->body); } function testParseResponse() { $response = GitHubCache_Connection::parseResponse( "HTTP/1.1 200 OK\r Date: Tue, 19 Jul 2016 12:00:00 GMT\r Server: Apache/2.2.15 (Red Hat)\r Accept-Ranges: bytes\r Connection: close\r Content-Type: text/html", "Line 1\r\nLine 2" ); Assert::same('200', $response->code); Assert::same('OK', $response->reason_phrase); Assert::same('text/html', $response->headers['content-type']); Assert::same("Line 1\nLine 2", $response->body); } function testParseInvalidResponse() { Assert::exception(function() { GitHubCache_Connection::parseResponse("Blah blah", "Blah blah"); }, 'RuntimeException'); } function testMessageHeaders() { Assert::same( array( 'date' => 'Tue, 19 Jul 2016 08:58:40 GMT', 'server' => 'Apache/2.2.15 (Red Hat)', 'accept-ranges' => 'bytes', 'connection' => 'close', 'content-type' => 'text/html', 'link' => array(array('url' => 'www.example.com', 'rel' => 'something')), ), GitHubCache_Connection::parseMessageHeaders( 'Date: Tue, 19 Jul 2016 08:58:40 GMT Server: Apache/2.2.15 (Red Hat) Accept-Ranges: bytes Connection: close Content-Type: text/html Link: ;rel=something' ) ); } function testDuplicateMessageHeaders() { Assert::same( array( 'val' => '1', ), GitHubCache_Connection::parseMessageHeaders( 'Val: 1 Error thing' ) ); $handlers = Log::$log->getHandlers(); Assert::true($handlers[0]->hasRecordThatContains( 'Error parsing http header', \Monolog\Logger::ERROR )); } function testParseLinkHeader() { Assert::same( array(array( 'url' => 'http://www.cern.ch/TheBook/chapter2', 'rel' => 'Previous', )), GitHubCache_Connection::parseLinkHeader( '; rel="Previous"' ) ); Assert::same( array(array( 'url' => 'mailto:timbl@w3.org', 'rev' => 'Made', 'title' => 'Tim Berners-Lee', )), GitHubCache_Connection::parseLinkHeader( '; rev="Made"; title="Tim Berners-Lee"' ) ); Assert::same( array( array( 'url' => '../media/contrast.css', 'rel' => 'stylesheet alternate', 'title' => 'High Contrast Styles', 'type' => 'text/css', 'media' => 'screen', ), array( 'url' => '../media/print.css', 'rel' => 'stylesheet', 'type' => 'text/css', 'media' => 'print', ), ), GitHubCache_Connection::parseLinkHeader(' <../media/contrast.css>; rel="stylesheet alternate"; title="High Contrast Styles"; type="text/css"; media="screen", <../media/print.css>; rel="stylesheet"; type="text/css"; media="print" ') ); Assert::same( array( array( 'url' => 'sec-12-glossary.xml', 'rel' => 'glossary', 'anchor' => '#sec12', ), ), GitHubCache_Connection::parseLinkHeader( '; rel="glossary"; anchor="#sec12"' ) ); Assert::same( array( array( 'url' => 'link.html', ), ), GitHubCache_Connection::parseLinkHeader( '' ) ); Assert::same( array( array( 'url' => 'link1.html', ), array( 'url' => 'link2.html', ), ), GitHubCache_Connection::parseLinkHeader( ',' ) ); Assert::same( array( array( 'url' => 'link.html', 'thing' => '";"', 'thing2' => '', 'thing3' => '', ), ), GitHubCache_Connection::parseLinkHeader( ' ; thing="\";\"" ; thing2="" ; thing3=' ) ); } function testParseInvalidLinkHeader() { Assert::exception(function() { GitHubCache_Connection::parseLinkHeader('http://example.com/'); }, 'RuntimeException', '#Error parsing link#'); } function testParseLinkHeaderDuplicateKey() { Assert::same( array(array( 'url' => 'http://example.com/', 'rel' => '1', )), GitHubCache_Connection::parseLinkHeader(';rel=1;rel=2') ); $handlers = Log::$log->getHandlers(); Assert::true($handlers[0]->hasRecordThatContains( 'Duplicate link key', \Monolog\Logger::ERROR )); } } $test = new GitHubCacheTest(); $test->run(); $test = new GitHubCache_ConnectionTest(); $test->run();