WIP
This commit is contained in:
340
tests/BaseTestCase.php
Normal file
340
tests/BaseTestCase.php
Normal file
@@ -0,0 +1,340 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
tests/Controllers/ApiControllerTest.php
Normal file
37
tests/Controllers/ApiControllerTest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ApiControllerTest extends TestCase
|
||||
{
|
||||
|
||||
public function testMakeQrCode()
|
||||
{
|
||||
}
|
||||
|
||||
public function testGetOne()
|
||||
{
|
||||
}
|
||||
|
||||
public function testHealth()
|
||||
{
|
||||
}
|
||||
|
||||
public function testStats()
|
||||
{
|
||||
}
|
||||
|
||||
public function testVersion()
|
||||
{
|
||||
}
|
||||
}
|
||||
113
tests/Core/IniFileTest.php
Normal file
113
tests/Core/IniFileTest.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?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\Core;
|
||||
|
||||
use App\Core\IniFile;
|
||||
use App\Exceptions\FileReadException;
|
||||
use App\Exceptions\IniParsingException;
|
||||
use App\Exceptions\PlaylistNotFoundException;
|
||||
use Tests\BaseTestCase;
|
||||
use Tests\FixtureHandler;
|
||||
|
||||
class IniFileTest extends BaseTestCase
|
||||
{
|
||||
use FixtureHandler;
|
||||
|
||||
/**
|
||||
* Проверяет успешное создание объекта, чтение и парсинг файла
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
*/
|
||||
public function testMain(): void
|
||||
{
|
||||
$ini = $this->makeIni();
|
||||
$ini = new IniFile($ini);
|
||||
|
||||
$this->assertNotNull($ini->updatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет исключение при попытке чтения ini-файла по некорректнмоу пути
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
*/
|
||||
public function testFileReadException(): void
|
||||
{
|
||||
$this->expectException(FileReadException::class);
|
||||
$this->expectExceptionMessage('Ошибка чтения файла');
|
||||
$ini = '';
|
||||
|
||||
new IniFile($ini);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет исключение при попытке парсинга битого ini-файла
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
*/
|
||||
public function testIniParsingException(): void
|
||||
{
|
||||
$this->expectException(IniParsingException::class);
|
||||
$this->expectExceptionMessage('Ошибка разбора файла');
|
||||
$ini = $this->makeIni('z]');
|
||||
|
||||
new IniFile($ini);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет успешное получение определение плейлиста из ini-файла
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
* @throws PlaylistNotFoundException
|
||||
*/
|
||||
public function testGetPlaylist(): void
|
||||
{
|
||||
$ini = $this->makeIni();
|
||||
$ini = new IniFile($ini);
|
||||
$isset = isset($ini['foo']);
|
||||
$foo = $ini->playlist('foo');
|
||||
$foo2 = $ini['foo'];
|
||||
|
||||
$this->assertTrue($isset);
|
||||
$this->assertIsArray($foo);
|
||||
$this->assertSame('foo name', $foo['name']);
|
||||
$this->assertSame('foo description', $foo['desc']);
|
||||
$this->assertSame('http://example.com/foo.m3u', $foo['url']);
|
||||
$this->assertSame('http://example.com/', $foo['src']);
|
||||
$this->assertSame($foo, $foo2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет исключение при попытке парсинга битого ini-файла
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws PlaylistNotFoundException
|
||||
* @throws IniParsingException
|
||||
*/
|
||||
public function testPlaylistNotFoundException(): void
|
||||
{
|
||||
$code = 'test';
|
||||
$this->expectException(PlaylistNotFoundException::class);
|
||||
$this->expectExceptionMessage("Плейлист '{$code}' не найден");
|
||||
$ini = $this->makeIni();
|
||||
|
||||
(new IniFile($ini))->playlist($code);
|
||||
}
|
||||
}
|
||||
111
tests/Core/PlaylistTest.php
Normal file
111
tests/Core/PlaylistTest.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?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\Core;
|
||||
|
||||
use App\Core\IniFile;
|
||||
use App\Core\Playlist;
|
||||
use App\Exceptions\FileReadException;
|
||||
use App\Exceptions\IniParsingException;
|
||||
use App\Exceptions\PlaylistNotFoundException;
|
||||
use App\Exceptions\PlaylistWithoutUrlException;
|
||||
use Redis;
|
||||
use Tests\BaseTestCase;
|
||||
use Tests\FixtureHandler;
|
||||
|
||||
class PlaylistTest extends BaseTestCase
|
||||
{
|
||||
use FixtureHandler;
|
||||
|
||||
/**
|
||||
* Проверяет успешное создание объекта
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
* @throws PlaylistNotFoundException
|
||||
*/
|
||||
public function testMain(): void
|
||||
{
|
||||
$code = 'foo';
|
||||
$ini = new IniFile($this->makeIni());
|
||||
$definition = $ini->playlist($code);
|
||||
|
||||
$pls = new Playlist($code, $definition);
|
||||
|
||||
$this->assertSame($code, $pls->code);
|
||||
$this->assertSame($definition['name'], $pls->name);
|
||||
$this->assertSame($definition['desc'], $pls->desc);
|
||||
$this->assertSame($definition['url'], $pls->url);
|
||||
$this->assertSame($definition['src'], $pls->src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет успешное создание объекта при отсутствии значений опциональных параметров
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
* @throws PlaylistNotFoundException
|
||||
*/
|
||||
public function testOptionalParams(): void
|
||||
{
|
||||
$code = 'foo';
|
||||
$ini = new IniFile($this->makeIni());
|
||||
$definition = $ini->playlist($code);
|
||||
unset($definition['name']);
|
||||
unset($definition['desc']);
|
||||
unset($definition['src']);
|
||||
|
||||
$pls = new Playlist($code, $definition);
|
||||
|
||||
$this->assertSame($code, $pls->code);
|
||||
$this->assertNull($pls->name);
|
||||
$this->assertNull($pls->desc);
|
||||
$this->assertSame($definition['url'], $pls->url);
|
||||
$this->assertNull($pls->src);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет исключение при попытке чтения ini-файла по некорректнмоу пути
|
||||
*
|
||||
* @return void
|
||||
* @throws FileReadException
|
||||
* @throws IniParsingException
|
||||
* @throws PlaylistNotFoundException
|
||||
*/
|
||||
public function testPlaylistWithoutUrlException(): void
|
||||
{
|
||||
$code = 'foo';
|
||||
$this->expectException(PlaylistWithoutUrlException::class);
|
||||
$this->expectExceptionMessage("Плейлист '{$code}' имеет неверный url");
|
||||
$ini = new IniFile($this->makeIni());
|
||||
$definition = $ini->playlist($code);
|
||||
unset($definition['url']);
|
||||
|
||||
new Playlist($code, $definition);
|
||||
}
|
||||
|
||||
public function testGetCheckResult(): void
|
||||
{
|
||||
$code = 'foo';
|
||||
$ini = new IniFile($this->makeIni());
|
||||
|
||||
$definition = $ini->playlist($code);
|
||||
$pls = new Playlist($code, $definition);
|
||||
|
||||
$redis = $this->createPartialMock(Redis::class, ['get']);
|
||||
$redis->expects($this->once())->method('get')->with($code)->willReturn(null);
|
||||
|
||||
|
||||
$pls->getCheckResult();
|
||||
|
||||
}
|
||||
}
|
||||
109
tests/FixtureHandler.php
Normal file
109
tests/FixtureHandler.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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 InvalidArgumentException;
|
||||
|
||||
trait FixtureHandler
|
||||
{
|
||||
/**
|
||||
* Вычитывает содержимое файла строкой
|
||||
*
|
||||
* @param string $filepath
|
||||
* @return string
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function loadFixtureContent(string $filepath): string
|
||||
{
|
||||
$filepath = static::buildFixtureFilePath($filepath);
|
||||
is_file($filepath) || throw new InvalidArgumentException('File not found: ' . $filepath);
|
||||
|
||||
return (string) file_get_contents($filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Вычитывает .json файл в php-массив
|
||||
*
|
||||
* @param string $filepath
|
||||
* @param string|null $key
|
||||
* @return array
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function loadJsonFixture(string $filepath, ?string $key = null): array
|
||||
{
|
||||
$contents = $this->loadFixtureContent($filepath);
|
||||
$contents = json_decode($contents, true);
|
||||
|
||||
return $key ? $contents[$key] : $contents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Подгружает фиксутуру для тестов
|
||||
*
|
||||
* @param string $filepath Имя файла или путь до него внутри tests/Fixtures/...
|
||||
* @return mixed
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function loadPhpFixture(string $filepath): mixed
|
||||
{
|
||||
$filepath = static::buildFixtureFilePath($filepath);
|
||||
is_file($filepath) || throw new InvalidArgumentException('File not found: ' . $filepath);
|
||||
|
||||
return require $filepath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохраняет указанные сырые данные в виде файла с данными
|
||||
* для использования в качестве фикстуры в тестах.
|
||||
*
|
||||
* Использование:
|
||||
* 0. предполагается при подготовке к написанию теста
|
||||
* 1. вызвать `makeFixture()`, передав нужные данные
|
||||
* 2. найти файл в `tests/Fixtures/...`, проверить корректность
|
||||
* 3. подгрузить фикстуру и замокать вызов курсорной БД-функции
|
||||
* ```
|
||||
* $fixture = this->loadFixture(...);
|
||||
* $this->mockDbCursor(...)->andReturn($fixture);
|
||||
* ```
|
||||
*
|
||||
* @param array|Collection $data Данные для сохранения в фикстуре
|
||||
* @param string $name Имя файла или путь до него внутри tests/Fixtures/...
|
||||
* @param bool $is_json Сохранить в json-формате
|
||||
*/
|
||||
public static function saveFixture(mixed $data, string $name, bool $is_json = false): void
|
||||
{
|
||||
$data = match (true) {
|
||||
$data instanceof Traversable => iterator_to_array($data),
|
||||
default => $data,
|
||||
};
|
||||
if ($is_json) {
|
||||
$string = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
$ext = 'json';
|
||||
} else {
|
||||
$string = var_export($data, true);
|
||||
$string = preg_replace("/(\n\\s+)?array\\s\\(/", '[', $string); // конвертим в короткий синтаксис
|
||||
$string = str_replace([')', 'NULL'], [']', 'null'], $string); // остатки
|
||||
$string = "<?php\n\ndeclare(strict_types=1);\n\nreturn {$string};\n"; // добавляем заголовок для файла
|
||||
$ext = 'php';
|
||||
}
|
||||
$filepath = __DIR__ . "/Fixtures/{$name}.{$ext}";
|
||||
!file_exists($filepath) && @mkdir(dirname($filepath), recursive: true);
|
||||
$filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath);
|
||||
file_put_contents($filepath, $string);
|
||||
}
|
||||
|
||||
protected static function buildFixtureFilePath(string $filepath): string
|
||||
{
|
||||
$filepath = trim(ltrim($filepath, DIRECTORY_SEPARATOR));
|
||||
|
||||
return __DIR__ . DIRECTORY_SEPARATOR . 'Fixtures' . DIRECTORY_SEPARATOR . $filepath;
|
||||
}
|
||||
}
|
||||
5
tests/Fixtures/broken.ini
Normal file
5
tests/Fixtures/broken.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
n]
|
||||
name=
|
||||
desc=
|
||||
url=
|
||||
src=
|
||||
11
tests/Fixtures/playlists.ini
Normal file
11
tests/Fixtures/playlists.ini
Normal file
@@ -0,0 +1,11 @@
|
||||
[p1]
|
||||
name=
|
||||
desc=
|
||||
url=
|
||||
src=
|
||||
|
||||
[z2]
|
||||
name=
|
||||
desc=
|
||||
url=
|
||||
src=
|
||||
Reference in New Issue
Block a user