341 lines
12 KiB
PHP
341 lines
12 KiB
PHP
<?php
|
||
|
||
/*
|
||
* Copyright (c) 2025, Антон Аксенов
|
||
* This file is part of m3u.su project
|
||
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace Tests;
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
use Symfony\Component\HttpFoundation\Request;
|
||
|
||
abstract class BaseTestCase extends TestCase
|
||
{
|
||
/**
|
||
* Тестирует наличие методов в классе
|
||
*
|
||
* @param array $methods
|
||
* @param object|string $class
|
||
*/
|
||
public function assertHasMethods(array $methods, object|string $class): void
|
||
{
|
||
foreach ($methods as $method) {
|
||
$this->assertTrue(
|
||
method_exists(is_object($class) ? $class::class : $class, $method),
|
||
"Method {$class}::{$method}() does not exist"
|
||
);
|
||
}
|
||
}
|
||
|
||
protected function makePlaylist(): void
|
||
{
|
||
}
|
||
|
||
protected function makeIni(?string $contents = null): string
|
||
{
|
||
$contents ??= <<<'EOD'
|
||
[foo]
|
||
name=foo name
|
||
desc=foo description
|
||
url=http://example.com/foo.m3u
|
||
src=http://example.com/
|
||
[bar]
|
||
name=bar name
|
||
desc=bar description
|
||
url=http://example.com/bar.m3u
|
||
src=http://example.com/
|
||
EOD;
|
||
|
||
return data_stream($contents, 'text/ini');
|
||
}
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Методы для заглушки объектов и методов других классов
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
/**
|
||
* Создаёт и возвращает объект HTTP-запроса для тестирования методов контроллеров
|
||
*
|
||
* @param array $query The GET parameters
|
||
* @param array $request The POST parameters
|
||
* @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...)
|
||
* @param array $cookies The COOKIE parameters
|
||
* @param array $files The FILES parameters
|
||
* @param array $server The SERVER parameters
|
||
* @param resource|string|null $content The raw body data
|
||
*
|
||
* @return HttpRequest
|
||
*
|
||
* @see Request::__construct
|
||
*/
|
||
protected function makeHttpRequest(
|
||
array $query = [],
|
||
array $request = [],
|
||
array $attributes = [],
|
||
array $cookies = [],
|
||
array $files = [],
|
||
array $server = [],
|
||
mixed $content = null
|
||
): HttpRequest {
|
||
return new HttpRequest(func_get_args());
|
||
}
|
||
|
||
/**
|
||
* Возвращает объект ответа HTTP-клиента Laravel
|
||
*
|
||
* @param int $status
|
||
* @param array $headers
|
||
* @param $body
|
||
* @param string $version
|
||
* @param string|null $reason
|
||
*
|
||
* @return HttpClientResponse
|
||
*/
|
||
protected function makeHttpResponse(
|
||
int $status = 200,
|
||
array $headers = [],
|
||
$body = null,
|
||
string $version = '1.1',
|
||
?string $reason = null
|
||
): HttpClientResponse {
|
||
return new HttpClientResponse(new GuzzleResponse(...func_get_args()));
|
||
}
|
||
|
||
/**
|
||
* Вызывает любой метод указанного объекта с нужными аргументами и обходом его видимости
|
||
*
|
||
* @param class-string|object $objectOrClass
|
||
* @param string $method
|
||
* @param mixed ...$args
|
||
*
|
||
* @return mixed
|
||
*
|
||
* @throws ReflectionException
|
||
*
|
||
* @see https://stackoverflow.com/questions/249664
|
||
* @see \Psy\Sudo::callMethod()
|
||
* @see \Psy\Sudo::callStatic()
|
||
*/
|
||
protected function callMethod(object|string $objectOrClass, string $method, mixed ...$args): mixed
|
||
{
|
||
$reflObject = is_string($objectOrClass)
|
||
? new ReflectionClass($objectOrClass)
|
||
: new ReflectionObject($objectOrClass);
|
||
$reflMethod = $reflObject->getMethod($method);
|
||
|
||
return $reflMethod->invokeArgs(is_string($objectOrClass) ? null : $objectOrClass, $args);
|
||
}
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Методы-хелперы для подготовки и отладки тестов
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
/**
|
||
* Конвертирует многоуровневый массив в html-файл с таблицей для визуальной
|
||
* отладки и сохраняет в `"storage/app/$filename"`.
|
||
*
|
||
* Использование:
|
||
* 0. предполагается во время отладки теста
|
||
* 1. вызвать метод, который возвращает массив, приводимый к массиву или читаемый как массив объект
|
||
* 2. вызвать `$this->toTable($array, 'test')`
|
||
* 3. открыть в браузере файл `storage/app/test.html`
|
||
*
|
||
* @param array|ArrayAccess $data
|
||
* @param string $filename
|
||
*/
|
||
protected static function toTable(array|ArrayAccess $data, string $filename): void
|
||
{
|
||
$headers = $result = $html = [];
|
||
foreach ($data as $row) {
|
||
$result[] = $row = Arr::dot($row);
|
||
empty($headers) && $headers = array_keys($row);
|
||
}
|
||
$html[] = '<html lang="ru"><style>body{margin:0}table{font-family:monospace;border-collapse:collapse}'
|
||
. 'thead{background:darkorange;position:sticky;top:-1}th,td{white-space:nowrap;'
|
||
. 'border:1px solid black;padding:0 2px}tr:active{font-weight:bold}'
|
||
. 'tr:hover{background:lightgrey}</style><body><table><thead>';
|
||
foreach ($headers as $header) {
|
||
$html[] = "<th>{$header}</th>";
|
||
}
|
||
$html[] = '</thead><tbody>';
|
||
|
||
foreach ($result as $row) {
|
||
$html[] = '<tr>';
|
||
foreach ($row as $value) {
|
||
$value instanceof BackedEnum && $value = $value->value;
|
||
$value = str_replace("'", '', var_export($value, true)); // строки без кавычек
|
||
$html[] = "<td>{$value}</td>";
|
||
}
|
||
$html[] = '</tr>';
|
||
}
|
||
$html[] = '</tbody></table></body></html>';
|
||
Storage::put("{$filename}.html", implode('', $html));
|
||
}
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Методы проверки значений
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
/**
|
||
* Проверяет идентичность двух классов
|
||
*
|
||
* @param object|string $class1
|
||
* @param object|string $class2
|
||
*
|
||
* @return bool
|
||
*/
|
||
protected function checkIsSameClass(object|string $class1, object|string $class2): bool
|
||
{
|
||
return (is_object($class1) ? $class1::class : $class1) === (is_object($class2) ? $class2::class : $class2);
|
||
}
|
||
|
||
/**
|
||
* Проверяет наследование других классов указанным
|
||
*
|
||
* @param string[] $parents Массив имён потенциальных классов-родителей
|
||
* @param object|string $class Объект или имя класса для проверки
|
||
*
|
||
* @see https://www.php.net/manual/en/function.class-parents.php
|
||
*/
|
||
protected function checkExtendsClasses(array $parents, object|string $class): bool
|
||
{
|
||
return !empty(array_intersect($parents, is_object($class) ? class_parents($class) : [$class]));
|
||
}
|
||
|
||
/**
|
||
* Проверяет реализацию интерфейсов указанным классом
|
||
*
|
||
* @param string[] $interfaces Массив имён интерфейсов
|
||
* @param object|string $class Объект или имя класса для проверки
|
||
*
|
||
* @see https://www.php.net/manual/en/function.class-parents.php
|
||
*/
|
||
protected function checkImplementsInterfaces(array $interfaces, object|string $class): bool
|
||
{
|
||
return !empty(array_intersect($interfaces, is_object($class) ? class_implements($class) : [$class]));
|
||
}
|
||
|
||
/*
|
||
|--------------------------------------------------------------------------
|
||
| Методы проверки утверждений в тестах
|
||
|--------------------------------------------------------------------------
|
||
*/
|
||
|
||
/**
|
||
* Утверждает, что в массиве имеются все указанные ключи
|
||
*
|
||
* @param array $keys Ключи для проверки в массиве
|
||
* @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertArrayHasKeys(array $keys, iterable $array): void
|
||
{
|
||
$array = iterator_to_array($array);
|
||
|
||
foreach ($keys as $key) {
|
||
$this->assertArrayHasKey($key, $array);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Утверждает, что в объекте имеются все указанные свойства
|
||
*
|
||
* @param array $props Свойства для проверки в объекте
|
||
* @param object $object Проверяемый объект
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertObjectHasProperties(array $props, object $object): void
|
||
{
|
||
foreach ($props as $prop) {
|
||
$this->assertObjectHasProperty($prop, $object);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Утверждает, что в массиве отсутствуют все указанные ключи
|
||
*
|
||
* @param array $keys Ключи для проверки в массиве
|
||
* @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertArrayNotHasKeys(array $keys, iterable $array): void
|
||
{
|
||
foreach ($keys as $key) {
|
||
$this->assertArrayNotHasKey($key, $array);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Утверждает, что в объекте отсутствуют все указанные свойства
|
||
*
|
||
* @param array $props Свойства для проверки в объекте
|
||
* @param object $object Проверяемый объект
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertObjectNotHasProperties(array $props, object $object): void
|
||
{
|
||
foreach ($props as $prop) {
|
||
$this->assertObjectNotHasProperty($prop, $object);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Утверждает, что в массиве элементы только указанного типа
|
||
*
|
||
* @param string $type Название типа, один из возможных результатов функции gettype()
|
||
* @param iterable $array Проверяемый массив, итератор или приводимый к массиву объект
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertArrayValuesTypeOf(string $type, iterable $array): void
|
||
{
|
||
foreach ($array as $key => $value) {
|
||
$this->assertEquals($type, gettype($value), "Failed asserting that element [{$key}] is type of {$type}");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Утверждает, что в массив содержит только объекты (опционально -- указанного класса)
|
||
*
|
||
* Работает гибче {@link self::assertContainsOnlyInstancesOf()}
|
||
* засчёт предварительной подготовки проверяемых данных и возможности
|
||
* нестрогой проверки имени класса.
|
||
*
|
||
* @param mixed $array Массив для проверки
|
||
* @param object|string|null $class Имя класса (если не указано, проверяется только тип)
|
||
*
|
||
* @return void
|
||
*/
|
||
protected function assertIsArrayOfObjects(mixed $array, object|string|null $class = null): void
|
||
{
|
||
is_object($class) && $class = $class::class;
|
||
|
||
if (is_string($array) && json_validate($array)) {
|
||
$array = json_decode($array);
|
||
}
|
||
|
||
$this->assertNotEmpty($array);
|
||
|
||
if (empty($class)) {
|
||
$filtered = array_filter($array, static fn ($elem) => is_object($elem));
|
||
$this->assertSame($array, $filtered, 'Failed asserting that array containts only objects');
|
||
} else {
|
||
$this->assertContainsOnlyInstancesOf($class, $array);
|
||
}
|
||
}
|
||
}
|