* test: refactor test suite

* docs

* refactor

* yup

* docs
This commit is contained in:
Dag 2023-10-01 19:23:30 +02:00 committed by GitHub
parent 0c92cf32d4
commit 41df17bc46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 309 additions and 566 deletions

View File

@ -248,6 +248,8 @@ Modify `report_limit` so that an error must occur 3 times before it is reported.
; Defines how often an error must occur before it is reported to the user
report_limit = 3
The report count is reset to 0 each day.
### How to password-protect the instance
HTTP basic access authentication:

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* Checks if the website for a given bridge is reachable.
*

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DetectAction implements ActionInterface
{
public function execute(array $request)

View File

@ -101,8 +101,8 @@ class DisplayAction implements ActionInterface
try {
$bridge->loadConfiguration();
// Remove parameters that don't concern bridges
$bridgeData = array_diff_key($request, array_fill_keys(['action', 'bridge', 'format', '_noproxy', '_cache_timeout', '_error_time'], ''));
$bridge->setDatas($bridgeData);
$input = array_diff_key($request, array_fill_keys(['action', 'bridge', 'format', '_noproxy', '_cache_timeout', '_error_time'], ''));
$bridge->setInput($input);
$bridge->collectData();
$items = $bridge->getItems();
if (isset($items[0]) && is_array($items[0])) {

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class ListAction implements ActionInterface
{
public function execute(array $request)
@ -26,14 +14,14 @@ class ListAction implements ActionInterface
$bridge = $bridgeFactory->create($bridgeClassName);
$list->bridges[$bridgeClassName] = [
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
];
}
$list->total = count($list->bridges);

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class SetBridgeCacheAction implements ActionInterface
{
private CacheInterface $cache;

View File

@ -4,7 +4,7 @@ class DemoBridge extends BridgeAbstract
{
const MAINTAINER = 'teromene';
const NAME = 'DemoBridge';
const URI = 'http://github.com/rss-bridge/rss-bridge';
const URI = 'https://github.com/rss-bridge/rss-bridge';
const DESCRIPTION = 'Bridge used for demos';
const CACHE_TIMEOUT = 15;

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
final class ApiAuthenticationMiddleware
{
public function __invoke($request): void

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
final class AuthenticationMiddleware
{
public function __construct()

View File

@ -90,17 +90,70 @@ abstract class BridgeAbstract
return static::CACHE_TIMEOUT;
}
/**
* Sets the input values for a given context.
*
* @param array $inputs Associative array of inputs
* @param string $queriedContext The context name
* @return void
*/
protected function setInputs(array $inputs, $queriedContext)
public function loadConfiguration()
{
foreach (static::CONFIGURATION as $optionName => $optionValue) {
$section = $this->getShortName();
$configurationOption = Configuration::getConfig($section, $optionName);
if ($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
if (isset($optionValue['required']) && $optionValue['required'] === true) {
throw new \Exception(sprintf('Missing configuration option: %s', $optionName));
} elseif (isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
}
}
public function setInput(array $input)
{
$context = $input['context'] ?? null;
if ($context) {
// Context hinting (optional)
$this->queriedContext = $context;
unset($input['context']);
}
$parameters = $this->getParameters();
if (!$parameters) {
if ($input) {
throw new \Exception('Invalid parameters value(s)');
}
return;
}
$validator = new ParameterValidator();
// $input is passed by reference!
if (!$validator->validateInput($input, $parameters)) {
$invalidParameterKeys = array_column($validator->getInvalidParameters(), 'name');
throw new \Exception(sprintf('Invalid parameters value(s): %s', implode(', ', $invalidParameterKeys)));
}
// Guess the context from input data
if (empty($this->queriedContext)) {
$queriedContext = $validator->getQueriedContext($input, $parameters);
$this->queriedContext = $queriedContext;
}
if (is_null($this->queriedContext)) {
throw new \Exception('Required parameter(s) missing');
} elseif ($this->queriedContext === false) {
throw new \Exception('Mixed context parameters');
}
$this->setInputWithContext($input, $this->queriedContext);
}
private function setInputWithContext(array $input, $queriedContext)
{
// Import and assign all inputs to their context
foreach ($inputs as $name => $value) {
foreach ($input as $name => $value) {
foreach (static::PARAMETERS as $context => $set) {
if (array_key_exists($name, static::PARAMETERS[$context])) {
$this->inputs[$context][$name]['value'] = $value;
@ -128,7 +181,7 @@ abstract class BridgeAbstract
switch ($type) {
case 'checkbox':
$this->inputs[$context][$name]['value'] = $inputs[$context][$name]['value'] ?? false;
$this->inputs[$context][$name]['value'] = $input[$context][$name]['value'] ?? false;
break;
case 'list':
if (!isset($properties['defaultValue'])) {
@ -153,8 +206,8 @@ abstract class BridgeAbstract
// Copy global parameter values to the guessed context
if (array_key_exists('global', static::PARAMETERS)) {
foreach (static::PARAMETERS['global'] as $name => $properties) {
if (isset($inputs[$name])) {
$value = $inputs[$name];
if (isset($input[$name])) {
$value = $input[$name];
} else {
if ($properties['type'] ?? null === 'checkbox') {
$value = false;
@ -176,91 +229,6 @@ abstract class BridgeAbstract
}
}
/**
* Set inputs for the bridge
*
* Returns errors and aborts execution if the provided input parameters are
* invalid.
*
* @param array List of input parameters. Each element in this list must
* relate to an item in {@see BridgeAbstract::PARAMETERS}
* @return void
*/
public function setDatas(array $inputs)
{
if (isset($inputs['context'])) { // Context hinting (optional)
$this->queriedContext = $inputs['context'];
unset($inputs['context']);
}
if (empty(static::PARAMETERS)) {
if (!empty($inputs)) {
throw new \Exception('Invalid parameters value(s)');
}
return;
}
$validator = new ParameterValidator();
if (!$validator->validateData($inputs, static::PARAMETERS)) {
$parameters = array_map(
function ($i) {
return $i['name'];
}, // Just display parameter names
$validator->getInvalidParameters()
);
throw new \Exception(sprintf('Invalid parameters value(s): %s', implode(', ', $parameters)));
}
// Guess the context from input data
if (empty($this->queriedContext)) {
$this->queriedContext = $validator->getQueriedContext($inputs, static::PARAMETERS);
}
if (is_null($this->queriedContext)) {
throw new \Exception('Required parameter(s) missing');
} elseif ($this->queriedContext === false) {
throw new \Exception('Mixed context parameters');
}
$this->setInputs($inputs, $this->queriedContext);
}
/**
* Loads configuration for the bridge
*
* Returns errors and aborts execution if the provided configuration is
* invalid.
*
* @return void
*/
public function loadConfiguration()
{
foreach (static::CONFIGURATION as $optionName => $optionValue) {
$section = $this->getShortName();
$configurationOption = Configuration::getConfig($section, $optionName);
if ($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
if (isset($optionValue['required']) && $optionValue['required'] === true) {
throw new \Exception(sprintf('Missing configuration option: %s', $optionName));
} elseif (isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
}
}
/**
* Returns the value for the provided input
*
* @param string $input The input name
* @return mixed|null The input value or null if the input is not defined
*/
protected function getInput($input)
{
return $this->inputs[$this->queriedContext][$input]['value'] ?? null;

View File

@ -1,25 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* A generator class for a single bridge card on the home page of RSS-Bridge.
*
* This class generates the HTML content for a single bridge card for the home
* page of RSS-Bridge.
*
* @todo Return error if a caller creates an object of this class.
*/
final class BridgeCard
{
/**

View File

@ -4,9 +4,9 @@ final class BridgeFactory
{
private CacheInterface $cache;
private Logger $logger;
private $bridgeClassNames = [];
private $enabledBridges = [];
private $missingEnabledBridges = [];
private array $bridgeClassNames = [];
private array $enabledBridges = [];
private array $missingEnabledBridges = [];
public function __construct()
{
@ -22,7 +22,7 @@ final class BridgeFactory
$enabledBridges = Configuration::getConfig('system', 'enabled_bridges');
if ($enabledBridges === null) {
throw new \Exception('No bridges are enabled... wtf?');
throw new \Exception('No bridges are enabled...');
}
foreach ($enabledBridges as $enabledBridge) {
if ($enabledBridge === '*') {

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* Configuration module for RSS-Bridge.
*

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* An abstract class for bridges that need to transform existing RSS or Atom
* feeds.

View File

@ -1,17 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class FormatFactory
{
private $folder;

View File

@ -1,154 +1,21 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
* Validator for bridge parameters
*/
class ParameterValidator
{
/**
* Holds the list of invalid parameters
*
* @var array
*/
private $invalid = [];
private array $invalid = [];
/**
* Add item to list of invalid parameters
* Check that inputs are actually present in the bridge parameters.
*
* @param string $name The name of the parameter
* @param string $reason The reason for that parameter being invalid
* @return void
* Also check whether input values are allowed.
*/
private function addInvalidParameter($name, $reason)
public function validateInput(&$input, $parameters): bool
{
$this->invalid[] = [
'name' => $name,
'reason' => $reason,
];
}
/**
* Return list of invalid parameters.
*
* Each element is an array of 'name' and 'reason'.
*
* @return array List of invalid parameters
*/
public function getInvalidParameters()
{
return $this->invalid;
}
/**
* Validate value for a text input
*
* @param string $value The value of a text input
* @param string|null $pattern (optional) A regex pattern
* @return string|null The filtered value or null if the value is invalid
*/
private function validateTextValue($value, $pattern = null)
{
if (!is_null($pattern)) {
$filteredValue = filter_var(
$value,
FILTER_VALIDATE_REGEXP,
['options' => [
'regexp' => '/^' . $pattern . '$/'
]
]
);
} else {
$filteredValue = filter_var($value);
}
if ($filteredValue === false) {
return null;
}
return $filteredValue;
}
/**
* Validate value for a number input
*
* @param int $value The value of a number input
* @return int|null The filtered value or null if the value is invalid
*/
private function validateNumberValue($value)
{
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
if ($filteredValue === false) {
return null;
}
return $filteredValue;
}
/**
* Validate value for a checkbox
*
* @param bool $value The value of a checkbox
* @return bool The filtered value
*/
private function validateCheckboxValue($value)
{
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
/**
* Validate value for a list
*
* @param string $value The value of a list
* @param array $expectedValues A list of expected values
* @return string|null The filtered value or null if the value is invalid
*/
private function validateListValue($value, $expectedValues)
{
$filteredValue = filter_var($value);
if ($filteredValue === false) {
return null;
}
if (!in_array($filteredValue, $expectedValues)) { // Check sub-values?
foreach ($expectedValues as $subName => $subValue) {
if (is_array($subValue) && in_array($filteredValue, $subValue)) {
return $filteredValue;
}
}
return null;
}
return $filteredValue;
}
/**
* Check if all required parameters are satisfied
*
* @param array $data (ref) A list of input values
* @param array $parameters The bridge parameters
* @return bool True if all parameters are satisfied
*/
public function validateData(&$data, $parameters)
{
if (!is_array($data)) {
if (!is_array($input)) {
return false;
}
foreach ($data as $name => $value) {
foreach ($input as $name => $value) {
// Some RSS readers add a cache-busting parameter (_=<timestamp>) to feed URLs, detect and ignore them.
if ($name === '_') {
continue;
@ -156,54 +23,60 @@ class ParameterValidator
$registered = false;
foreach ($parameters as $context => $set) {
if (array_key_exists($name, $set)) {
$registered = true;
if (!isset($set[$name]['type'])) {
$set[$name]['type'] = 'text';
}
if (!array_key_exists($name, $set)) {
continue;
}
$registered = true;
if (!isset($set[$name]['type'])) {
// Default type is text
$set[$name]['type'] = 'text';
}
switch ($set[$name]['type']) {
case 'number':
$data[$name] = $this->validateNumberValue($value);
break;
case 'checkbox':
$data[$name] = $this->validateCheckboxValue($value);
break;
case 'list':
$data[$name] = $this->validateListValue($value, $set[$name]['values']);
break;
default:
case 'text':
if (isset($set[$name]['pattern'])) {
$data[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
} else {
$data[$name] = $this->validateTextValue($value);
}
break;
}
switch ($set[$name]['type']) {
case 'number':
$input[$name] = $this->validateNumberValue($value);
break;
case 'checkbox':
$input[$name] = $this->validateCheckboxValue($value);
break;
case 'list':
$input[$name] = $this->validateListValue($value, $set[$name]['values']);
break;
default:
case 'text':
if (isset($set[$name]['pattern'])) {
$input[$name] = $this->validateTextValue($value, $set[$name]['pattern']);
} else {
$input[$name] = $this->validateTextValue($value);
}
break;
}
if (is_null($data[$name]) && isset($set[$name]['required']) && $set[$name]['required']) {
$this->addInvalidParameter($name, 'Parameter is invalid!');
}
if (
is_null($input[$name])
&& isset($set[$name]['required'])
&& $set[$name]['required']
) {
$this->invalid[] = ['name' => $name, 'reason' => 'Parameter is invalid!'];
}
}
if (!$registered) {
$this->addInvalidParameter($name, 'Parameter is not registered!');
$this->invalid[] = ['name' => $name, 'reason' => 'Parameter is not registered!'];
}
}
return empty($this->invalid);
return $this->invalid === [];
}
/**
* Get the name of the context matching the provided inputs
*
* @param array $data Associative array of user data
* @param array $input Associative array of user data
* @param array $parameters Array of bridge parameters
* @return string|null Returns the context name or null if no match was found
*/
public function getQueriedContext($data, $parameters)
public function getQueriedContext($input, $parameters)
{
$queriedContexts = [];
@ -212,7 +85,7 @@ class ParameterValidator
$queriedContexts[$context] = null;
// Ensure all user data exist in the current context
$notInContext = array_diff_key($data, $set);
$notInContext = array_diff_key($input, $set);
if (array_key_exists('global', $parameters)) {
$notInContext = array_diff_key($notInContext, $parameters['global']);
}
@ -222,7 +95,7 @@ class ParameterValidator
// Check if all parameters of the context are satisfied
foreach ($set as $id => $properties) {
if (isset($data[$id]) && !empty($data[$id])) {
if (isset($input[$id]) && !empty($input[$id])) {
$queriedContexts[$context] = true;
} elseif (
isset($properties['type'])
@ -248,8 +121,8 @@ class ParameterValidator
switch (array_sum($queriedContexts)) {
case 0:
// Found no match, is there a context without parameters?
if (isset($data['context'])) {
return $data['context'];
if (isset($input['context'])) {
return $input['context'];
}
foreach ($queriedContexts as $context => $queried) {
if (is_null($queried)) {
@ -264,4 +137,55 @@ class ParameterValidator
return false;
}
}
public function getInvalidParameters(): array
{
return $this->invalid;
}
private function validateTextValue($value, $pattern = null)
{
if (is_null($pattern)) {
// No filtering taking place
$filteredValue = filter_var($value);
} else {
$filteredValue = filter_var($value, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '/^' . $pattern . '$/']]);
}
if ($filteredValue === false) {
return null;
}
return $filteredValue;
}
private function validateNumberValue($value)
{
$filteredValue = filter_var($value, FILTER_VALIDATE_INT);
if ($filteredValue === false) {
return null;
}
return $filteredValue;
}
private function validateCheckboxValue($value)
{
return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
}
private function validateListValue($value, $expectedValues)
{
$filteredValue = filter_var($value);
if ($filteredValue === false) {
return null;
}
if (!in_array($filteredValue, $expectedValues)) {
// Check sub-values?
foreach ($expectedValues as $subName => $subValue) {
if (is_array($subValue) && in_array($filteredValue, $subValue)) {
return $filteredValue;
}
}
return null;
}
return $filteredValue;
}
}

View File

@ -1,18 +1,6 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/** Path to the formats library */
// Path to the formats library
const PATH_LIB_FORMATS = __DIR__ . '/../formats/';
/** Path to the caches library */

View File

@ -211,12 +211,12 @@ final class Response
}
}
public function getBody()
public function getBody(): string
{
return $this->body;
}
public function getCode()
public function getCode(): int
{
return $this->code;
}
@ -226,7 +226,7 @@ final class Response
return self::STATUS_CODES[$this->code] ?? '';
}
public function getHeaders()
public function getHeaders(): array
{
return $this->headers;
}

View File

@ -1,39 +1,5 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
// based on https://github.com/laravel/framework/blob/8.x/src/Illuminate/Support/Str.php
//
// Copyright (c) Taylor Otwell
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
if (!function_exists('str_starts_with')) {
function str_starts_with($haystack, $needle)
{

View File

@ -1,6 +1,6 @@
<?php
namespace RssBridge\Tests\Bridges;
namespace RssBridge\Tests;
use BridgeAbstract;
use FeedExpander;
@ -8,8 +8,8 @@ use PHPUnit\Framework\TestCase;
class BridgeImplementationTest extends TestCase
{
private $class;
private $obj;
private string $className;
private BridgeAbstract $bridge;
/**
* @dataProvider dataBridgesProvider
@ -17,9 +17,9 @@ class BridgeImplementationTest extends TestCase
public function testClassName($path)
{
$this->setBridge($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('Bridge', $this->class, 'class name must end with "Bridge"');
$this->assertTrue($this->className === ucfirst($this->className), 'class name must start with uppercase character');
$this->assertEquals(0, substr_count($this->className, ' '), 'class name must not contain spaces');
$this->assertStringEndsWith('Bridge', $this->className, 'class name must end with "Bridge"');
}
/**
@ -28,7 +28,7 @@ class BridgeImplementationTest extends TestCase
public function testClassType($path)
{
$this->setBridge($path);
$this->assertInstanceOf(BridgeAbstract::class, $this->obj);
$this->assertInstanceOf(BridgeAbstract::class, $this->bridge);
}
/**
@ -38,18 +38,18 @@ class BridgeImplementationTest extends TestCase
{
$this->setBridge($path);
$this->assertIsString($this->obj::NAME, 'class::NAME');
$this->assertNotEmpty($this->obj::NAME, 'class::NAME');
$this->assertIsString($this->obj::URI, 'class::URI');
$this->assertNotEmpty($this->obj::URI, 'class::URI');
$this->assertIsString($this->obj::DESCRIPTION, 'class::DESCRIPTION');
$this->assertNotEmpty($this->obj::DESCRIPTION, 'class::DESCRIPTION');
$this->assertIsString($this->obj::MAINTAINER, 'class::MAINTAINER');
$this->assertNotEmpty($this->obj::MAINTAINER, 'class::MAINTAINER');
$this->assertIsString($this->bridge::NAME, 'class::NAME');
$this->assertNotEmpty($this->bridge::NAME, 'class::NAME');
$this->assertIsString($this->bridge::URI, 'class::URI');
$this->assertNotEmpty($this->bridge::URI, 'class::URI');
$this->assertIsString($this->bridge::DESCRIPTION, 'class::DESCRIPTION');
$this->assertNotEmpty($this->bridge::DESCRIPTION, 'class::DESCRIPTION');
$this->assertIsString($this->bridge::MAINTAINER, 'class::MAINTAINER');
$this->assertNotEmpty($this->bridge::MAINTAINER, 'class::MAINTAINER');
$this->assertIsArray($this->obj::PARAMETERS, 'class::PARAMETERS');
$this->assertIsInt($this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
$this->assertGreaterThanOrEqual(0, $this->obj::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
$this->assertIsArray($this->bridge::PARAMETERS, 'class::PARAMETERS');
$this->assertIsInt($this->bridge::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
$this->assertGreaterThanOrEqual(0, $this->bridge::CACHE_TIMEOUT, 'class::CACHE_TIMEOUT');
}
/**
@ -60,23 +60,22 @@ class BridgeImplementationTest extends TestCase
$this->setBridge($path);
$multiMinimum = 2;
if (isset($this->obj::PARAMETERS['global'])) {
if (isset($this->bridge::PARAMETERS['global'])) {
++$multiMinimum;
}
$multiContexts = (count($this->obj::PARAMETERS) >= $multiMinimum);
$multiContexts = (count($this->bridge::PARAMETERS) >= $multiMinimum);
$paramsSeen = [];
$allowedTypes = [
'text',
'number',
'list',
'checkbox'
'checkbox',
];
foreach ($this->obj::PARAMETERS as $context => $params) {
foreach ($this->bridge::PARAMETERS as $context => $params) {
if ($multiContexts) {
$this->assertIsString($context, 'invalid context name');
$this->assertNotEmpty($context, 'The context name cannot be empty');
}
@ -152,11 +151,10 @@ class BridgeImplementationTest extends TestCase
}
}
foreach ($this->obj::TEST_DETECT_PARAMETERS as $url => $params) {
$this->assertEquals($this->obj->detectParameters($url), $params);
foreach ($this->bridge::TEST_DETECT_PARAMETERS as $url => $params) {
$detectedParameters = $this->bridge->detectParameters($url);
$this->assertEquals($detectedParameters, $params);
}
$this->assertTrue(true);
}
/**
@ -164,19 +162,21 @@ class BridgeImplementationTest extends TestCase
*/
public function testVisibleMethods($path)
{
$allowedBridgeAbstract = get_class_methods(BridgeAbstract::class);
sort($allowedBridgeAbstract);
$allowedFeedExpander = get_class_methods(FeedExpander::class);
sort($allowedFeedExpander);
$bridgeAbstractMethods = get_class_methods(BridgeAbstract::class);
sort($bridgeAbstractMethods);
$feedExpanderMethods = get_class_methods(FeedExpander::class);
sort($feedExpanderMethods);
$this->setBridge($path);
$methods = get_class_methods($this->obj);
sort($methods);
if ($this->obj instanceof FeedExpander) {
$this->assertEquals($allowedFeedExpander, $methods);
} else {
$this->assertEquals($allowedBridgeAbstract, $methods);
$publicMethods = get_class_methods($this->bridge);
sort($publicMethods);
foreach ($publicMethods as $publicMethod) {
if ($this->bridge instanceof FeedExpander) {
$this->assertContains($publicMethod, $feedExpanderMethods);
} else {
$this->assertContains($publicMethod, $bridgeAbstractMethods);
}
}
}
@ -187,23 +187,23 @@ class BridgeImplementationTest extends TestCase
{
$this->setBridge($path);
$value = $this->obj->getDescription();
$value = $this->bridge->getDescription();
$this->assertIsString($value, '$class->getDescription()');
$this->assertNotEmpty($value, '$class->getDescription()');
$value = $this->obj->getMaintainer();
$value = $this->bridge->getMaintainer();
$this->assertIsString($value, '$class->getMaintainer()');
$this->assertNotEmpty($value, '$class->getMaintainer()');
$value = $this->obj->getName();
$value = $this->bridge->getName();
$this->assertIsString($value, '$class->getName()');
$this->assertNotEmpty($value, '$class->getName()');
$value = $this->obj->getURI();
$value = $this->bridge->getURI();
$this->assertIsString($value, '$class->getURI()');
$this->assertNotEmpty($value, '$class->getURI()');
$value = $this->obj->getIcon();
$value = $this->bridge->getIcon();
$this->assertIsString($value, '$class->getIcon()');
}
@ -214,14 +214,14 @@ class BridgeImplementationTest extends TestCase
{
$this->setBridge($path);
$this->checkUrl($this->obj::URI);
$this->checkUrl($this->obj->getURI());
$this->assertNotFalse(filter_var($this->bridge::URI, FILTER_VALIDATE_URL));
$this->assertNotFalse(filter_var($this->bridge->getURI(), FILTER_VALIDATE_URL));
}
public function dataBridgesProvider()
{
$bridges = [];
foreach (glob(__DIR__ . '/../../bridges/*Bridge.php') as $path) {
foreach (glob(__DIR__ . '/../bridges/*Bridge.php') as $path) {
$bridges[basename($path, '.php')] = [$path];
}
return $bridges;
@ -229,16 +229,11 @@ class BridgeImplementationTest extends TestCase
private function setBridge($path)
{
$this->class = '\\' . basename($path, '.php');
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
$this->obj = new $this->class(
$this->className = '\\' . basename($path, '.php');
$this->assertTrue(class_exists($this->className), 'class ' . $this->className . ' doesn\'t exist');
$this->bridge = new $this->className(
new \NullCache(),
new \NullLogger()
new \NullLogger(),
);
}
private function checkUrl($url)
{
$this->assertNotFalse(filter_var($url, FILTER_VALIDATE_URL), 'no valid URL: ' . $url);
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace RssBridge\Tests;
use CacheInterface;
use PHPUnit\Framework\TestCase;
class CacheImplementationTest extends TestCase
{
public function getCacheClassNames()
{
$caches = [];
foreach (glob(PATH_LIB_CACHES . '*.php') as $path) {
$caches[] = [basename($path, '.php')];
}
return $caches;
}
/**
* @dataProvider getCacheClassNames
*/
public function testClassName($path)
{
$this->assertTrue($path === ucfirst($path), 'class name must start with uppercase character');
$this->assertEquals(0, substr_count($path, ' '), 'class name must not contain spaces');
$this->assertStringEndsWith('Cache', $path, 'class name must end with "Cache"');
}
/**
* @dataProvider getCacheClassNames
*/
public function testClassType($path)
{
$this->assertTrue(is_subclass_of($path, CacheInterface::class), 'class must be subclass of CacheInterface');
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace RssBridge\Tests\Caches;
use CacheInterface;
use PHPUnit\Framework\TestCase;
class CacheImplementationTest extends TestCase
{
private $class;
/**
* @dataProvider dataCachesProvider
*/
public function testClassName($path)
{
$this->setCache($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('Cache', $this->class, 'class name must end with "Cache"');
}
/**
* @dataProvider dataCachesProvider
*/
public function testClassType($path)
{
$this->setCache($path);
$this->assertTrue(is_subclass_of($this->class, CacheInterface::class), 'class must be subclass of CacheInterface');
}
////////////////////////////////////////////////////////////////////////////
public function dataCachesProvider()
{
$caches = [];
foreach (glob(PATH_LIB_CACHES . '*.php') as $path) {
$caches[basename($path, '.php')] = [$path];
}
return $caches;
}
private function setCache($path)
{
$this->class = '\\' . basename($path, '.php');
$this->assertTrue(class_exists($this->class), 'class ' . $this->class . ' doesn\'t exist');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace RssBridge\Tests;
use PHPUnit\Framework\TestCase;
class ParameterValidatorTest extends TestCase
{
public function test1()
{
$sut = new \ParameterValidator();
$input = ['user' => 'joe'];
$parameters = [
[
'user' => [
'name' => 'User',
'type' => 'text',
],
]
];
$this->assertTrue($sut->validateInput($input, $parameters));
}
public function test2()
{
$sut = new \ParameterValidator();
$input = ['username' => 'joe'];
$parameters = [
[
'user' => [
'name' => 'User',
'type' => 'text',
],
]
];
$this->assertFalse($sut->validateInput($input, $parameters));
}
}