/ src / controllers / TestsController.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\controllers;

use seekquarry\yioop as B;
use seekquarry\yioop\configs as C;
use seekquarry\yioop\library as L;
use seekquarry\yioop\tests as T;
use seekquarry\yioop\library\UnitTest;
use seekquarry\yioop\library\JavascriptUnitTest;

/**
 * don't try to use cache
 * @ignore
 */
$_SERVER["USE_CACHE"] = false;
/**
 * Do not send output to log files
 * @ignore
 */
$_SERVER["LOG_TO_FILES"] = false;
/**
 * Controller used to handle search requests to SeekQuarry
 * search site. Used to both get and display
 * search results.
 *
 * @author Chris Pollett
 */
class TestsController extends Controller
{
    /**
     * Handles requests to list all tests, run all test cases, or run a
     * particular test case
     */
    public function processRequest()
    {
        $view = 'tests';
        $allowed_activities = ["listTests", "runSelectedTests", "runTest"];
        $data = [];
        $signin_model = $this->model("signin");
        if (isset($_SESSION['USER_ID'])) {
            if ($this->getCSRFTime(C\p('CSRF_TOKEN')) == 0 &&
                $_SERVER['REQUEST_METHOD'] == "GET") {
                $_REQUEST[C\p('CSRF_TOKEN')] = $this->generateCSRFToken(
                    $_SESSION['USER_ID']);
                $this->redirectLocation(C\SHORT_BASE_URL . "?" .
                    http_build_query($_REQUEST));
                exit();
            }
            $user_id = $_SESSION['USER_ID'];
            $data['ADMIN'] = 1;
            $data['USERNAME'] = $signin_model->getUserName($user_id);
            $_SESSION['USER_NAME'] = $data['USERNAME'];
        } else {
            $user_id = C\PUBLIC_GROUP_ID;
        }
        $token_okay = $this->checkCSRFToken(C\p('CSRF_TOKEN'), $user_id);
        if (!empty($data['ADMIN']) && !$token_okay) {
            unset($data['ADMIN']);
        }
        $data[C\p('CSRF_TOKEN')] = $this->generateCSRFToken($user_id);
        $_SERVER['NO_LOGGING'] = true;
        if (isset($_REQUEST['activity']) &&
            in_array($_REQUEST['activity'], $allowed_activities)) {
            $activity = $_REQUEST['activity'];
        } else {
            $activity = "listTests";
        }
        $data = array_merge($data, $this->$activity());
        if (!empty($data['ERROR'])) {
            include(C\BASE_DIR . "/error.php");
            \seekquarry\atto\webExit();
        }
        $this->displayView($view, $data);
    }
    /**
     * This function is responsible for getting a list of test case names
     * from the folder tests so that this list of names can eventually
     * be displayed to the user.
     *
     * @return array $data field variables for the TestsView with the
     *      'list' ACTIVITY and a TEST_NAMES list of test class names
     */
    function listTests()
    {
        $data = ['ACTIVITY' => 'list', 'FILTER' => ''];
        $data['TEST_NAMES'] = [];
        $names = $this->getTestNames();
        foreach ($names as $name) {
            $test_name = $this->getClassNameFromFileName($name);
            $data['TEST_NAMES'][] = $test_name;
        }
        return $data;
    }
    /**
     * This is a utility function to get the Full URL of the current page.
     * @param bool $strip_query_params whether to get rid of the query string
     *      or not
     * @return string full url
     */
    function getFullURL($strip_query_params = false)
    {
        $page_url = (!empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on")
            ? "https://" : "http://";
        if (!in_array($_SERVER["SERVER_PORT"], ["80", "443"])) {
            $page_url .= $_SERVER["SERVER_NAME"] . ":" .
                $_SERVER["SERVER_PORT"] . $_SERVER["REQUEST_URI"];
        } else {
            $page_url .= $_SERVER["SERVER_NAME"] . $_SERVER["REQUEST_URI"];
        }
        //return full URL with query params stripped, if requested.
        return $strip_query_params ? strtok($page_url, '?') : $page_url;
    }
    /**
     * Runs the unit tests in the current directory and displays the
     * results. When $_REQUEST['filter'] is non-empty, only test classes
     * whose name contains the filter substring (case-insensitive) are
     * run; the filter UI on the tests-list page constructs this query
     * parameter so a partial match like "Mail" runs just the mail
     * tests instead of the full suite.
     *
     * @return array $data field variables for the TestsView with the
     *      'render_selected_tests' ACTIVITY and the TEST_RESULTS map (test
     *      class name => per-test result rows)
     */
    function runSelectedTests()
    {
        $data = ['ACTIVITY' => 'render_selected_tests', 'FILTER' => ''];
        $filter = '';
        if (!empty($_REQUEST['filter'])) {
            $filter = preg_replace("/[^A-Za-z_0-9 ]/", '',
                $_REQUEST['filter']);
            $data['FILTER'] = $filter;
        }
        $names = $this->getTestNames();
        $data['TEST_RESULTS'] = [];
        foreach ($names as $name) {
            $class_name = $this->getClassNameFromFileName($name);
            if ($filter !== '' && stripos($class_name, $filter) === false) {
                continue;
            }
            $_REQUEST['test'] = $class_name;
            $test_results = $this->runTest();
            $data['TEST_RESULTS'][$class_name] = $test_results['RESULTS'];
            if (!empty($test_results['ERROR'])) {
                $data['ERROR'] = $test_results['ERROR'];
            }
        }
        return $data;
    }
    /**
     * Run the single unit test whose name is given in $_REQUEST['test'] and
     * display the results. If the unit test file was blah_test.php, then
     * $_REQUEST['test'] should be blah.
     *
     * @return array $data field variables for the TestsView with the
     *      'render_test' ACTIVITY, a RESULTS array, plus TEST_NAME,
     *      METHOD, and ERROR keys as appropriate
     */
    function runTest()
    {
        $data = ['ACTIVITY' => 'render_test', 'RESULTS' => []];
        if (isset($_REQUEST['test'])) {
            //clean name
            $name = preg_replace("/[^A-Za-z_0-9]/", '', $_REQUEST['test']);
            $data['TEST_NAME'] = $name;
            if (!file_exists(C\TEST_DIR . "/$name.php")) {
                $data['ERROR'] = true;
                return $data;
            }
            $class_name = C\NS_TESTS . $name;
            $test = new $class_name();
            $method = null;
            if (!empty($_REQUEST['method']) &&
                method_exists($test, $_REQUEST['method'])) {
                $method = $_REQUEST['method'];
                $data['METHOD'] = $method;
            }
            if ($test instanceof JavascriptUnitTest) {
                if (php_sapi_name() == "cli" && $test->nodeCommand() != "") {
                    $data['RESULTS'] = $test->run($method);
                } else {
                    $data['RESULTS'] = [ 'JS' => true, 'DATA' =>
                        $test->run($method)];
                }
            } else {
                $data['RESULTS'] = $test->run($method);
            }
        } else {
            $data['ERROR'] = true;
        }
        return $data;
    }
    /**
     * Gets the names of all the unit test files in the current directory.
     * Doesn't really check for this explicitly, just checks if the file
     * end with _test.php
     *
     * @return array an array of unit test files
     */
    function getTestNames()
    {
        return glob(C\TEST_DIR .'/*Test.php');
    }
    /**
     * Convert the unit test file names into unit test class names
     *
     * @param string $name a file name with words separated by
     *      underscores, ending in .php
     *
     * @return string  a camel-cased name ending with Test
     */
    function getClassNameFromFileName($name)
    {
        //strip .php
        $class_name = substr($name, 0, - strlen(".php"));
        $class_name = substr($class_name, strlen(C\TEST_DIR) + 1);
        return $class_name;
    }
}
X