diff --git a/.README.md.swp b/.README.md.swp new file mode 100644 index 0000000..9817a7f Binary files /dev/null and b/.README.md.swp differ diff --git a/.gitignore b/.gitignore index 1d43027..1a2a590 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ logs/ *~ vendor +*.lock diff --git a/composer.json b/composer.json index 4b13e33..e869ab7 100644 --- a/composer.json +++ b/composer.json @@ -13,11 +13,11 @@ "repositories" : [ { "type" : "vcs", - "url" : "https://usrpath@bitbucket.org/usrpath/bsrconfig.git" + "url" : "https://usrpath@bitbucket.org/usrpath/bsrutils.git" } ], "require" : { - "bsr/config" : "v0.0.1" + "bsr/utils" : "^v1.0.1" }, "autoload": { "psr-4": {"BSR\\" : "src/"} diff --git a/composer.lock b/composer.lock index 371b55b..5a67012 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,46 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e0bdda87ed25bff2590ed52de3095589", - "content-hash": "c96a273bceb8c0c36a0832819e22ed63", - "packages": [], + "hash": "d9f96343b41310f062dbbd9a1175cf6a", + "content-hash": "cc63913f1b21e50ab5e9530dfb46d2b9", + "packages": [ + { + "name": "bsr/utils", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://usrpath@bitbucket.org/usrpath/bsrutils.git", + "reference": "3289696873675db9fc9fca35271d61df5e68adcd" + }, + "require-dev": { + "pds/skeleton": "^1.0", + "phpdocumentor/phpdocumentor": "2.*", + "phpunit/phpunit": "^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "BSR\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "BSR\\": "tests/" + } + }, + "authors": [ + { + "name": "Simon" + }, + { + "name": "Guillermo Pages", + "email": "g@lespagesweb.ch" + } + ], + "description": "Bsr configuration module", + "time": "2018-10-12 14:16:01" + } + ], "packages-dev": [ { "name": "doctrine/instantiator", diff --git a/config/configuration.local.php b/config/configuration.local.php index d72b9bd..fca09f2 100644 --- a/config/configuration.local.php +++ b/config/configuration.local.php @@ -24,8 +24,9 @@ return array( 'path' => 'solr/', 'result_count' => 10, ), + 'renderer' => array('class' => '\BSR\Webservice\MockRenderer'), 'log' => array( - 'file' => '/var/www/webservice/logs/log.txt', + 'file' => realpath(dirname(__FILE__) . '/..') . '/log/log.txt', 'format' => '%ip% - [%date%] - %status% %error% - %time% - %func%', // The greater the verbosity, the more is displayed // 0 : no log at all diff --git a/flow.txt b/flow.txt new file mode 100644 index 0000000..034ff3d --- /dev/null +++ b/flow.txt @@ -0,0 +1,68 @@ +index.php +web = new NetBiblio() + parent::__construct(self::$version) +web->Run() <=> parent->Run() +| Logger::start(version) // initialize static log array +| new Renderer::__construct() +| |---ob_start() +| $data = [] +| try { +| $result = this->Call() //1) start session, +| | //2) call webservice method and params passed in +| | // http request after doing a few checks +| | //3) log the request +| | session_save_path(Configuration::get(session.save_path)) +| | session_start() +| | $paras = GET or POST +| | if empty(params) throw NoArguments +| | if !isset(params["func"]) throw MissingMethod +| | this->func = params["func"] +| | unset(params["func"]) +| | Logger::info([ +| | "func" => this->func . '(' . implode(',' params) . ')' +| | ]) // add func -> string to Logger::data +| | if !is_callable([this, this->func]) throw BadMethod +| | // descriptive wrapper for NetBiblio method +| | $rm = new \ReflectionMethod(this, this->func) +| | // check whether provided params match required +| | //TODO here is where the magic happens !!!!!!!!!!!!!!!!!!!! +| |---return call_user_func_array([this, this->func], params) +| $data['result'][this->func] = $result +| } catch (WebException $e) { +| $data['error'] = ['code' => $e->getCode(), ... ] +| $this->status = 400 +| Logger::info($e->getName(), 'error') // add to log +| } catch (\Exception $e) { +| $data['failure'] = ['code' => $e->getCode(), 'reason' ...] +| Logger::info($e->getMessage, 'error') +| } +| Logger::stop(['status' => $this->status]) +| $renderer->render($this->status, $data) // default status:200 +| | header(sprintf( +| | 'HTTP/1.0 %s %s', +| | $status, self::$statusMessages[$status] +| | )) +| | header('Access-Control-Allow-Origin: *') +| | ob_clean() +| | flush() +| | $formatter = Formatter::getFormatter(); +| | | self::loadFormatters() // call init on all formatters in dir +| | | | foreach($files as f) { +| | | | //infer class name from file name +| | | | //and if it is not this current class +| | | | //call init on the class +| | | | //Ex: +| | | | Json::init() +| | | | | self::registerFormats([ //self parent +| | | | | 'application/json', +| | | | | 'application/x-json', +| | | | | ]); +| | | | | | forach($formats as $f) { // self +| | | | | | self::$formats[$f] = get_called_class() +| | | | |---|---} +| | | | } +| | | $format = self::getFormatFromHeader(); +| | | |---return 'BSR\Lib\Formatter\Json'; +| | |---return new $format() +| | $formatter->render($data) +|---|---|---echo json_encode($data) diff --git a/src/Webservice/.WebService.php.swp b/src/Webservice/.WebService.php.swp new file mode 100644 index 0000000..4a0509c Binary files /dev/null and b/src/Webservice/.WebService.php.swp differ diff --git a/src/Webservice/Exception/.UsageException.php.swp b/src/Webservice/Exception/.UsageException.php.swp new file mode 100644 index 0000000..2da13ae Binary files /dev/null and b/src/Webservice/Exception/.UsageException.php.swp differ diff --git a/src/Webservice/Formatter/Formatter.php b/src/Webservice/Formatter/Formatter.php index f9895db..fea4fa8 100644 --- a/src/Webservice/Formatter/Formatter.php +++ b/src/Webservice/Formatter/Formatter.php @@ -1,5 +1,4 @@ class) */ - protected static function registerFormats(array $formats) { + protected static function registerFormats(array $formats) + { foreach($formats as $f) { self::$formats[$f] = get_called_class(); } } + /** + * See loadFormatters() init() + * @return array of registered formats by subclasses on init() + */ + public static function getRegisterdFormats() + { + return self::$formats; + } + /** * @return Formatter The formatter to use for this request */ - public static function getFormatter() { + public static function getFormatter() + { self::loadFormatters(); $format = self::getFormatFromHeader(); @@ -66,7 +76,6 @@ abstract class Formatter { } } - return 'BSR\Webservice\Formatter\Json'; } diff --git a/src/Webservice/Formatter/Html.php b/src/Webservice/Formatter/Html.php index 9be2a4e..c13fb47 100644 --- a/src/Webservice/Formatter/Html.php +++ b/src/Webservice/Formatter/Html.php @@ -1,5 +1,4 @@ 'info', ); } - $info = Logger::data(); + $info = Logger::getData(); $context['time'] = $info['time']; $panel = static::template($context, 'panel'); diff --git a/src/Webservice/Help.php b/src/Webservice/Help.php index c3e09d4..6096a3e 100644 --- a/src/Webservice/Help.php +++ b/src/Webservice/Help.php @@ -1,5 +1,4 @@ 'Ok', 400 => 'Bad request', @@ -13,13 +13,19 @@ class Renderer { 500 => 'Server Error', ); - public function __construct() { + public function __construct() + { ob_start(); } - public function render($status, $data) { + /** + * + */ + public function render($status, $data) + { header(sprintf('HTTP/1.0 %s %s', $status, self::$statusMessages[$status])); header("Access-Control-Allow-Origin: *"); + ob_clean(); flush(); diff --git a/src/Webservice/WebService.php b/src/Webservice/WebService.php index 02482da..5798dd5 100644 --- a/src/Webservice/WebService.php +++ b/src/Webservice/WebService.php @@ -21,17 +21,19 @@ abstract class WebService /** * Treat the current request and output the result. This is the only * method that should be called on the webservice directly ! + * @param bool $sendSession needed for testing */ - public function run() + public function run($sendSession = true) { Logger::start(array('version' => $this->version)); - $renderer = new Renderer(); + $rendererClass = Configuration::get('renderer.class', __NAMESPACE__ . '\Renderer'); + $renderer = new $rendererClass; $data = array(); try { - $result = $this->call(); + $result = $this->call($sendSession); $data["result"][$this->func] = $result; // Logger::log(print_r($result, true)); @@ -61,13 +63,16 @@ abstract class WebService * Determines which method to call based on GET or POST parameters and * call it before returning the result. * + * @param bool $sendSession used for testing * @return array * @throws UsageException */ - private function call() + private function call($sendSession = true) { - session_save_path(Configuration::get('session.save_path')); - session_start(); + if ($sendSession) { + session_save_path(Configuration::get('session.save_path')); + session_start(); + } $params = empty($_GET) ? $_POST : $_GET; if (empty($params)) { diff --git a/templates/func_help.html b/templates/func_help.html new file mode 100644 index 0000000..e6e1925 --- /dev/null +++ b/templates/func_help.html @@ -0,0 +1,14 @@ +

{{ func }}

+ +

Parameters

+
+ {{ parameters }} +
+ +

Return

+{{ return }} + +

Description

+{{ help }} + + diff --git a/templates/layout.html b/templates/layout.html new file mode 100644 index 0000000..2f00480 --- /dev/null +++ b/templates/layout.html @@ -0,0 +1,40 @@ + + + + + + BSR WebService - {{ title }} + + + + + + +
+ {{ content }} +
+ + + + + \ No newline at end of file diff --git a/templates/panel.html b/templates/panel.html new file mode 100644 index 0000000..207c655 --- /dev/null +++ b/templates/panel.html @@ -0,0 +1,11 @@ +
+
+

{{ title }}

+
+
+ {{ content }} +
+ +
\ No newline at end of file diff --git a/templates/param_help.html b/templates/param_help.html new file mode 100644 index 0000000..df14e71 --- /dev/null +++ b/templates/param_help.html @@ -0,0 +1,5 @@ +
{{ name }} {{ optional }}
+
+ {{ type }} + {{ doc }} +
\ No newline at end of file diff --git a/templates/return_help.html b/templates/return_help.html new file mode 100644 index 0000000..8f361cf --- /dev/null +++ b/templates/return_help.html @@ -0,0 +1,2 @@ +{{ type }} +{{ doc }} diff --git a/tests/Webservice/.MockWebserviceSubclass.php.swp b/tests/Webservice/.MockWebserviceSubclass.php.swp new file mode 100644 index 0000000..4245311 Binary files /dev/null and b/tests/Webservice/.MockWebserviceSubclass.php.swp differ diff --git a/tests/Webservice/.WebserviceTest.php.swp b/tests/Webservice/.WebserviceTest.php.swp new file mode 100644 index 0000000..5a8eca6 Binary files /dev/null and b/tests/Webservice/.WebserviceTest.php.swp differ diff --git a/tests/Webservice/Formatter/FormatterTest.php b/tests/Webservice/Formatter/FormatterTest.php index f9895db..dbe15fc 100644 --- a/tests/Webservice/Formatter/FormatterTest.php +++ b/tests/Webservice/Formatter/FormatterTest.php @@ -1,78 +1,13 @@ class) - */ - protected static function registerFormats(array $formats) { - foreach($formats as $f) { - self::$formats[$f] = get_called_class(); - } - } - - /** - * @return Formatter The formatter to use for this request - */ - public static function getFormatter() { - self::loadFormatters(); - $format = self::getFormatFromHeader(); - - return new $format(); - } - - /** - * Load all formatters in the current directory - */ - private static function loadFormatters() { - preg_match('/(.+)\\\([a-zA-Z0-9]+)/', get_called_class(), $parts); - $us = $parts[2]; - $namespace = $parts[1]; +use PHPUnit\Framework\TestCase; - $base = __DIR__.'/'; - $ext = '.php'; - $files = glob(sprintf('%s%s%s', $base, '*', $ext)); - foreach($files as $f) { - $c = str_replace(array($base, $ext), '', $f); - if($c !== $us) { - $c = $namespace.'\\'.$c; - call_user_func(array($c, 'init')); - } - } +class FormatterTest extends TestCase +{ + public function testFormatterAllwaysReturnsAJsonFormatter() + { + $formatter = Formatter::getFormatter(); + $this->assertEquals($formatter instanceof Json, true); } - - /** - * @return string The class name to instantiate in accord to the Accept header - */ - private static function getFormatFromHeader() { - //TODO this is ugly - return 'BSR\Webservice\Formatter\Json'; - if(isset($_SERVER['HTTP_ACCEPT'])) { - $formats = array_map(function($f) { - $parts = explode(';', $f); - $parts[1] = (isset($parts[1]) ? (float) preg_replace('/[^0-9\.]/', '', $parts[1]) : 1.0) * 100; - return $parts; - }, explode(',', $_SERVER['HTTP_ACCEPT'])); - - usort($formats, function($a, $b) { return $b[1] - $a[1]; }); - - foreach($formats as $f) { - if(isset(self::$formats[$f[0]])) { - return self::$formats[$f[0]]; - } - } - - } - - return 'BSR\Webservice\Formatter\Json'; - } - - /** - * Output the content for the given data - * @param array $data - */ - abstract public function render($data); } diff --git a/tests/Webservice/Formatter/JsonTest.php b/tests/Webservice/Formatter/JsonTest.php index 45d46a8..eda054c 100644 --- a/tests/Webservice/Formatter/JsonTest.php +++ b/tests/Webservice/Formatter/JsonTest.php @@ -1,17 +1,34 @@ array(1, 'a', 'b', array()), + '1' => array(3 => 'c'), + 0 => 'hello', + ); + ob_start(); + $format->render($someArray); + $jsonRepresentation = ob_get_clean(); + $this->assertSame(json_decode($jsonRepresentation, true), $someArray); } - public function render($data) { - echo json_encode($data); + public function testRegistersItsFormatAcceptHeader() + { + Formatter::getFormatter(); + $formats = Formatter::getRegisterdFormats(); + $this->assertSame(array_intersect($this->accept, array_keys($formats)), $this->accept); } } diff --git a/tests/Webservice/MockRenderer.php b/tests/Webservice/MockRenderer.php new file mode 100644 index 0000000..a284274 --- /dev/null +++ b/tests/Webservice/MockRenderer.php @@ -0,0 +1,16 @@ +render($data); + } +} diff --git a/tests/Webservice/MockWebserviceSubclass.php b/tests/Webservice/MockWebserviceSubclass.php new file mode 100644 index 0000000..06fde41 --- /dev/null +++ b/tests/Webservice/MockWebserviceSubclass.php @@ -0,0 +1,10 @@ + 'Ok', 400 => 'Bad request', 404 => 'Not Found', @@ -13,17 +15,15 @@ class Renderer { 500 => 'Server Error', ); - public function __construct() { + public function testThatOutputBufferGetsInializedOnConstruction() + { + $text = 'This text should be captured by the renderers ob_start()'; ob_start(); - } - - public function render($status, $data) { - header(sprintf('HTTP/1.0 %s %s', $status, self::$statusMessages[$status])); - header("Access-Control-Allow-Origin: *"); - ob_clean(); - flush(); - - $formatter = Formatter::getFormatter(); - $formatter->render($data); + echo 'Rubish text, that is not intended to be outputed'; + ob_get_clean(); + $renderer = new Renderer(); + echo $text; + $obcontent = ob_get_clean(); + $this->assertSame($text, $obcontent); } } diff --git a/tests/Webservice/WebserviceTest.php b/tests/Webservice/WebserviceTest.php index 4035af5..464334e 100644 --- a/tests/Webservice/WebserviceTest.php +++ b/tests/Webservice/WebserviceTest.php @@ -2,12 +2,131 @@ namespace BSR\Webservice; use PHPUnit\Framework\TestCase; +use BSR\Utils\Logger\Logger; +use BSR\Utils\Configuration\Configuration; class WebserviceTest extends TestCase { + private $webservice; + public function setUp() { + $this->webservice = new MockWebserviceSubclass('1.2.3'); + } + + private function removeLogs() + { + $logsDir = realpath(dirname(Configuration::get('log.file'))); + $files = glob("$logsDir/*"); + foreach($files as $file){ + if(is_file($file)) unlink($file); + } } - public function test + + /** + * @see config/configuration.local.php for the mock renderer used + * @runInSeparateProcess + */ + public function testRunSavesTheRequestIntoALogFile() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + $this->webservice->run(false); + $response = ob_get_clean(); + $log = Logger::getLastLogs(); + $this->assertNotEquals(Logger::NO_LOG_YET_MSG, $log); + } + + /** + * @see config/configuration.local.php for the mock renderer used + * @runInSeparateProcess + */ + public function testReturnsErrorNoArgumentsWhenNoParamtersInGetPostRequest() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + + $_GET = array(); $_POST = array(); + + $this->webservice->run(false); + $response = ob_get_clean(); + $responseArray = json_decode($response, true); + $this->assertEquals(100, $responseArray['error']['code']); + $this->assertEquals('NoArguments', $responseArray['error']['name']); + } + + public function testDoesNotReturnErrorNoArgumentsWhenParamtersInGetRequest() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + + $_GET = array('a' => 'a1'); + + $this->webservice->run(false); + $response = ob_get_clean(); + $responseArray = json_decode($response, true); + $this->assertNotEquals(100, $responseArray['error']['code']); + $this->assertNotEquals('NoArguments', $responseArray['error']['name']); + } + + public function testDoesNotReturnErrorNoArgumentsWhenParamtersInPostRequest() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + + $_POST = array('a' => 'a1'); + + $this->webservice->run(false); + $response = ob_get_clean(); + $responseArray = json_decode($response, true); + var_dump($responseArray); + $this->assertNotEquals(100, $responseArray['error']['code']); + $this->assertNotEquals('NoArguments', $responseArray['error']['name']); + } + + public function testReturnsErrorMissingMethodWhenNoFuncParamInRequest() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + + $_POST = array('a' => 'a1'); + + $this->webservice->run(false); + $response = ob_get_clean(); + $responseArray = json_decode($response, true); + $this->assertEquals(101, $responseArray['error']['code']); + $this->assertEquals('MissingMethod', $responseArray['error']['name']); + } + + public function testDoesNotReturnErrorMissingMethodWhenFuncParamInRequest() + { + $this->removeLogs(); + $log = Logger::getLastLogs(); + $this->assertEquals(Logger::NO_LOG_YET_MSG, $log); + + $_GET = array('func' => 'someMockFunction'); $_POST = array(); + + $this->webservice->run(false); + $response = ob_get_clean(); + $responseArray = json_decode($response, true); + $this->assertEquals(100, $responseArray['error']['code']); + } + + public function testRunThrowsAUsageExceptionWhenFuncParamIsNotCallableOnSubclass() + { + } + + public function testRunThrowsAUsageExceptionWhenMissingRequiredParamsInRequest() + { + } + + public function testRunThrowsAUsageExceptionWhenTooManyParamsInRequest() + { + } }