<?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
 * @package seek_quarry\tests
 */
namespace seekquarry\yioop\tests;

use seekquarry\yioop\configs as C;
use seekquarry\yioop\controllers\StaticController;
use seekquarry\yioop\library as L;
use seekquarry\yioop\library\UnitTest;

/**
 * Checks how the static controller picks the group whose wiki pages it
 * serves. A domain an admin routes to a landing group is served through
 * this controller, and the router hands it that group's id on the
 * ROUTE_GROUP_ID server variable; with no route, or a malformed one, the
 * world-readable Public group must be served instead. These cases pin
 * that resolution down so a stray or forged value cannot select an
 * arbitrary group.
 *
 * @author Chris Pollett
 */
class StaticControllerTest extends UnitTest
{
    /**
     * The controller whose group resolver is under test.
     * @var StaticController
     */
    public $controller;
    /**
     * Builds a controller for each test case without running its
     * constructor, since the group resolver only reads the request's
     * server variables and needs none of the controller's normal set-up.
     */
    public function setUp()
    {
        $reflection = new \ReflectionClass(StaticController::class);
        $this->controller = $reflection->newInstanceWithoutConstructor();
    }
    /**
     * Clears the routed-group server variable so one case does not leak
     * into the next.
     */
    public function tearDown()
    {
        unset($_SERVER['ROUTE_GROUP_ID']);
    }
    /**
     * A page served this way says it is a wiki page being read and
     * names itself and its group, so the body carries the same classes
     * it would were the page served through its group.
     */
    public function staticPageSaysItIsAWikiPageBeingReadTestCase()
    {
        $reading = $this->controller->readingWikiPageData("Privacy", 2,
            "Public");
        $this->assertEqual($reading['MODE'], "read",
            "the page says it is being read");
        $this->assertEqual($reading['PAGE_NAME'], "Privacy",
            "and names itself");
        $this->assertEqual($reading['GROUP']['GROUP_NAME'], "Public",
            "and names the group it belongs to");
        $this->assertEqual($reading['GROUP']['GROUP_ID'], 2,
            "and says which group that is");
    }
    /**
     * The group named is the one the domain routes to, not the public
     * one, so two domains serving the same page name give the body two
     * different group classes.
     */
    public function groupNamedIsTheOneTheDomainRoutesToTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = 7;
        $routed = $this->controller->staticGroupId();
        $this->assertEqual($routed, 7,
            "the domain's own group is the one served");
        $reading = $this->controller->readingWikiPageData("Privacy",
            $routed, "Their Group");
        $this->assertEqual($reading['GROUP']['GROUP_NAME'],
            "Their Group", "and its name is what the page carries");
        $this->assertEqual($reading['GROUP']['GROUP_ID'], 7,
            "along with which group it is");
        unset($_SERVER['ROUTE_GROUP_ID']);
        $reading = $this->controller->readingWikiPageData("Privacy",
            $this->controller->staticGroupId(), "Public");
        $this->assertEqual($reading['GROUP']['GROUP_NAME'], "Public",
            "and a domain routing nowhere carries the public one");
    }
    /**
     * A group with no name to give leaves the group out rather than
     * naming an empty one, so no class is built from nothing.
     */
    public function groupWithNoNameIsLeftOutTestCase()
    {
        $reading = $this->controller->readingWikiPageData("Privacy", 2,
            "");
        $this->assertEqual($reading['MODE'], "read",
            "the page still says it is being read");
        $this->assertTrue(!isset($reading['GROUP']),
            "and no group is named");
    }
    /**
     * With no routed group set, the Public group is served, the
     * historical behavior for the site's own static pages.
     */
    public function defaultsToPublicGroupTestCase()
    {
        unset($_SERVER['ROUTE_GROUP_ID']);
        $this->assertEqual($this->controller->staticGroupId(),
            C\PUBLIC_GROUP_ID,
            "no route serves the Public group");
    }
    /**
     * A positive routed group id is served as an integer, so a domain
     * pointed at a custom landing group reads that group's pages.
     */
    public function usesPositiveRoutedGroupTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = "7";
        $this->assertEqual($this->controller->staticGroupId(), 7,
            "a positive route selects that group");
        $this->assertTrue(
            $this->controller->staticGroupId() === 7,
            "the routed id comes back as an integer");
    }
    /**
     * A zero or negative routed group id is treated as no route and
     * falls back to the Public group.
     */
    public function rejectsNonPositiveRoutedGroupTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = "0";
        $this->assertEqual($this->controller->staticGroupId(),
            C\PUBLIC_GROUP_ID,
            "a zero route falls back to the Public group");
        $_SERVER['ROUTE_GROUP_ID'] = "-5";
        $this->assertEqual($this->controller->staticGroupId(),
            C\PUBLIC_GROUP_ID,
            "a negative route falls back to the Public group");
    }
    /**
     * A non-numeric routed group id cannot select a group and falls back
     * to the Public group.
     */
    public function rejectsNonNumericRoutedGroupTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = "not_a_number";
        $this->assertEqual($this->controller->staticGroupId(),
            C\PUBLIC_GROUP_ID,
            "a non-numeric route falls back to the Public group");
    }
    /**
     * On a routed group a baked group-controller self link written with
     * clean urls is rewritten to its static p/ url, keeping the page
     * name.
     */
    public function rewritesCleanUrlSelfLinkTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = "100";
        $html = '<a href="http://localhost/[{controller}]/100?a=wiki' .
            '&amp;[{token}]&amp;page_name=bob">BOB</a>';
        $out = $this->controller->rewriteRoutedGroupLinks($html);
        $this->assertTrue(
            strpos($out, 'href="' . C\SHORT_BASE_URL . 'p/bob"') !== false,
            "clean-url self link becomes the static p/ url");
        $this->assertTrue(strpos($out, '[{token}]') === false,
            "the token placeholder is gone from the rewritten link");
    }
    /**
     * The rewrite keys on the page_name query the read url ends with, so
     * a self link written as a query string, not clean urls, is rewritten
     * the same way.
     */
    public function rewritesQueryStringSelfLinkTestCase()
    {
        $_SERVER['ROUTE_GROUP_ID'] = "100";
        $html = '<a href="/src/?c=[{controller}]&amp;a=wiki&amp;' .
            'group_id=100&amp;[{token}]&amp;page_name=bob">BOB</a>';
        $out = $this->controller->rewriteRoutedGroupLinks($html);
        $this->assertTrue(
            strpos($out, 'href="' . C\SHORT_BASE_URL . 'p/bob"') !== false,
            "query-string self link becomes the static p/ url");
    }
    /**
     * A page served for the Public group keeps its links untouched, since
     * the Public group already carries controller-agnostic links.
     */
    public function leavesPublicGroupLinksUnchangedTestCase()
    {
        unset($_SERVER['ROUTE_GROUP_ID']);
        $html = '<a href="[{controller_and_page}]bob">BOB</a>';
        $this->assertEqual(
            $this->controller->rewriteRoutedGroupLinks($html), $html,
            "a Public page's links are returned unchanged");
    }
    /**
     * A page with no title of its own is titled "Site Name - Page Name",
     * the site name passed in so that a routed domain's name is used, and
     * the page name read with underscores as spaces.
     */
    public function untitledPageFallsBackToSiteAndPageNameTestCase()
    {
        L\setLocaleObject("en-US");
        $title = $this->controller->untitledPageTitle("Pollett.org",
            "Privacy_Policy");
        $this->assertTrue(strpos($title, "Pollett.org") !== false,
            "the untitled fallback carries the site name");
        $this->assertTrue(strpos($title, "Privacy Policy") !== false,
            "the untitled fallback carries the page name with spaces");
    }
    /**
     * The page name in the untitled fallback has its url encoding undone,
     * so an accented page name reads as its characters rather than percent
     * escapes.
     */
    public function untitledPageNameIsUrlDecodedTestCase()
    {
        L\setLocaleObject("en-US");
        $title = $this->controller->untitledPageTitle("Site",
            "caf%C3%A9");
        $this->assertTrue(strpos($title, "caf\u{e9}") !== false,
            "the untitled fallback url-decodes the page name");
    }
}
