Add PHP unit tests and page tests.

Run "composer install", then "composer test".
This commit is contained in:
tim
2026-09-26 03:48:09 +02:00
parent 848c447c5b
commit ed959888b4
42 changed files with 5522 additions and 0 deletions
+256
View File
@@ -0,0 +1,256 @@
<?php
namespace Xxx\Tests\Unit;
use PHPUnit\Framework\Attributes\DataProvider;
use Xxx\Tests\Support\TestCase;
// The functions in get_data.lib.php that read and clean request values
class RequestArgsTest extends TestCase {
protected function setUp(): void {
parent::setUp();
open_db('open');
}
public static function intArgs(): array {
return array(
'whole number' => array('7', 7),
'spaces around' => array(' 7 ', 7),
'negative' => array('-3', 0),
'above the maximum' => array('500', 100),
'zero' => array('0', 0),
'decimal' => array('7.5', NULL),
'trailing text' => array('7abc', NULL),
'leading zero' => array('07', NULL),
'plus sign' => array('+7', 7),
'exponent' => array('1e3', NULL),
'SQL' => array('1; DROP TABLE xxx', NULL),
'empty' => array('', NULL),
);
}
#[DataProvider('intArgs')]
public function testGetIntArg(string $value, ?int $expected): void {
$this->request(array('n' => $value));
$this->assertSame($expected, get_int_arg('n', 0, 100));
}
public function testGetIntArgMissingOrNotAString(): void {
$this->assertNull(get_int_arg('n', 0));
$this->request(array('n' => array('1')));
$this->assertNull(get_int_arg('n', 0));
}
public function testGetIntArgHasNoMaximumByDefault(): void {
$this->request(array('n' => '123456789'));
$this->assertSame(123456789, get_int_arg('n', 0));
}
public function testShowNumComesFromTheRequest(): void {
$this->request(array('show' => '12'));
$this->assertSame(12, get_show_num());
}
public function testShowNumIsCappedAndAtLeastOne(): void {
$this->request(array('show' => '100000'));
$this->assertSame(MAX_SHOW, get_show_num());
$this->request(array('show' => '0'));
$this->assertSame(1, get_show_num());
}
public function testShowNumFallsBackToThePageSetting(): void {
$GLOBALS['num_stories'] = 5;
$this->assertSame(5, get_show_num());
$this->request(array('show' => 'many'));
$this->assertSame(5, get_show_num());
}
public function testShowNumFallsBackToOne(): void {
$this->assertSame(1, get_show_num());
}
public function testStartNum(): void {
$this->assertSame(0, get_start_num());
$this->request(array('start' => '15'));
$this->assertSame(15, get_start_num());
$this->request(array('start' => '-15'));
$this->assertSame(0, get_start_num());
$this->request(array('start' => '15 OR 1=1'));
$this->assertSame(0, get_start_num());
}
public static function sorts(): array {
return array(
array(NULL, 'date'), array('author', 'author'), array('title', 'title'), array('date', 'date'),
array('id', 'date'), array('title; DROP TABLE xxx', 'date'), array('TITLE', 'date'),
);
}
#[DataProvider('sorts')]
public function testSortArgAllowsOnlyKnownColumns(?string $sort, string $expected): void {
if ($sort !== NULL)
$this->request(array('sort' => $sort));
$this->assertSame($expected, get_sort_arg());
}
public function testSortArgIgnoresArrays(): void {
$this->request(array('sort' => array('title')));
$this->assertSame('date', get_sort_arg());
}
public static function orders(): array {
return array(
'date default' => array(array(), 'desc'),
'title default' => array(array('sort' => 'title'), 'asc'),
'author default' => array(array('sort' => 'author'), 'asc'),
'asc' => array(array('order' => 'asc'), 'asc'),
'DESC' => array(array('order' => 'DESC', 'sort' => 'title'), 'DESC'),
'injection' => array(array('order' => 'desc, (SELECT 1)'), 'desc'),
'unknown' => array(array('order' => 'sideways', 'sort' => 'title'), 'asc'),
'array' => array(array('order' => array('asc')), 'desc'),
);
}
#[DataProvider('orders')]
public function testOrderAllowsOnlyAscAndDesc(array $get, string $expected): void {
$this->request($get);
$this->assertSame($expected, get_order());
}
public function testSearchArgs(): void {
$this->assertNull(get_search_args());
$this->request(array('search' => "O'Brien <b>"));
$this->assertSame("O'Brien <b>", get_search_args());
$this->request(array('search' => array('x')));
$this->assertNull(get_search_args());
}
public function testIdMustBeNumeric(): void {
$this->assertNull(get_id());
$this->request(array('id' => 'abc'));
$this->assertNull(get_id());
$this->request(array('id' => '1 OR 1=1'));
$this->assertNull(get_id());
$this->request(array('id' => '42'));
$this->assertSame('42', get_id());
}
public function testIdComesFromPostToo(): void {
$this->request(array(), array('id' => '9'));
$this->assertSame('9', get_id());
}
public function testIdKeepsAGlobalValue(): void {
$GLOBALS['id'] = 5;
$this->request(array('id' => '42'));
$this->assertSame(5, get_id());
}
public function testIdIgnoresArrays(): void {
$this->request(array('id' => array('1')));
$this->assertNull(get_id());
}
public function testQueueIsOffByDefault(): void {
$this->assertFalse(get_queue());
}
public function testQueueComesFromTheRequest(): void {
$this->request(array('queue' => '1'));
$this->assertTrue(get_queue());
$GLOBALS['queue'] = NULL;
$this->request(array('queue' => '0'));
$this->assertFalse(get_queue());
$GLOBALS['queue'] = NULL;
$this->request(array('queue' => 'yes'));
$this->assertFalse(get_queue());
$GLOBALS['queue'] = NULL;
$this->request(array('queue' => array('1')));
$this->assertFalse(get_queue());
}
public function testQueueIsOnForSaveAndSubmit(): void {
foreach (array('save', 'submit') as $action) {
$GLOBALS['action'] = $action;
$this->request(array('queue' => '0'));
$this->assertTrue(get_queue(), $action);
}
}
public function testQueueKeepsAGlobalValue(): void {
$GLOBALS['queue'] = TRUE;
$this->assertTrue(get_queue());
}
public static function tables(): array {
// is_admin, queue, move, action => table
return array(
'visitor reads the live table' => array(FALSE, FALSE, FALSE, 'read', 'xxx'),
'visitor cannot read the queue' => array(FALSE, TRUE, FALSE, 'read', 'xxx'),
'editor reads the queue' => array(TRUE, TRUE, FALSE, 'read', 'queue'),
'editor reads the live table' => array(TRUE, FALSE, FALSE, 'read', 'xxx'),
'visitor post goes to the queue' => array(FALSE, FALSE, FALSE, 'post', 'queue'),
'visitor save goes to the queue' => array(FALSE, FALSE, FALSE, 'save', 'queue'),
'visitor submit goes to the queue' => array(FALSE, FALSE, FALSE, 'submit', 'queue'),
'visitor delete only touches the queue' => array(FALSE, FALSE, TRUE, 'delete', 'queue'),
'editor post goes live' => array(TRUE, TRUE, TRUE, 'post', 'xxx'),
'editor save goes to the queue' => array(TRUE, FALSE, FALSE, 'save', 'queue'),
'editor submit goes to the queue' => array(TRUE, FALSE, FALSE, 'submit', 'queue'),
'editor moving deletes from the queue' => array(TRUE, TRUE, TRUE, 'delete', 'queue'),
'editor deletes from the live table' => array(TRUE, FALSE, FALSE, 'delete', 'xxx'),
'editor deletes from the queue' => array(TRUE, TRUE, FALSE, 'delete', 'queue'),
);
}
#[DataProvider('tables')]
public function testWhichTable(bool $isAdmin, bool $queue, bool $move, string $action, string $table): void {
$GLOBALS['is_admin'] = $isAdmin;
$GLOBALS['queue'] = $queue;
$this->assertSame($table, which_table($move, $action));
}
public function testPostDataHasEveryField(): void {
$this->request(array(), array('title' => 'T', 'author' => 'A', 'email' => 'e@x.org', 'story' => '<p>S</p>',
'comments' => 'C', 'date' => '2020-01-01', 'hidden' => 'on', 'uploaded' => 'pending',
'editor' => 'ed', 'remove_img' => '1', 'id' => '4', 'ignored' => 'x'));
$entry = get_post_data()[0];
$this->assertSame(array('id', 'title', 'author', 'email', 'upload', 'story', 'comments', 'date', 'hidden',
'uploaded', 'editor', 'remove_img', 'queue_url'), array_keys($entry));
$this->assertSame('T', $entry['title']);
$this->assertSame(1, $entry['remove_img']);
$this->assertNull($entry['upload']);
$this->assertSame(0, $entry['queue_url']);
}
public function testPostDataAsStrings(): void {
$this->request(array(), array('title' => array('not', 'a string'), 'remove_img' => '0', 'id' => '4'));
$entry = get_post_data(TRUE)[0];
$this->assertSame('', $entry['title']);
$this->assertSame('', $entry['author']);
$this->assertSame('0', $entry['remove_img']);
$this->assertSame('4', $entry['id']);
foreach ($entry as $value)
$this->assertIsString($value);
}
public function testPostDataEscapedForHtml(): void {
$this->request(array(), array('title' => '<b>"T" & co</b>', 'story' => 'Wait&hellip;'));
$entry = get_post_data(TRUE, TRUE)[0];
$this->assertSame('&lt;b&gt;&quot;T&quot; &amp; co&lt;/b&gt;', $entry['title']);
$this->assertSame('Wait&hellip;', $entry['story']);
}
public function testPostDataQueueUrlFollowsTheQueue(): void {
$GLOBALS['queue'] = TRUE;
$this->assertSame(1, get_post_data()[0]['queue_url']);
}
public function testMapHelpers(): void {
$this->assertSame('', map_string(NULL));
$this->assertSame('', map_string(array(1)));
$this->assertSame('0', map_string(0));
$this->assertSame('1', map_string(TRUE));
$this->assertSame('&lt;a href=&quot;x&quot;&gt;', map_html('<a href="x">'));
$this->assertSame('<a href="x">', unmap_html(map_html('<a href="x">')));
$this->assertSame("it's", unmap_html('it&#039;s'));
}
}