refactor: logger (#3678)

This commit is contained in:
Dag 2023-09-21 22:05:55 +02:00 committed by GitHub
parent 360f953be8
commit 7329b83cc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 297 additions and 338 deletions

View File

@ -3,13 +3,19 @@
class DisplayAction implements ActionInterface
{
private CacheInterface $cache;
private Logger $logger;
public function __construct()
{
$this->cache = RssBridge::getCache();
$this->logger = RssBridge::getLogger();
}
public function execute(array $request)
{
if (Configuration::getConfig('system', 'enable_maintenance_mode')) {
return new Response('503 Service Unavailable', 503);
}
$this->cache = RssBridge::getCache();
$cacheKey = 'http_' . json_encode($request);
/** @var Response $cachedResponse */
$cachedResponse = $this->cache->get($cacheKey);
@ -113,15 +119,15 @@ class DisplayAction implements ActionInterface
if ($e instanceof HttpException) {
// Reproduce (and log) these responses regardless of error output and report limit
if ($e->getCode() === 429) {
Logger::info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
$this->logger->info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
return new Response('429 Too Many Requests', 429);
}
if ($e->getCode() === 503) {
Logger::info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
$this->logger->info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
return new Response('503 Service Unavailable', 503);
}
}
Logger::error(sprintf('Exception in DisplayAction(%s)', $bridge->getShortName()), ['e' => $e]);
$this->logger->error(sprintf('Exception in DisplayAction(%s)', $bridge->getShortName()), ['e' => $e]);
$errorOutput = Configuration::getConfig('error', 'output');
$reportLimit = Configuration::getConfig('error', 'report_limit');
$errorCount = 1;

View File

@ -14,6 +14,13 @@
class SetBridgeCacheAction implements ActionInterface
{
private CacheInterface $cache;
public function __construct()
{
$this->cache = RssBridge::getCache();
}
public function execute(array $request)
{
$authenticationMiddleware = new ApiAuthenticationMiddleware();
@ -35,18 +42,15 @@ class SetBridgeCacheAction implements ActionInterface
// whitelist control
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge->loadConfiguration();
$value = $request['value'];
$cache = RssBridge::getCache();
$cacheKey = get_class($bridge) . '_' . $key;
$ttl = 86400 * 3;
$cache->set($cacheKey, $value, $ttl);
$this->cache->set($cacheKey, $value, $ttl);
header('Content-Type: text/plain');
echo 'done';

View File

@ -48,7 +48,6 @@ class EZTVBridge extends BridgeAbstract
public function collectData()
{
$eztv_uri = $this->getEztvUri();
Logger::debug($eztv_uri);
$ids = explode(',', trim($this->getInput('ids')));
foreach ($ids as $id) {
$data = json_decode(getContents(sprintf('%s/api/get-torrents?imdb_id=%s', $eztv_uri, $id)));

View File

@ -113,15 +113,14 @@ class ElloBridge extends BridgeAbstract
private function getAPIKey()
{
$cache = RssBridge::getCache();
$cacheKey = 'ElloBridge_key';
$apiKey = $cache->get($cacheKey);
$apiKey = $this->cache->get($cacheKey);
if (!$apiKey) {
$keyInfo = getContents(self::URI . 'api/webapp-token') or returnServerError('Unable to get token.');
$apiKey = json_decode($keyInfo)->token->access_token;
$ttl = 60 * 60 * 20;
$cache->set($cacheKey, $apiKey, $ttl);
$this->cache->set($cacheKey, $apiKey, $ttl);
}
return $apiKey;

View File

@ -63,7 +63,7 @@ TEXT;
try {
$this->collectExpandableDatas($feed);
} catch (HttpException $e) {
Logger::warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
$this->logger->warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
$this->items[] = [
'title' => 'RSS-Bridge: ' . $e->getMessage(),
// Give current time so it sorts to the top
@ -73,7 +73,7 @@ TEXT;
} catch (\Exception $e) {
if (str_starts_with($e->getMessage(), 'Unable to parse xml')) {
// Allow this particular exception from FeedExpander
Logger::warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
$this->logger->warning(sprintf('Exception in FeedMergeBridge: %s', create_sane_exception_message($e)));
continue;
}
throw $e;

View File

@ -217,7 +217,7 @@ HTML,
if ($relativeDate) {
date_sub($date, $relativeDate);
} else {
Logger::info(sprintf('Unable to parse date string: %s', $dateString));
$this->logger->info(sprintf('Unable to parse date string: %s', $dateString));
}
return date_format($date, 'r');
}

View File

@ -98,9 +98,8 @@ class InstagramBridge extends BridgeAbstract
return $username;
}
$cache = RssBridge::getCache();
$cacheKey = 'InstagramBridge_' . $username;
$pk = $cache->get($cacheKey);
$pk = $this->cache->get($cacheKey);
if (!$pk) {
$data = $this->getContents(self::URI . 'web/search/topsearch/?query=' . $username);
@ -112,7 +111,7 @@ class InstagramBridge extends BridgeAbstract
if (!$pk) {
returnServerError('Unable to find username in search result.');
}
$cache->set($cacheKey, $pk);
$this->cache->set($cacheKey, $pk);
}
return $pk;
}

View File

@ -72,12 +72,6 @@ class RedditBridge extends BridgeAbstract
]
]
];
private CacheInterface $cache;
public function __construct()
{
$this->cache = RssBridge::getCache();
}
public function collectData()
{

View File

@ -36,15 +36,12 @@ class SoundCloudBridge extends BridgeAbstract
private $feedTitle = null;
private $feedIcon = null;
private CacheInterface $cache;
private $clientIdRegex = '/client_id.*?"(.+?)"/';
private $widgetRegex = '/widget-.+?\.js/';
public function collectData()
{
$this->cache = RssBridge::getCache();
$res = $this->getUser($this->getInput('u'));
$this->feedTitle = $res->username;

View File

@ -278,10 +278,9 @@ class SpotifyBridge extends BridgeAbstract
private function fetchAccessToken()
{
$cache = RssBridge::getCache();
$cacheKey = sprintf('SpotifyBridge:%s:%s', $this->getInput('clientid'), $this->getInput('clientsecret'));
$token = $cache->get($cacheKey);
$token = $this->cache->get($cacheKey);
if ($token) {
$this->token = $token;
} else {
@ -294,7 +293,7 @@ class SpotifyBridge extends BridgeAbstract
$data = Json::decode($json);
$this->token = $data['access_token'];
$cache->set($cacheKey, $this->token, 3600);
$this->cache->set($cacheKey, $this->token, 3600);
}
}

View File

@ -234,8 +234,7 @@ EOD
$tweets = [];
// Get authentication information
$cache = RssBridge::getCache();
$api = new TwitterClient($cache);
$api = new TwitterClient($this->cache);
// Try to get all tweets
switch ($this->queriedContext) {
case 'By username':

View File

@ -77,12 +77,6 @@ class YoutubeBridge extends BridgeAbstract
private $channel_name = '';
// This took from repo BetterVideoRss of VerifiedJoseph.
const URI_REGEX = '/(https?:\/\/(?:www\.)?(?:[a-zA-Z0-9-.]{2,256}\.[a-z]{2,20})(\:[0-9]{2 ,4})?(?:\/[a-zA-Z0-9@:%_\+.,~#"\'!?&\/\/=\-*]+|\/)?)/ims'; //phpcs:ignore
private CacheInterface $cache;
public function __construct()
{
$this->cache = RssBridge::getCache();
}
private function collectDataInternal()
{
@ -368,7 +362,7 @@ class YoutubeBridge extends BridgeAbstract
$scriptRegex = '/var ytInitialData = (.*?);<\/script>/';
$result = preg_match($scriptRegex, $html, $matches);
if (! $result) {
Logger::debug('Could not find ytInitialData');
$this->logger->debug('Could not find ytInitialData');
return null;
}
return json_decode($matches[1]);

View File

@ -180,13 +180,13 @@ class ZDNetBridge extends FeedExpander
$article = getSimpleHTMLDOMCached($item['uri']);
if (!$article) {
Logger::info('Unable to parse the dom from ' . $item['uri']);
$this->logger->info('Unable to parse the dom from ' . $item['uri']);
return $item;
}
$articleTag = $article->find('article', 0) ?? $article->find('.c-articleContent', 0);
if (!$articleTag) {
Logger::info('Unable to parse <article> tag in ' . $item['uri']);
$this->logger->info('Unable to parse <article> tag in ' . $item['uri']);
return $item;
}
$contents = $articleTag->innertext;

View File

@ -4,10 +4,14 @@ declare(strict_types=1);
class FileCache implements CacheInterface
{
private Logger $logger;
private array $config;
public function __construct(array $config = [])
{
public function __construct(
Logger $logger,
array $config = []
) {
$this->logger = $logger;
$default = [
'path' => null,
'enable_purge' => true,
@ -28,7 +32,7 @@ class FileCache implements CacheInterface
}
$item = unserialize(file_get_contents($cacheFile));
if ($item === false) {
Logger::warning(sprintf('Failed to unserialize: %s', $cacheFile));
$this->logger->warning(sprintf('Failed to unserialize: %s', $cacheFile));
$this->delete($key);
return $default;
}

View File

@ -4,10 +4,15 @@ declare(strict_types=1);
class MemcachedCache implements CacheInterface
{
private Logger $logger;
private \Memcached $conn;
public function __construct(string $host, int $port)
{
public function __construct(
Logger $logger,
string $host,
int $port
) {
$this->logger = $logger;
$this->conn = new \Memcached();
// This call does not actually connect to server yet
if (!$this->conn->addServer($host, $port)) {
@ -29,7 +34,7 @@ class MemcachedCache implements CacheInterface
$expiration = $ttl === null ? 0 : time() + $ttl;
$result = $this->conn->set($key, $value, $expiration);
if ($result === false) {
Logger::warning('Failed to store an item in memcached', [
$this->logger->warning('Failed to store an item in memcached', [
'key' => $key,
'code' => $this->conn->getLastErrorCode(),
'message' => $this->conn->getLastErrorMessage(),

View File

@ -8,11 +8,15 @@ declare(strict_types=1);
*/
class SQLiteCache implements CacheInterface
{
private \SQLite3 $db;
private Logger $logger;
private array $config;
private \SQLite3 $db;
public function __construct(array $config)
{
public function __construct(
Logger $logger,
array $config
) {
$this->logger = $logger;
$default = [
'file' => null,
'timeout' => 5000,
@ -59,7 +63,7 @@ class SQLiteCache implements CacheInterface
$blob = $row['value'];
$value = unserialize($blob);
if ($value === false) {
Logger::error(sprintf("Failed to unserialize: '%s'", mb_substr($blob, 0, 100)));
$this->logger->error(sprintf("Failed to unserialize: '%s'", mb_substr($blob, 0, 100)));
// delete?
return $default;
}
@ -68,6 +72,7 @@ class SQLiteCache implements CacheInterface
// delete?
return $default;
}
public function set(string $key, $value, int $ttl = null): void
{
$cacheKey = $this->createCacheKey($key);

View File

@ -27,8 +27,15 @@ abstract class BridgeAbstract
protected string $queriedContext = '';
private array $configuration = [];
public function __construct()
{
protected CacheInterface $cache;
protected Logger $logger;
public function __construct(
CacheInterface $cache,
Logger $logger
) {
$this->cache = $cache;
$this->logger = $logger;
}
abstract public function collectData();
@ -310,16 +317,14 @@ abstract class BridgeAbstract
protected function loadCacheValue(string $key)
{
$cache = RssBridge::getCache();
$cacheKey = $this->getShortName() . '_' . $key;
return $cache->get($cacheKey);
return $this->cache->get($cacheKey);
}
protected function saveCacheValue(string $key, $value, $ttl = 86400)
{
$cache = RssBridge::getCache();
$cacheKey = $this->getShortName() . '_' . $key;
$cache->set($cacheKey, $value, $ttl);
$this->cache->set($cacheKey, $value, $ttl);
}
public function getShortName(): string

View File

@ -2,12 +2,17 @@
final class BridgeFactory
{
private CacheInterface $cache;
private Logger $logger;
private $bridgeClassNames = [];
private $enabledBridges = [];
private $missingEnabledBridges = [];
public function __construct()
{
$this->cache = RssBridge::getCache();
$this->logger = RssBridge::getLogger();
// Create all possible bridge class names from fs
foreach (scandir(__DIR__ . '/../bridges/') as $file) {
if (preg_match('/^([^.]+Bridge)\.php$/U', $file, $m)) {
@ -29,14 +34,14 @@ final class BridgeFactory
$this->enabledBridges[] = $bridgeClassName;
} else {
$this->missingEnabledBridges[] = $enabledBridge;
Logger::info(sprintf('Bridge not found: %s', $enabledBridge));
$this->logger->info(sprintf('Bridge not found: %s', $enabledBridge));
}
}
}
public function create(string $name): BridgeAbstract
{
return new $name();
return new $name($this->cache, $this->logger);
}
public function isEnabled(string $bridgeName): bool

View File

@ -4,6 +4,14 @@ declare(strict_types=1);
class CacheFactory
{
private Logger $logger;
public function __construct(
Logger $logger
) {
$this->logger = $logger;
}
public function create(string $name = null): CacheInterface
{
$name ??= Configuration::getConfig('cache', 'type');
@ -49,7 +57,7 @@ class CacheFactory
if (!is_writable($fileCacheConfig['path'])) {
throw new \Exception(sprintf('The FileCache path is not writable: %s', $fileCacheConfig['path']));
}
return new FileCache($fileCacheConfig);
return new FileCache($this->logger, $fileCacheConfig);
case SQLiteCache::class:
if (!extension_loaded('sqlite3')) {
throw new \Exception('"sqlite3" extension not loaded. Please check "php.ini"');
@ -66,7 +74,7 @@ class CacheFactory
} elseif (!is_dir(dirname($file))) {
throw new \Exception(sprintf('Invalid configuration for %s', 'SQLiteCache'));
}
return new SQLiteCache([
return new SQLiteCache($this->logger, [
'file' => $file,
'timeout' => Configuration::getConfig('SQLiteCache', 'timeout'),
'enable_purge' => Configuration::getConfig('SQLiteCache', 'enable_purge'),
@ -94,7 +102,7 @@ class CacheFactory
if ($port < 1 || $port > 65535) {
throw new \Exception('"port" param is invalid for ' . $section);
}
return new MemcachedCache($host, $port);
return new MemcachedCache($this->logger, $host, $port);
default:
if (!file_exists(PATH_LIB_CACHES . $className . '.php')) {
throw new \Exception('Unable to find the cache file');

View File

@ -24,6 +24,8 @@ class Debug
array_pop($trace);
$lastFrame = $trace[array_key_last($trace)];
$text = sprintf('%s(%s): %s', $lastFrame['file'], $lastFrame['line'], $message);
Logger::debug($text);
$logger = RssBridge::getLogger();
$logger->debug($text);
}
}

View File

@ -113,7 +113,7 @@ abstract class FeedExpander extends BridgeAbstract
if ($rssContent === false) {
$xmlErrors = libxml_get_errors();
foreach ($xmlErrors as $xmlError) {
Logger::debug(trim($xmlError->message));
Debug::log(trim($xmlError->message));
}
if ($xmlErrors) {
// Render only the first error into exception message

View File

@ -1,97 +0,0 @@
<?php
declare(strict_types=1);
final class Logger
{
public static function debug(string $message, array $context = [])
{
self::log('DEBUG', $message, $context);
}
public static function info(string $message, array $context = []): void
{
self::log('INFO', $message, $context);
}
public static function warning(string $message, array $context = []): void
{
self::log('WARNING', $message, $context);
}
public static function error(string $message, array $context = []): void
{
self::log('ERROR', $message, $context);
}
private static function log(string $level, string $message, array $context = []): void
{
if (!Debug::isEnabled() && $level === 'DEBUG') {
// Don't log this debug log record because debug mode is disabled
return;
}
if (isset($context['e'])) {
/** @var \Throwable $e */
$e = $context['e'];
unset($context['e']);
$context['type'] = get_class($e);
$context['code'] = $e->getCode();
$context['message'] = sanitize_root($e->getMessage());
$context['file'] = sanitize_root($e->getFile());
$context['line'] = $e->getLine();
$context['url'] = get_current_url();
$context['trace'] = trace_to_call_points(trace_from_exception($e));
// Don't log these exceptions
// todo: this logic belongs in log handler
$ignoredExceptions = [
'You must specify a format',
'Format name invalid',
'Unknown format given',
'Bridge name invalid',
'Invalid action',
'twitter: No results for this query',
// telegram
'Unable to find channel. The channel is non-existing or non-public',
// fb
'This group is not public! RSS-Bridge only supports public groups!',
'You must be logged in to view this page',
'Unable to get the page id. You should consider getting the ID by hand',
// tiktok 404
'https://www.tiktok.com/@',
];
foreach ($ignoredExceptions as $ignoredException) {
if (str_starts_with($e->getMessage(), $ignoredException)) {
return;
}
}
}
if ($context) {
try {
$context = Json::encode($context);
} catch (\JsonException $e) {
$context['message'] = null;
$context = Json::encode($context);
}
} else {
$context = '';
}
$text = sprintf(
"[%s] rssbridge.%s %s %s\n",
now()->format('Y-m-d H:i:s'),
$level,
// Intentionally not sanitizing $message
$message,
$context
);
// Log to stderr/stdout whatever that is
// todo: extract to log handler
error_log($text);
// Log to file
// todo: extract to log handler
//$bytes = file_put_contents('/tmp/rss-bridge.log', $text, FILE_APPEND | LOCK_EX);
}
}

View File

@ -2,8 +2,9 @@
final class RssBridge
{
private static HttpClient $httpClient;
private static CacheInterface $cache;
private static Logger $logger;
private static HttpClient $httpClient;
public function __construct()
{
@ -19,7 +20,7 @@ final class RssBridge
date_default_timezone_set(Configuration::getConfig('system', 'timezone'));
set_exception_handler(function (\Throwable $e) {
Logger::error('Uncaught Exception', ['e' => $e]);
self::$logger->error('Uncaught Exception', ['e' => $e]);
http_response_code(500);
print render(__DIR__ . '/../templates/error.html.php', ['e' => $e]);
exit(1);
@ -35,7 +36,7 @@ final class RssBridge
sanitize_root($file),
$line
);
Logger::warning($text);
self::$logger->warning($text);
if (Debug::isEnabled()) {
print sprintf("<pre>%s</pre>\n", e($text));
}
@ -52,17 +53,23 @@ final class RssBridge
sanitize_root($error['file']),
$error['line']
);
Logger::error($message);
self::$logger->error($message);
if (Debug::isEnabled()) {
// todo: extract to log handler
print sprintf("<pre>%s</pre>\n", e($message));
}
}
});
self::$logger = new SimpleLogger('rssbridge');
if (Debug::isEnabled()) {
self::$logger->addHandler(new StreamHandler(Logger::DEBUG));
} else {
self::$logger->addHandler(new StreamHandler(Logger::INFO));
}
self::$httpClient = new CurlHttpClient();
$cacheFactory = new CacheFactory();
$cacheFactory = new CacheFactory(self::$logger);
if (Debug::isEnabled()) {
self::$cache = $cacheFactory->create('array');
} else {
@ -108,19 +115,24 @@ final class RssBridge
$response->send();
}
} catch (\Throwable $e) {
Logger::error('Exception in RssBridge::main()', ['e' => $e]);
self::$logger->error('Exception in RssBridge::main()', ['e' => $e]);
http_response_code(500);
print render(__DIR__ . '/../templates/error.html.php', ['e' => $e]);
}
}
public static function getCache(): CacheInterface
{
return self::$cache;
}
public static function getLogger(): Logger
{
return self::$logger;
}
public static function getHttpClient(): HttpClient
{
return self::$httpClient;
}
public static function getCache(): CacheInterface
{
return self::$cache ?? new NullCache();
}
}

View File

@ -43,6 +43,7 @@ $files = [
__DIR__ . '/../lib/php8backports.php',
__DIR__ . '/../lib/utils.php',
__DIR__ . '/../lib/http.php',
__DIR__ . '/../lib/logger.php',
// Vendor
__DIR__ . '/../vendor/parsedown/Parsedown.php',
__DIR__ . '/../vendor/php-urljoin/src/urljoin.php',

172
lib/logger.php Normal file
View File

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
interface Logger
{
public const DEBUG = 10;
public const INFO = 20;
public const WARNING = 30;
public const ERROR = 40;
public const LEVEL_NAMES = [
self::DEBUG => 'DEBUG',
self::INFO => 'INFO',
self::WARNING => 'WARNING',
self::ERROR => 'ERROR',
];
public function debug(string $message, array $context = []);
public function info(string $message, array $context = []): void;
public function warning(string $message, array $context = []): void;
public function error(string $message, array $context = []): void;
}
final class SimpleLogger implements Logger
{
private string $name;
private array $handlers;
/**
* @param callable[] $handlers
*/
public function __construct(
string $name,
array $handlers = []
) {
$this->name = $name;
$this->handlers = $handlers;
}
public function addHandler(callable $fn)
{
$this->handlers[] = $fn;
}
public function debug(string $message, array $context = [])
{
$this->log(self::DEBUG, $message, $context);
}
public function info(string $message, array $context = []): void
{
$this->log(self::INFO, $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log(self::WARNING, $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log(self::ERROR, $message, $context);
}
private function log(int $level, string $message, array $context = []): void
{
foreach ($this->handlers as $handler) {
$handler([
'name' => $this->name,
'created_at' => now(),
'level' => $level,
'level_name' => self::LEVEL_NAMES[$level],
'message' => $message,
'context' => $context,
]);
}
}
}
final class StreamHandler
{
private int $level;
public function __construct(int $level = Logger::DEBUG)
{
$this->level = $level;
}
public function __invoke(array $record)
{
if ($record['level'] < $this->level) {
return;
}
if (isset($record['context']['e'])) {
/** @var \Throwable $e */
$e = $record['context']['e'];
unset($record['context']['e']);
$record['context']['type'] = get_class($e);
$record['context']['code'] = $e->getCode();
$record['context']['message'] = sanitize_root($e->getMessage());
$record['context']['file'] = sanitize_root($e->getFile());
$record['context']['line'] = $e->getLine();
$record['context']['url'] = get_current_url();
$record['context']['trace'] = trace_to_call_points(trace_from_exception($e));
$ignoredExceptions = [
'You must specify a format',
'Format name invalid',
'Unknown format given',
'Bridge name invalid',
'Invalid action',
'twitter: No results for this query',
// telegram
'Unable to find channel. The channel is non-existing or non-public',
// fb
'This group is not public! RSS-Bridge only supports public groups!',
'You must be logged in to view this page',
'Unable to get the page id. You should consider getting the ID by hand',
// tiktok 404
'https://www.tiktok.com/@',
];
foreach ($ignoredExceptions as $ignoredException) {
if (str_starts_with($e->getMessage(), $ignoredException)) {
return;
}
}
}
$context = '';
if ($record['context']) {
try {
$context = Json::encode($record['context']);
} catch (\JsonException $e) {
$record['context']['message'] = null;
$context = Json::encode($record['context']);
}
}
$text = sprintf(
"[%s] %s.%s %s %s\n",
$record['created_at']->format('Y-m-d H:i:s'),
$record['name'],
$record['level_name'],
// Should probably sanitize message for output context
$record['message'],
$context
);
error_log($text);
//$bytes = file_put_contents('/tmp/rss-bridge.log', $text, FILE_APPEND | LOCK_EX);
}
}
final class NullLogger implements Logger
{
public function debug(string $message, array $context = [])
{
}
public function info(string $message, array $context = []): void
{
}
public function warning(string $message, array $context = []): void
{
}
public function error(string $message, array $context = []): void
{
}
}

View File

@ -1,69 +0,0 @@
<?php
namespace RssBridge\Tests\Actions;
use ActionInterface;
use PHPUnit\Framework\TestCase;
class ActionImplementationTest extends TestCase
{
private $class;
private $obj;
public function setUp(): void
{
\Configuration::loadConfiguration();
}
/**
* @dataProvider dataActionsProvider
*/
public function testClassName($path)
{
$this->setAction($path);
$this->assertTrue($this->class === ucfirst($this->class), 'class name must start with uppercase character');
$this->assertEquals(0, substr_count($this->class, ' '), 'class name must not contain spaces');
$this->assertStringEndsWith('Action', $this->class, 'class name must end with "Action"');
}
/**
* @dataProvider dataActionsProvider
*/
public function testClassType($path)
{
$this->setAction($path);
$this->assertInstanceOf(ActionInterface::class, $this->obj);
}
/**
* @dataProvider dataActionsProvider
*/
public function testVisibleMethods($path)
{
$allowedMethods = get_class_methods(ActionInterface::class);
sort($allowedMethods);
$this->setAction($path);
$methods = array_diff(get_class_methods($this->obj), ['__construct']);
sort($methods);
$this->assertEquals($allowedMethods, $methods);
}
public function dataActionsProvider()
{
$actions = [];
foreach (glob(PATH_LIB_ACTIONS . '*.php') as $path) {
$actions[basename($path, '.php')] = [$path];
}
return $actions;
}
private function setAction($path)
{
$this->class = '\\' . basename($path, '.php');
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
$this->obj = new $this->class();
}
}

View File

@ -1,76 +0,0 @@
<?php
namespace RssBridge\Tests\Actions;
use BridgeFactory;
use PHPUnit\Framework\TestCase;
class ListActionTest extends TestCase
{
public function setUp(): void
{
\Configuration::loadConfiguration();
}
public function testHeaders()
{
$action = new \ListAction();
$response = $action->execute([]);
$headers = $response->getHeaders();
$contentType = $response->getHeader('content-type');
$this->assertSame($contentType, 'application/json');
}
public function testOutput()
{
$action = new \ListAction();
$response = $action->execute([]);
$data = $response->getBody();
$items = json_decode($data, true);
$this->assertNotNull($items, 'invalid JSON output: ' . json_last_error_msg());
$this->assertArrayHasKey('total', $items, 'Missing "total" parameter');
$this->assertIsInt($items['total'], 'Invalid type');
$this->assertArrayHasKey('bridges', $items, 'Missing "bridges" array');
$this->assertEquals(
$items['total'],
count($items['bridges']),
'Item count doesn\'t match'
);
$bridgeFactory = new BridgeFactory();
$this->assertEquals(
count($bridgeFactory->getBridgeClassNames()),
count($items['bridges']),
'Number of bridges doesn\'t match'
);
$expectedKeys = [
'status',
'uri',
'name',
'icon',
'parameters',
'maintainer',
'description'
];
$allowedStatus = [
'active',
'inactive'
];
foreach ($items['bridges'] as $bridge) {
foreach ($expectedKeys as $key) {
$this->assertArrayHasKey($key, $bridge, 'Missing key "' . $key . '"');
}
$this->assertContains($bridge['status'], $allowedStatus, 'Invalid status value');
}
}
}

View File

@ -6,25 +6,14 @@ use PHPUnit\Framework\TestCase;
class BridgeFactoryTest extends TestCase
{
public function setUp(): void
{
\Configuration::loadConfiguration();
}
public function testNormalizeBridgeName()
{
$this->assertSame('TwitterBridge', \BridgeFactory::normalizeBridgeName('TwitterBridge'));
$this->assertSame('TwitterBridge', \BridgeFactory::normalizeBridgeName('TwitterBridge.php'));
$this->assertSame('TwitterBridge', \BridgeFactory::normalizeBridgeName('Twitter'));
}
public function testSanitizeBridgeName()
{
$sut = new \BridgeFactory();
$this->assertSame('TwitterBridge', $sut->createBridgeClassName('twitterbridge'));
$this->assertSame('TwitterBridge', $sut->createBridgeClassName('twitter'));
$this->assertSame('TwitterBridge', $sut->createBridgeClassName('tWitTer'));
$this->assertSame('TwitterBridge', $sut->createBridgeClassName('TWITTERBRIDGE'));
// $this->assertSame('TwitterBridge', $sut->createBridgeClassName('twitterbridge'));
// $this->assertSame('TwitterBridge', $sut->createBridgeClassName('twitter'));
// $this->assertSame('TwitterBridge', $sut->createBridgeClassName('tWitTer'));
// $this->assertSame('TwitterBridge', $sut->createBridgeClassName('TWITTERBRIDGE'));
}
}

View File

@ -231,7 +231,10 @@ class BridgeImplementationTest extends TestCase
{
$this->class = '\\' . basename($path, '.php');
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
$this->obj = new $this->class();
$this->obj = new $this->class(
new \NullCache(),
new \NullLogger()
);
}
private function checkUrl($url)

View File

@ -8,13 +8,13 @@ class CacheTest extends TestCase
{
public function testConfig()
{
$sut = new \FileCache(['path' => '/tmp/']);
$sut = new \FileCache(new \NullLogger(), ['path' => '/tmp/']);
$this->assertSame(['path' => '/tmp/', 'enable_purge' => true], $sut->getConfig());
$sut = new \FileCache(['path' => '/', 'enable_purge' => false]);
$sut = new \FileCache(new \NullLogger(), ['path' => '/', 'enable_purge' => false]);
$this->assertSame(['path' => '/', 'enable_purge' => false], $sut->getConfig());
$sut = new \FileCache(['path' => '/tmp', 'enable_purge' => true]);
$sut = new \FileCache(new \NullLogger(), ['path' => '/tmp', 'enable_purge' => true]);
$this->assertSame(['path' => '/tmp/', 'enable_purge' => true], $sut->getConfig());
}
@ -23,7 +23,7 @@ class CacheTest extends TestCase
$temporaryFolder = sprintf('%s/rss_bridge_%s/', sys_get_temp_dir(), create_random_string());
mkdir($temporaryFolder);
$sut = new \FileCache([
$sut = new \FileCache(new \NullLogger(), [
'path' => $temporaryFolder,
'enable_purge' => true,
]);