test(core): unit tests for Util/Database/Enum pure logic (+44 tests)

Adds tests for Encryption, TimeUtils, QueryHelper, AdminHelpers, ImageUtils,
StreamUtils and BootContext/ServerEnvironment enums. 242 -> 286 tests, all green.
This commit is contained in:
Divarion-D
2026-06-22 18:47:50 +03:00
parent 9d49ddd7e9
commit 5ce38ea61f
7 changed files with 339 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers AdminHelpers
*/
final class AdminHelpersTest extends TestCase {
public function testValidateCidrAcceptsValidAddressesAndMasks() {
$this->assertTrue(AdminHelpers::validateCIDR('192.168.1.1'));
$this->assertTrue(AdminHelpers::validateCIDR('192.168.1.0/24'));
$this->assertTrue(AdminHelpers::validateCIDR('::1'));
}
public function testValidateCidrRejectsInvalidInput() {
$this->assertFalse(AdminHelpers::validateCIDR('not-an-ip'));
$this->assertFalse(AdminHelpers::validateCIDR('10.0.0.0/33'));
$this->assertFalse(AdminHelpers::validateCIDR('2001:db8::/129'));
}
public function testRoundUpToAny() {
$this->assertEquals(10, AdminHelpers::roundUpToAny(7, 5));
$this->assertEquals(15, AdminHelpers::roundUpToAny(13, 5));
$this->assertEquals(5, AdminHelpers::roundUpToAny(3, 5));
}
public function testGenerateStringLengthAndUnambiguousCharset() {
$charset = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ';
$s = AdminHelpers::generateString(16);
$this->assertSame(16, strlen($s));
$this->assertSame(strlen($s), strspn($s, $charset));
}
public function testSortArrayByArray() {
$this->assertSame(
array('c', 'b', 'a'),
AdminHelpers::sortArrayByArray(array('a', 'b', 'c'), array('c', 'b', 'a'))
);
}
public function testGetBarColourThresholds() {
$this->assertSame('bg-danger', AdminHelpers::getBarColour(80));
$this->assertSame('bg-danger', AdminHelpers::getBarColour(75));
$this->assertSame('bg-warning', AdminHelpers::getBarColour(50));
$this->assertSame('bg-success', AdminHelpers::getBarColour(10));
}
public function testFormatUptime() {
$this->assertSame('01h 01m 01s', AdminHelpers::formatUptime(3661));
$this->assertSame('01d 01h 00m', AdminHelpers::formatUptime(90000));
}
public function testFilterIdsKeepsOnlyAllowedPositiveInts() {
$this->assertSame(
array(1, 2),
AdminHelpers::filterIDs(array('1', '2', 'x', 5), array(1, 2, 3))
);
}
public function testGetPageFromUrl() {
$this->assertSame('bouquet', AdminHelpers::getPageFromURL('http://host/admin/bouquet.php'));
$this->assertNull(AdminHelpers::getPageFromURL(''));
}
public function testProtocolDetectionFromServerVars() {
$saved = $_SERVER;
$_SERVER['HTTPS'] = 'on';
$this->assertTrue(AdminHelpers::issecure());
$this->assertSame('https', AdminHelpers::getProtocol());
unset($_SERVER['HTTPS']);
$_SERVER['SERVER_PORT'] = 80;
$this->assertFalse(AdminHelpers::issecure());
$this->assertSame('http', AdminHelpers::getProtocol());
$_SERVER['SERVER_PORT'] = 443;
$this->assertTrue(AdminHelpers::issecure());
$_SERVER = $saved;
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers BootContext
* @covers ServerEnvironment
*/
final class CoreEnumTest extends TestCase {
public function testBootContextValues() {
$this->assertSame('minimal', BootContext::Minimal->value);
$this->assertSame('cli', BootContext::Cli->value);
$this->assertSame('stream', BootContext::Stream->value);
$this->assertSame('admin', BootContext::Admin->value);
}
public function testBootContextFromString() {
$this->assertSame(BootContext::Admin, BootContext::from('admin'));
$this->assertNull(BootContext::tryFrom('nope'));
$this->assertCount(4, BootContext::cases());
}
public function testServerEnvironmentValues() {
$this->assertSame('main', ServerEnvironment::Main->value);
$this->assertSame('lb', ServerEnvironment::LoadBalancer->value);
}
public function testServerEnvironmentFromString() {
$this->assertSame(ServerEnvironment::LoadBalancer, ServerEnvironment::from('lb'));
$this->assertNull(ServerEnvironment::tryFrom('xxx'));
$this->assertCount(2, ServerEnvironment::cases());
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers Encryption
*/
final class EncryptionTest extends TestCase {
public function testEncryptDecryptRoundTrip() {
$plain = 'hello world / стрим 123';
$cipher = Encryption::encrypt($plain, 'secret-key', 'device-1');
$this->assertNotSame($plain, $cipher);
$this->assertSame($plain, Encryption::decrypt($cipher, 'secret-key', 'device-1'));
}
public function testEncryptIsDeterministicForSameInputs() {
// Same data/key/device → same ciphertext (derived key + IV are deterministic).
$a = Encryption::encrypt('data', 'k', 'd');
$b = Encryption::encrypt('data', 'k', 'd');
$this->assertSame($a, $b);
}
public function testDecryptWithWrongKeyFails() {
$cipher = Encryption::encrypt('secret', 'right-key', 'device');
$this->assertFalse(Encryption::decrypt($cipher, 'wrong-key', 'device'));
}
public function testDecryptWithWrongDeviceFails() {
$cipher = Encryption::encrypt('secret', 'key', 'device-a');
$this->assertFalse(Encryption::decrypt($cipher, 'key', 'device-b'));
}
public function testBase64urlIsUrlSafeAndUnpadded() {
$raw = "\xff\xfe\xfd\x00binary?";
$encoded = Encryption::base64urlEncode($raw);
$this->assertStringNotContainsString('+', $encoded);
$this->assertStringNotContainsString('/', $encoded);
$this->assertStringNotContainsString('=', $encoded);
$this->assertSame($raw, Encryption::base64urlDecode($encoded));
}
public function testRandomStringLengthAndCharset() {
$s = Encryption::randomString(40);
$this->assertSame(40, strlen($s));
$this->assertMatchesRegularExpression('/^[A-Za-z0-9]+$/', $s);
}
public function testRandomTokenLengthIsHex() {
$token = Encryption::randomToken(16);
$this->assertSame(32, strlen($token));
$this->assertMatchesRegularExpression('/^[0-9a-f]+$/', $token);
}
public function testGenerateKeyByteLength() {
$this->assertSame(16, strlen(Encryption::generateKey(128)));
$this->assertSame(32, strlen(Encryption::generateKey(256)));
}
public function testGenerateIvLengthForAes128() {
$this->assertSame(16, strlen(Encryption::generateIV('AES-128-CBC')));
}
public function testGenerateUniqueCodeIsStable15Chars() {
$code = Encryption::generateUniqueCode('pass');
$this->assertSame(15, strlen($code));
$this->assertSame($code, Encryption::generateUniqueCode('pass'));
$this->assertNotSame($code, Encryption::generateUniqueCode('other'));
}
}
+34
View File
@@ -0,0 +1,34 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers ImageUtils
*/
final class ImageUtilsTest extends TestCase {
public function testKeepAspectRatioDownscales() {
$size = ImageUtils::getImageSizeKeepAspectRatio(1000, 500, 100, 100);
$this->assertEquals(100, $size['width']);
$this->assertEquals(50, $size['height']);
}
public function testKeepAspectRatioDoesNotUpscale() {
$size = ImageUtils::getImageSizeKeepAspectRatio(50, 50, 100, 100);
$this->assertEquals(50, $size['width']);
$this->assertEquals(50, $size['height']);
}
public function testKeepAspectRatioTreatsZeroMaxAsUnbounded() {
$size = ImageUtils::getImageSizeKeepAspectRatio(200, 100, 0, 50);
$this->assertEquals(100, $size['width']);
$this->assertEquals(50, $size['height']);
}
public function testIsAbsoluteUrl() {
$this->assertTrue(ImageUtils::isAbsoluteUrl('http://example.com/a.png'));
$this->assertTrue(ImageUtils::isAbsoluteUrl('https://example.com/a.png'));
$this->assertFalse(ImageUtils::isAbsoluteUrl('/relative/a.png'));
$this->assertFalse(ImageUtils::isAbsoluteUrl('a.png'));
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers QueryHelper
*/
final class QueryHelperTest extends TestCase {
public function testPrepareColumnSanitizesToSafeIdentifier() {
$this->assertSame('foobar23', QueryHelper::prepareColumn('Foo Bar!23'));
$this->assertSame('user_id', QueryHelper::prepareColumn('user_id'));
$this->assertSame('droptable', QueryHelper::prepareColumn('drop;table'));
}
public function testPrepareArrayBuildsColumnsPlaceholdersAndData() {
$result = QueryHelper::prepareArray(array('name' => 'x', 'age' => 5));
$this->assertSame('`name`,`age`', $result['columns']);
$this->assertSame('?,?', $result['placeholder']);
$this->assertSame(array('x', 5), $result['data']);
$this->assertSame('`name` = ?,`age` = ?', $result['update']);
}
public function testPrepareArrayJsonEncodesArrayValues() {
$result = QueryHelper::prepareArray(array('tags' => array(1, 2, 3)));
$this->assertSame('[1,2,3]', $result['data'][0]);
}
public function testPrepareArrayNormalizesNullValues() {
$result = QueryHelper::prepareArray(array('a' => null, 'b' => 'null'));
$this->assertNull($result['data'][0]);
$this->assertNull($result['data'][1]);
}
public function testPrepareArraySanitizesColumnNames() {
$result = QueryHelper::prepareArray(array('na me!' => 'v'));
$this->assertSame('`name`', $result['columns']);
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers StreamUtils
*/
final class StreamUtilsTest extends TestCase {
public function testCustomOrderPutsInputArgumentsFirst() {
$this->assertSame(-1, StreamUtils::customOrder('-i input.ts', 'something'));
$this->assertSame(1, StreamUtils::customOrder('-c:v libx264', '-i input.ts'));
}
public function testDetectXcVmMatchesKnownStreamPaths() {
$this->assertTrue(StreamUtils::detectXC_VM('http://host/live/user/123'));
}
public function testDetectXcVmRejectsUnrelatedPaths() {
$this->assertFalse(StreamUtils::detectXC_VM('http://host/dashboard'));
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
use PHPUnit\Framework\TestCase;
/**
* @covers TimeUtils
*/
final class TimeUtilsTest extends TestCase {
public function testSecondsToTimeWithSeconds() {
$this->assertSame('1h 1m 1s', TimeUtils::secondsToTime(3661));
$this->assertSame('1d 0s', TimeUtils::secondsToTime(86400));
$this->assertSame('1m 30s', TimeUtils::secondsToTime(90));
$this->assertSame('0s', TimeUtils::secondsToTime(0));
}
public function testSecondsToTimeWithoutSeconds() {
$this->assertSame('1h 1m', TimeUtils::secondsToTime(3661, false));
$this->assertSame('1d', TimeUtils::secondsToTime(86400, false));
}
public function testDurationToSecondsThreeParts() {
$this->assertSame(3661, TimeUtils::durationToSeconds('01:01:01'));
$this->assertSame(7200, TimeUtils::durationToSeconds('02:00:00'));
}
public function testDurationToSecondsTwoParts() {
$this->assertSame(90, TimeUtils::durationToSeconds('01:30'));
}
public function testDurationToSecondsPlainNumber() {
$this->assertSame(42, TimeUtils::durationToSeconds('42'));
}
public function testTimeAgoBuckets() {
$now = time();
$this->assertStringEndsWith('s ago', TimeUtils::timeAgo($now - 5));
$this->assertStringEndsWith('m ago', TimeUtils::timeAgo($now - 120));
$this->assertStringEndsWith('h ago', TimeUtils::timeAgo($now - 7200));
$this->assertStringEndsWith('d ago', TimeUtils::timeAgo($now - 172800));
}
public function testNowMatchesFormat() {
$this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/', TimeUtils::now());
$this->assertMatchesRegularExpression('/^\d{4}$/', TimeUtils::now('Y'));
}
public function testGetDiffTimezoneReturnsInteger() {
$diff = TimeUtils::getDiffTimezone('UTC');
$this->assertIsInt($diff);
// Both anchors are "now in UTC"; allow ±1s for a second-boundary race.
$this->assertLessThanOrEqual(1, abs($diff));
}
}