Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/63 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
208 lines
9.5 KiB
PHP
208 lines
9.5 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use PHPUnit\Framework\Attributes\DataProvider;
|
|
|
|
final class UtilTest extends TestCase
|
|
{
|
|
// Define test date (strings) and expected outputs for
|
|
// testCanDisplayRelativeTime
|
|
public static function dateProvider(): array {
|
|
$datetime = new DateTimeImmutable();
|
|
|
|
return [
|
|
'1 minute ago' => [$datetime->modify('-1 minute')->format('c'), '1 minute ago'],
|
|
'2 hours ago' => [$datetime->modify('-2 hours')->format('c'), '2 hours ago'],
|
|
'3 days ago' => [$datetime->modify('-3 days')->format('c'), '3 days ago'],
|
|
'4 months ago' => [$datetime->modify('-4 months')->format('c'), '4 months ago'],
|
|
'5 years ago' => [$datetime->modify('-5 years')->format('c'), '5 years ago']
|
|
];
|
|
}
|
|
|
|
// Validate that the datetime strings provided by dateProvider
|
|
// yield the expected display strings
|
|
#[DataProvider('dateProvider')]
|
|
public function testCanDisplayRelativeTime(string $datetimeString, string $display): void {
|
|
$relativeTime = Util::relative_time($datetimeString);
|
|
$this->assertSame($relativeTime, $display);
|
|
}
|
|
|
|
public static function buildUrlProvider(): array {
|
|
return [
|
|
'basic path' => ['https://example.com', 'tkr', 'admin', 'https://example.com/tkr/admin'],
|
|
'baseUrl with trailing slash' => ['https://example.com/', 'tkr', 'admin', 'https://example.com/tkr/admin'],
|
|
'empty basePath' => ['https://example.com', '', 'admin', 'https://example.com/admin'],
|
|
'root basePath' => ['https://example.com', '/', 'admin', 'https://example.com/admin'],
|
|
'basePath no leading slash' => ['https://example.com', 'tkr', 'admin', 'https://example.com/tkr/admin'],
|
|
'basePath with leading slash' => ['https://example.com', '/tkr', 'admin', 'https://example.com/tkr/admin'],
|
|
'basePath with trailing slash' => ['https://example.com', 'tkr/', 'admin', 'https://example.com/tkr/admin'],
|
|
'basePath with both slashes' => ['https://example.com', '/tkr/', 'admin', 'https://example.com/tkr/admin'],
|
|
'complex path' => ['https://example.com', 'tkr', 'admin/css/upload', 'https://example.com/tkr/admin/css/upload'],
|
|
'path with leading slash' => ['https://example.com', 'tkr', '/admin', 'https://example.com/tkr/admin'],
|
|
'no path - empty basePath' => ['https://example.com', '', '', 'https://example.com/'],
|
|
'no path - root basePath' => ['https://example.com', '/', '', 'https://example.com/'],
|
|
'no path - tkr basePath' => ['https://example.com', 'tkr', '', 'https://example.com/tkr/'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('buildUrlProvider')]
|
|
public function testBuildUrl(string $baseUrl, string $basePath, string $path, string $expected): void {
|
|
$result = Util::buildUrl($baseUrl, $basePath, $path);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
public static function buildRelativeUrlProvider(): array {
|
|
return [
|
|
'empty basePath with path' => ['', 'admin', '/admin'],
|
|
'root basePath with path' => ['/', 'admin', '/admin'],
|
|
'tkr basePath with path' => ['tkr', 'admin', '/tkr/admin'],
|
|
'tkr with leading slash' => ['/tkr', 'admin', '/tkr/admin'],
|
|
'tkr with trailing slash' => ['tkr/', 'admin', '/tkr/admin'],
|
|
'tkr with both slashes' => ['/tkr/', 'admin', '/tkr/admin'],
|
|
'complex path' => ['tkr', 'admin/css/upload', '/tkr/admin/css/upload'],
|
|
'path with leading slash' => ['tkr', '/admin', '/tkr/admin'],
|
|
'no path - empty basePath' => ['', '', '/'],
|
|
'no path - root basePath' => ['/', '', '/'],
|
|
'no path - tkr basePath' => ['tkr', '', '/tkr'],
|
|
'no path - tkr with slashes' => ['/tkr/', '', '/tkr'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('buildRelativeUrlProvider')]
|
|
public function testBuildRelativeUrl(string $basePath, string $path, string $expected): void {
|
|
$result = Util::buildRelativeUrl($basePath, $path);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
// Test data for escape_html function
|
|
public static function escapeHtmlProvider(): array {
|
|
return [
|
|
'basic HTML' => ['<script>alert("xss")</script>', '<script>alert("xss")</script>'],
|
|
'quotes' => ['He said "Hello" & she said \'Hi\'', 'He said "Hello" & she said 'Hi''],
|
|
'empty string' => ['', ''],
|
|
'normal text' => ['Hello World', 'Hello World'],
|
|
'ampersand' => ['Tom & Jerry', 'Tom & Jerry'],
|
|
'unicode' => ['🚀 emoji & text', '🚀 emoji & text'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('escapeHtmlProvider')]
|
|
public function testEscapeHtml(string $input, string $expected): void {
|
|
$result = Util::escape_html($input);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
// Test data for escape_xml function
|
|
public static function escapeXmlProvider(): array {
|
|
return [
|
|
'basic XML' => ['<tag attr="value">content</tag>', '<tag attr="value">content</tag>'],
|
|
'quotes and ampersand' => ['Title & "Subtitle"', 'Title & "Subtitle"'],
|
|
'empty string' => ['', ''],
|
|
'normal text' => ['Hello World', 'Hello World'],
|
|
'unicode' => ['🎵 music & notes', '🎵 music & notes'],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('escapeXmlProvider')]
|
|
public function testEscapeXml(string $input, string $expected): void {
|
|
$result = Util::escape_xml($input);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
// Test data for linkify function
|
|
public static function linkifyProvider(): array {
|
|
return [
|
|
'simple URL' => [
|
|
'Check out https://example.com for more info',
|
|
'Check out <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for more info',
|
|
false // not strict accessibility
|
|
],
|
|
'URL with path' => [
|
|
'Visit https://example.com/path/to/page',
|
|
'Visit <a href="https://example.com/path/to/page" target="_blank" rel="noopener noreferrer">https://example.com/path/to/page</a>',
|
|
false
|
|
],
|
|
'multiple URLs' => [
|
|
'See https://example.com and https://other.com',
|
|
'See <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> and <a href="https://other.com" target="_blank" rel="noopener noreferrer">https://other.com</a>',
|
|
false
|
|
],
|
|
'URL with punctuation' => [
|
|
'Check https://example.com.',
|
|
'Check <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>',
|
|
false
|
|
],
|
|
'no URL' => [
|
|
'Just some regular text',
|
|
'Just some regular text',
|
|
false
|
|
],
|
|
'strict accessibility mode' => [
|
|
'Visit https://example.com now',
|
|
'Visit <a tabindex="0" href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> now',
|
|
true // strict accessibility
|
|
],
|
|
];
|
|
}
|
|
|
|
#[DataProvider('linkifyProvider')]
|
|
public function testLinkify(string $input, string $expected, bool $strictAccessibility): void {
|
|
// Set up global $app with settings
|
|
global $app;
|
|
$app = [
|
|
'settings' => (object)['strictAccessibility' => $strictAccessibility]
|
|
];
|
|
|
|
$result = Util::linkify($input);
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
public function testLinkifyNoNewWindow(): void {
|
|
// Test linkify without new window
|
|
global $app;
|
|
$app = [
|
|
'settings' => (object)['strictAccessibility' => false]
|
|
];
|
|
|
|
$input = 'Visit https://example.com';
|
|
$expected = 'Visit <a href="https://example.com">https://example.com</a>';
|
|
|
|
$result = Util::linkify($input, false); // no new window
|
|
$this->assertEquals($expected, $result);
|
|
}
|
|
|
|
public function testGetClientIp(): void {
|
|
// Test basic case with REMOTE_ADDR
|
|
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
|
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP']);
|
|
|
|
$result = Util::getClientIp();
|
|
$this->assertEquals('192.168.1.100', $result);
|
|
}
|
|
|
|
public function testGetClientIpWithForwardedHeaders(): void {
|
|
// Test precedence: HTTP_CLIENT_IP > HTTP_X_FORWARDED_FOR > HTTP_X_REAL_IP > REMOTE_ADDR
|
|
$_SERVER['HTTP_CLIENT_IP'] = '10.0.0.1';
|
|
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.2';
|
|
$_SERVER['HTTP_X_REAL_IP'] = '10.0.0.3';
|
|
$_SERVER['REMOTE_ADDR'] = '10.0.0.4';
|
|
|
|
$result = Util::getClientIp();
|
|
$this->assertEquals('10.0.0.1', $result); // Should use HTTP_CLIENT_IP
|
|
}
|
|
|
|
public function testGetClientIpUnknown(): void {
|
|
// Test when no IP is available
|
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
|
|
|
$result = Util::getClientIp();
|
|
$this->assertEquals('unknown', $result);
|
|
}
|
|
|
|
protected function tearDown(): void {
|
|
// Clean up $_SERVER after IP tests
|
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
|
}
|
|
|
|
} |