SeekquarrySeekquarry - Yioop Repo - Seekquarry

/ tests / WebSiteStatusLineTest.php
<?php
/**
 * SeekQuarry/Yioop --
 * Open Source Pure PHP Search Engine, Crawler, and Indexer
 *
 * Copyright (C) 2009 - 2026  Chris Pollett chris@pollett.org
 *
 * LICENSE:
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * END LICENSE
 *
 * @author Chris Pollett chris@pollett.org
 * @license https://www.gnu.org/licenses/ GPL3
 * @link https://www.seekquarry.com/
 * @copyright 2009 - 2026
 * @filesource
 */
namespace seekquarry\yioop\tests;

use seekquarry\atto\WebSite;
use seekquarry\yioop\library\UnitTest;

/**
 * Unit tests for the status line given to a response whose code set
 * headers but never said what the status was. What decides it is whether
 * a Location or a Refresh header was sent; what a header happens to be
 * carrying is not a header, and a picture whose name merely mentions a
 * location has not moved anywhere.
 *
 * @author Chris Pollett
 */
class WebSiteStatusLineTest extends UnitTest
{
    /**
     * The server whose choice of status line is under test.
     * @var WebSite
     */
    public $site;
    /**
     * The method that chooses the status line, reached through
     * reflection because a server keeps it to itself.
     * @var \ReflectionMethod
     */
    public $status_line;
    /**
     * The headers gathered so far, which the choice is made from,
     * reached through reflection because a server keeps them to itself.
     * @var \ReflectionProperty
     */
    public $header_data;
    /**
     * Builds the server without its constructor, which would want to
     * listen on a port that the status line has nothing to do with.
     */
    public function setUp()
    {
        $reflection = new \ReflectionClass(WebSite::class);
        $this->site = $reflection->newInstanceWithoutConstructor();
        /* what a class keeps to itself has been reachable through
           reflection without asking since PHP 8.1, and asking is an error
           as of 8.5, so nothing is asked for here. */
        $this->status_line = new \ReflectionMethod(WebSite::class,
            "statusLineForHeaders");
        $this->header_data = new \ReflectionProperty(WebSite::class,
            "header_data");
        $_SERVER['SERVER_PROTOCOL'] = "HTTP/1.1";
    }
    /**
     * Nothing to take down between test cases.
     */
    public function tearDown()
    {
    }
    /**
     * Asks what status line a given block of headers is given.
     *
     * @param string $headers the headers gathered so far
     * @return string the status line they are given
     */
    private function statusFor($headers)
    {
        $this->header_data->setValue($this->site, $headers);
        return $this->status_line->invoke($this->site);
    }
    /**
     * Sending a Location header says the thing asked for is somewhere
     * else, and that is answered as having moved.
     */
    public function locationHeaderMovedPermanentlyTestCase()
    {
        $this->assertEqual($this->statusFor(
            "Location: /elsewhere\x0D\x0A"),
            "HTTP/1.1 301 Moved Permanently\x0D\x0A",
            "a Location header says the thing has moved");
        $this->assertEqual($this->statusFor(
            "Content-Type: text/html\x0D\x0ALocation: /elsewhere\x0D\x0A"),
            "HTTP/1.1 301 Moved Permanently\x0D\x0A",
            "a Location header counts wherever in the block it stands");
        $this->assertEqual($this->statusFor(
            "location: /elsewhere\x0D\x0A"),
            "HTTP/1.1 301 Moved Permanently\x0D\x0A",
            "a header name is the same name however it is capitalised");
    }
    /**
     * Sending a Refresh header says the reader is to be sent on shortly.
     */
    public function refreshHeaderFoundTestCase()
    {
        $this->assertEqual($this->statusFor("Refresh: 5; url=/x\x0D\x0A"),
            "HTTP/1.1 302 Found\x0D\x0A",
            "a Refresh header says the reader is sent on shortly");
    }
    /**
     * A reply to what was asked for is answered as such.
     */
    public function ordinaryReplyIsOkayTestCase()
    {
        $this->assertEqual($this->statusFor("Content-Type: image/webp\x0D\x0A"),
            "HTTP/1.1 200 OK\x0D\x0A",
            "an ordinary reply is answered as one");
        $this->assertEqual($this->statusFor(""),
            "HTTP/1.1 200 OK\x0D\x0A",
            "headers saying nothing in particular are an ordinary reply");
    }
    /**
     * What a header carries is not itself a header. A picture whose name
     * mentions a location has not moved, and one whose name mentions a
     * refresh is not sending the reader on; both were served with a
     * redirect's status stamped over a picture, and browsers showed
     * nothing at all.
     */
    public function whatAHeaderCarriesIsNotAHeaderTestCase()
    {
        $this->assertEqual($this->statusFor("Content-Disposition: " .
            "filename=\"link_game_location1.webp\"\x0D\x0A"),
            "HTTP/1.1 200 OK\x0D\x0A",
            "a picture named for a location has not moved anywhere");
        $this->assertEqual($this->statusFor("Content-Disposition: " .
            "filename=\"page_refresh_button.png\"\x0D\x0A"),
            "HTTP/1.1 200 OK\x0D\x0A",
            "a picture named for a refresh sends nobody anywhere");
        $this->assertEqual($this->statusFor(
            "X-Note: the location of the refresh is here\x0D\x0A"),
            "HTTP/1.1 200 OK\x0D\x0A",
            "the words in a header's value decide nothing");
        $this->assertEqual($this->statusFor(
            "Content-Disposition: filename=\"a.png\"\x0D\x0A" .
            "Location: /elsewhere\x0D\x0A"),
            "HTTP/1.1 301 Moved Permanently\x0D\x0A",
            "a real Location header still counts beside other headers");
    }
    /**
     * The page sent with a request timeout is a complete, self-contained
     * HTML document that names the timeout and holds no site content,
     * since the timeout is exactly the real page not being finishable.
     */
    public function requestTimeoutPageIsSelfContainedTestCase()
    {
        $page_method = new \ReflectionMethod(WebSite::class,
            "requestTimeoutPage");
        $page = $page_method->invoke($this->site);
        $this->assertTrue(strpos($page, "<!DOCTYPE html>") === 0,
            "the timeout page is a whole HTML document");
        $this->assertTrue(strpos($page, "408") !== false,
            "the timeout page names the 408 status");
        $this->assertTrue(stripos($page, "too long") !== false,
            "the timeout page says the request took too long");
    }
}