diff --git a/app/appConf/base_config.php b/app/appConf/base_config.php index 612592ca..f7adccca 100644 --- a/app/appConf/base_config.php +++ b/app/appConf/base_config.php @@ -2,16 +2,16 @@ /** * Hummingbird Anime List Client * - * An API client for Kitsu and MyAnimeList to manage anime and manga watch lists + * An API client for Kitsu to manage anime and manga watch lists * - * PHP version 7 + * PHP version 8 * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2017 Timothy J. Warren + * @copyright 2015 - 2021 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @version 4.0 - * @link https://github.com/timw4mail/HummingBirdAnimeClient + * @version 5.2 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient */ use function Aviat\AnimeClient\loadConfig; @@ -21,12 +21,13 @@ use function Aviat\AnimeClient\loadConfig; // // You shouldn't generally need to change anything below this line // ---------------------------------------------------------------------------- -$APP_DIR = realpath(__DIR__ . '/../'); -$ROOT_DIR = realpath("{$APP_DIR}/../"); +$APP_DIR = dirname(__DIR__); +$ROOT_DIR = dirname($APP_DIR); $tomlConfig = loadConfig(__DIR__); return array_merge($tomlConfig, [ + 'root' => $ROOT_DIR, 'asset_dir' => "{$ROOT_DIR}/public", 'base_config_dir' => __DIR__, 'config_dir' => "{$APP_DIR}/config", diff --git a/app/appConf/routes.php b/app/appConf/routes.php index e5d5de3a..f599c439 100644 --- a/app/appConf/routes.php +++ b/app/appConf/routes.php @@ -2,15 +2,15 @@ /** * Hummingbird Anime List Client * - * An API client for Kitsu and MyAnimeList to manage anime and manga watch lists + * An API client for Kitsu to manage anime and manga watch lists * - * PHP version 7 + * PHP version 8 * * @package HummingbirdAnimeClient * @author Timothy J. Warren - * @copyright 2015 - 2018 Timothy J. Warren + * @copyright 2015 - 2021 Timothy J. Warren * @license http://www.opensource.org/licenses/mit-license.html MIT License - * @version 4.0 + * @version 5.2 * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient */ diff --git a/app/bootstrap.php b/app/bootstrap.php index 7594652c..d9c88629 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -34,11 +34,11 @@ use Psr\SimpleCache\CacheInterface; use function Aviat\Ion\_dir; -if ( ! defined('APP_DIR')) +if ( ! defined('HB_APP_DIR')) { - define('APP_DIR', __DIR__); - define('ROOT_DIR', dirname(APP_DIR)); - define('TEMPLATE_DIR', _dir(APP_DIR, 'templates')); + define('HB_APP_DIR', __DIR__); + define('ROOT_DIR', dirname(HB_APP_DIR)); + define('TEMPLATE_DIR', _dir(HB_APP_DIR, 'templates')); } // ----------------------------------------------------------------------------- @@ -50,7 +50,7 @@ return static function (array $configArray = []): Container { // ------------------------------------------------------------------------- // Logging // ------------------------------------------------------------------------- - $LOG_DIR = _dir(APP_DIR, 'logs'); + $LOG_DIR = _dir(HB_APP_DIR, 'logs'); $appLogger = new Logger('animeclient'); $appLogger->pushHandler(new RotatingFileHandler(_dir($LOG_DIR, 'app.log'), 2, Logger::WARNING)); diff --git a/build/update_header_comments.php b/build/update_header_comments.php index e5bf9191..efc9d911 100644 --- a/build/update_header_comments.php +++ b/build/update_header_comments.php @@ -2,6 +2,7 @@ declare(strict_types=1); $file_patterns = [ + 'app/appConf/*.php', 'app/bootstrap.php', 'migrations/*.php', 'src/**/*.php', @@ -16,7 +17,7 @@ if ( ! function_exists('glob_recursive')) { // Does not support flag GLOB_BRACE - function glob_recursive($pattern, $flags = 0) + function glob_recursive(string $pattern, int $flags = 0): array { $files = glob($pattern, $flags); @@ -57,17 +58,21 @@ function get_text_to_replace(array $tokens): string return $output; } -function get_tokens($source): array +function get_tokens(string $source): array { return token_get_all($source); } -function replace_files(array $files, $template) +function replace_files(array $files, string $template): void { print_r($files); foreach ($files as $file) { $source = file_get_contents($file); + if ($source === FALSE) + { + continue; + } if (stripos($source, 'namespace') === FALSE) { diff --git a/phpstan.neon b/phpstan.neon index 5a474a6a..168c502d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,6 +4,7 @@ parameters: inferPrivatePropertyTypeFromConstructor: true level: 8 paths: + - app/appConf - src - ./console - index.php diff --git a/src/AnimeClient/AnimeClient.php b/src/AnimeClient/AnimeClient.php index 1a903d89..43518e15 100644 --- a/src/AnimeClient/AnimeClient.php +++ b/src/AnimeClient/AnimeClient.php @@ -186,7 +186,7 @@ function checkFolderPermissions(ConfigInterface $config): array $errors = []; $publicDir = $config->get('asset_dir'); - $APP_DIR = _dir(dirname(__DIR__, 2), '/app'); + $APP_DIR = _dir($config->get('root'), 'app'); $pathMap = [ 'app/config' => "{$APP_DIR}/config", @@ -211,7 +211,9 @@ function checkFolderPermissions(ConfigInterface $config): array if ( ! $writable) { + // @codeCoverageIgnoreStart $errors['writable'][] = $pretty; + // @codeCoverageIgnoreEnd } } @@ -292,6 +294,7 @@ function getLocalImg (string $kitsuUrl, $webp = TRUE): string /** * Create a transparent placeholder image * + * @codeCoverageIgnore * @param string $path * @param int|null $width * @param int|null $height @@ -378,7 +381,6 @@ function colNotEmpty(array $search, string $key): bool * * @param CacheInterface $cache * @return bool - * @throws Throwable */ function clearCache(CacheInterface $cache): bool { @@ -393,9 +395,7 @@ function clearCache(CacheInterface $cache): bool $userData = array_filter((array)$userData, static fn ($value) => $value !== NULL); $cleared = $cache->clear(); - $saved = ( ! empty($userData)) - ? $cache->setMultiple($userData) - : TRUE; + $saved = ( ! empty($userData)) ? $cache->setMultiple($userData) : TRUE; return $cleared && $saved; } diff --git a/src/AnimeClient/Types/AbstractType.php b/src/AnimeClient/Types/AbstractType.php index 32766d2e..1dc3b593 100644 --- a/src/AnimeClient/Types/AbstractType.php +++ b/src/AnimeClient/Types/AbstractType.php @@ -236,6 +236,9 @@ abstract class AbstractType implements ArrayAccess, Countable { return TRUE; } + /** + * @codeCoverageIgnore + */ final protected function fromObject(mixed $parent = null): float|null|bool|int|array|string { $object = $parent ?? $this; diff --git a/src/AnimeClient/Types/Config.php b/src/AnimeClient/Types/Config.php index 9de10ffd..d7b9663c 100644 --- a/src/AnimeClient/Types/Config.php +++ b/src/AnimeClient/Types/Config.php @@ -32,6 +32,8 @@ class Config extends AbstractType { // Settings in config.toml // ------------------------------------------------------------------------ + public string $root; // Path to app root + public ?string $asset_path; // Path to public folder for urls /** @@ -62,8 +64,6 @@ class Config extends AbstractType { /** * Default list view type * 'cover_view' or 'list_view' - * - * @var string */ public ?string $default_view_type; @@ -71,21 +71,13 @@ class Config extends AbstractType { public bool $secure_urls = TRUE; - /** - * @var string|bool - */ public string|bool $show_anime_collection = FALSE; - /** - * @var string|bool - */ public string|bool $show_manga_collection = FALSE; /** * CSS theme: light, dark, or auto-switching * 'auto', 'light', or 'dark' - * - * @var string|null */ public ?string $theme = 'auto'; diff --git a/tests/AnimeClient/AnimeClientTest.php b/tests/AnimeClient/AnimeClientTest.php index f5ba4d99..fa75910d 100644 --- a/tests/AnimeClient/AnimeClientTest.php +++ b/tests/AnimeClient/AnimeClientTest.php @@ -16,9 +16,11 @@ namespace Aviat\AnimeClient\Tests; -use Amp\Http\Client\Response; - use function Aviat\AnimeClient\arrayToToml; +use function Aviat\AnimeClient\checkFolderPermissions; +use function Aviat\AnimeClient\clearCache; +use function Aviat\AnimeClient\colNotEmpty; +use function Aviat\AnimeClient\getLocalImg; use function Aviat\AnimeClient\getResponse; use function Aviat\AnimeClient\isSequentialArray; use function Aviat\AnimeClient\tomlToArray; @@ -89,4 +91,46 @@ class AnimeClientTest extends AnimeClientTestCase { $this->assertNotEmpty(getResponse('https://example.com')); } + + public function testCheckFolderPermissions(): void + { + $config = $this->container->get('config'); + $actual = checkFolderPermissions($config); + $this->assertTrue(is_array($actual)); + } + + public function testGetLocalImageEmptyUrl(): void + { + $actual = getLocalImg(''); + $this->assertEquals('images/placeholder.webp', $actual); + } + + public function testGetLocalImageBadUrl(): void + { + $actual = getLocalImg('//foo.bar'); + $this->assertEquals('images/placeholder.webp', $actual); + } + + public function testColNotEmpty(): void + { + $hasEmptyCols = [[ + 'foo' => '', + ], [ + 'foo' => '', + ]]; + + $hasNonEmptyCols = [[ + 'foo' => 'bar', + ], [ + 'foo' => 'baz', + ]]; + + $this->assertEquals(false, colNotEmpty($hasEmptyCols, 'foo')); + $this->assertEquals(true, colNotEmpty($hasNonEmptyCols, 'foo')); + } + + public function testClearCache(): void + { + $this->assertTrue(clearCache($this->container->get('cache'))); + } } \ No newline at end of file diff --git a/tests/AnimeClient/AnimeClientTestCase.php b/tests/AnimeClient/AnimeClientTestCase.php index 88a2db62..3da703f2 100644 --- a/tests/AnimeClient/AnimeClientTestCase.php +++ b/tests/AnimeClient/AnimeClientTestCase.php @@ -16,6 +16,7 @@ namespace Aviat\AnimeClient\Tests; +use Aviat\Ion\Di\ContainerInterface; use function Aviat\Ion\_dir; use Aviat\Ion\Json; @@ -59,6 +60,7 @@ class AnimeClientTestCase extends TestCase { parent::setUp(); $config_array = [ + 'root' => self::ROOT_DIR, 'asset_path' => '/assets', 'img_cache_path' => _dir(self::ROOT_DIR, 'public/images'), 'data_cache_path' => _dir(self::TEST_DATA_DIR, 'cache'), @@ -94,7 +96,7 @@ class AnimeClientTestCase extends TestCase { ]; // Set up DI container - $di = require _dir(self::ROOT_DIR, 'app', 'bootstrap.php'); + $di = require self::ROOT_DIR . '/app/bootstrap.php'; $container = $di($config_array); // Use mock session handler diff --git a/tests/AnimeClient/Types/ConfigTest.php b/tests/AnimeClient/Types/ConfigTest.php new file mode 100644 index 00000000..76ae05e6 --- /dev/null +++ b/tests/AnimeClient/Types/ConfigTest.php @@ -0,0 +1,53 @@ + + * @copyright 2015 - 2021 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.2 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Tests\Types; + +use Aviat\AnimeClient\Types\Config; +use Aviat\AnimeClient\Types\UndefinedPropertyException; + +class ConfigTest extends ConfigTestCase { + public function setUp(): void + { + parent::setUp(); + + $this->testClass = Config::class; + } + + public function testSetMethods(): void + { + $type = $this->testClass::from([ + 'anilist' => [], + 'cache' => [], + 'database' => [], + ]); + + $this->assertEquals(3, $type->count()); + } + + public function testOffsetUnset(): void + { + $type = $this->testClass::from([ + 'anilist' => [], + ]); + + $this->assertTrue($type->offsetExists('anilist')); + + $type->offsetUnset('anilist'); + + $this->assertNotTrue($type->offsetExists('anilist')); + } +} \ No newline at end of file diff --git a/tests/AnimeClient/Types/ConfigTestCase.php b/tests/AnimeClient/Types/ConfigTestCase.php new file mode 100644 index 00000000..1106117f --- /dev/null +++ b/tests/AnimeClient/Types/ConfigTestCase.php @@ -0,0 +1,72 @@ + + * @copyright 2015 - 2021 Timothy J. Warren + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @version 5.2 + * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient + */ + +namespace Aviat\AnimeClient\Tests\Types; + +use Aviat\AnimeClient\Tests\AnimeClientTestCase; +use Aviat\AnimeClient\Types\UndefinedPropertyException; + +abstract class ConfigTestCase extends AnimeClientTestCase { + public string $testClass; + + public function testCheck(): void + { + $result = $this->testClass::check([]); + $this->assertEquals([], $result); + } + + public function testSetUndefinedProperty(): void + { + $this->expectException(UndefinedPropertyException::class); + $this->testClass::from([ + 'foobar' => 'baz', + ]); + } + + public function testToString(): void + { + $actual = $this->testClass::from([])->__toString(); + $this->assertMatchesSnapshot($actual); + } + + public function testOffsetExists(): void + { + $actual = $this->testClass::from([ + 'anilist' => [], + ])->offsetExists('anilist'); + $this->assertTrue($actual); + } + + public function testSetState(): void + { + $normal = $this->testClass::from([]); + $setState = $this->testClass::__set_state([]); + + $this->assertEquals($normal, $setState); + } + + public function testIsEmpty(): void + { + $type = $this->testClass::from([]); + $this->assertTrue($type->isEmpty()); + } + + public function testCount(): void + { + $type = $this->testClass::from([]); + $this->assertEquals(0, $type->count()); + } +} \ No newline at end of file diff --git a/tests/AnimeClient/Types/__snapshots__/ConfigTest__testToString__1.txt b/tests/AnimeClient/Types/__snapshots__/ConfigTest__testToString__1.txt new file mode 100644 index 00000000..5663ee41 --- /dev/null +++ b/tests/AnimeClient/Types/__snapshots__/ConfigTest__testToString__1.txt @@ -0,0 +1,3 @@ +Aviat\AnimeClient\Types\Config Object +( +)