<?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\yioop\configs as C;
use seekquarry\yioop\library\HotTargets;
use seekquarry\yioop\library\UnitTest;
/**
* HotTargetsTest checks the count of refusals a wiki page has drawn and
* the judgment that a page has drawn enough to be turned away early.
*
* @author Chris Pollett
*/
class HotTargetsTest extends UnitTest
{
/**
* setUp clears the counts file so each case starts with none.
*/
public function setUp()
{
$path = C\TEMP_DIR . "/" . HotTargets::FILE_NAME;
if (file_exists($path)) {
unlink($path);
}
}
/**
* tearDown removes the counts file the cases wrote.
*/
public function tearDown()
{
$this->setUp();
}
/**
* pageBecomesHotAfterEnoughRefusalsTestCase checks that refusals of
* one page's history from many addresses add up on the page, that it
* is not hot before REFUSALS_TO_BE_HOT of them and is hot after, and
* that an ask for a different page or for a plain read is never
* counted.
*/
public function pageBecomesHotAfterEnoughRefusalsTestCase()
{
$key = HotTargets::targetKey(40, "3971", "history");
$this->assertEqual("40:3971:history", $key,
"the key names the group, page and what was asked");
$this->assertEqual("", HotTargets::targetKey(40, "3971", "read"),
"a plain read is not something that is counted");
$now = 1000000;
for ($i = 1; $i < HotTargets::REFUSALS_TO_BE_HOT; $i++) {
HotTargets::recordRefusal($key, $now + $i);
}
$this->assertFalse(HotTargets::isHot($key, $now + 100),
"one short of the count, the page is not hot");
HotTargets::recordRefusal($key, $now + 100);
$this->assertTrue(HotTargets::isHot($key, $now + 101),
"at the count, the page is hot");
$this->assertFalse(HotTargets::isHot(
HotTargets::targetKey(40, "1", "history"), $now + 101),
"another page of the group is not hot");
}
/**
* hotPageStaysHotWhileTheWebServerRefusesItTestCase checks that a
* page that became hot stays hot well past WINDOW with no further
* refusal recorded, since the web server answers those requests and
* PHP never sees them, and cools only once HOT_FOR has passed; and
* that a page that never became hot is forgotten after WINDOW so
* old refusals do not count toward a new burst.
*/
public function hotPageStaysHotWhileTheWebServerRefusesItTestCase()
{
$key = HotTargets::targetKey(40, "3971", "source");
$now = 2000000;
for ($i = 0; $i < HotTargets::REFUSALS_TO_BE_HOT; $i++) {
HotTargets::recordRefusal($key, $now);
}
$this->assertTrue(HotTargets::isHot($key, $now + 1),
"the page is hot right after its refusals");
$this->assertTrue(HotTargets::isHot($key,
$now + HotTargets::WINDOW + 1),
"and is still hot past the window with nothing recorded since");
$this->assertFalse(HotTargets::isHot($key,
$now + HotTargets::HOT_FOR + 1),
"and cools once the hot span has passed");
$warm = HotTargets::targetKey(40, "1", "source");
HotTargets::recordRefusal($warm, $now);
$later = $now + HotTargets::WINDOW + 1;
HotTargets::recordRefusal($warm, $later);
$this->assertFalse(HotTargets::isHot($warm, $later + 1),
"a page that never became hot starts its count over after the" .
" window");
}
/**
* keyAndMarkerAgreeAcrossRequestShapesTestCase checks that the three
* shapes a scraper's request takes, a page by id in the query, a page
* by name in the path, and a path name with an id in the query, each
* key to what the web server's rewrite rule builds for it, so a
* marker PHP writes is the one Apache tests for; and that markers are
* written for hot targets only and taken away when a target cools.
*/
public function keyAndMarkerAgreeAcrossRequestShapesTestCase()
{
$shapes = [
["/wiki/group/40?a=wiki&arg=history&page_id=3920&show=1",
["arg" => "history", "page_id" => "3920"], "40-3920-history"],
["/wiki/group/2/Main?arg=source&settings=true",
["arg" => "source"], "2-Main-source"],
["/group/40/faculty-office-hours?arg=source&page_id=3920",
["arg" => "source", "page_id" => "3920"], "40-3920-source"],
];
foreach ($shapes as [$uri, $request, $marker]) {
$this->assertEqual($marker, HotTargets::markerName(
HotTargets::keyFromRequest($uri, $request)),
"the marker for $uri is what the rewrite rule builds");
}
$folder = C\WORK_DIRECTORY . "/" . HotTargets::MARKER_FOLDER;
$hot = ["40:3920:history" => ["count" =>
HotTargets::REFUSALS_TO_BE_HOT, "last" => 5, "hot_since" => 5]];
$cool = ["40:3920:history" => ["count" => 1, "last" => 5]];
HotTargets::writeMarkers($hot);
$this->assertTrue(file_exists($folder . "/40-3920-history"),
"a hot target has its marker file");
HotTargets::writeMarkers($cool);
$this->assertFalse(file_exists($folder . "/40-3920-history"),
"and loses it once it is no longer hot");
}
}