Commit eaffae28 authored by Henri-Damien LAURENT's avatar Henri-Damien LAURENT
Browse files

refactoring

parent ba7e79a4
Pipeline #10304 passed with stage
in 42 seconds
......@@ -4,7 +4,8 @@ test:php7_latest:
- apt update -yqq && apt install git zip -yqq
- git submodule init
- git submodule update
- ./vendor/bin/phpunit -c tests/phpunit.xml
- php --version
- ./vendor/bin/phpunit -c tests/phpunit.xml --testdox
except:
- tags
tags:
......
<?php
namespace Pellicule\Controllers;
use Slim\Routing\RouteContext;
use Slim\Psr7\Request;
use Slim\Psr7\Response;
use Fig\Http\Message\StatusCodeInterface;
use Pellicule\Models\Record;
use Pellicule\Providers\Provider;
......@@ -11,8 +9,15 @@ use Pellicule\Providers\FetchRecordResult;
class Media {
protected
$_request,
$_args = [];
public function __invoke($request, $response, $args) {
$fetch_result = $this->_fetchRecord($request, $args);
$this->_request = $request;
$this->_args = $this->_processArgs($args);
$fetch_result = $this->_fetchRecord();
if ($fetch_result->hasError()) {
$response->getBody()->write(json_encode(['error' => $fetch_result->getErrorCode()]));
......@@ -23,52 +28,56 @@ class Media {
$record = $fetch_result->getRecord();
$record->save();
$response->getBody()->write($record->asJSON());
return $response->withHeader('Content-Type', 'application/json');
}
protected function _fetchRecord($request, $args) {
$findargs = $this->_processParams($args);
if ($record = Record::findFirstBy($findargs))
protected function _fetchRecord() {
if (!$this->_args)
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_BAD_REQUEST, 'no valid params provided');
if ($record = Record::findFirstBy($this->_args))
return (new FetchRecordResult())->beSuccess($record);
if (!$request->hasHeader('Authorization'))
if (!$this->_request->hasHeader('Authorization'))
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_NOT_FOUND, 'record not found');
return $this->_fetchRecordFromProviders($request, $findargs);
return $this->_fetchRecordFromProviders();
}
protected function _fetchRecordFromProviders($request, $args){
$credentials = array_filter($this->_parseAuthorizationHeader($request));
if (!$credentials)
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED, 'no_valid_authentication_information_provided');
protected function _fetchRecordFromProviders() {
if (!$credentials = array_filter($this->_parseAuthorizationHeader()))
return (new FetchRecordResult())
->beError(StatusCodeInterface::STATUS_PROXY_AUTHENTICATION_REQUIRED,
'no_valid_authentication_information_provided');
$provider = Provider::newProvider($credentials[0]);
return $provider->fetchRecord($this->_processParams($args));
return $provider->fetchRecord($this->_args);
}
protected function _parseAuthorizationHeader($request){
return (new \Storm\Model\Collection($request->getHeader('Authorization')))
protected function _parseAuthorizationHeader() {
return (new \Storm\Collection($this->_request->getHeader('Authorization')))
->select(function($header)
{
return ($header && (strpos('Pellicule ', $header) == 0));
return ($header && 'Pellicule ' == substr($header, 0, 10));
})
->collect([$this, 'decodeAuthorizationHeader'])->getArrayCopy();
->collect([$this, 'decodeAuthorizationHeader'])
->getArrayCopy();
}
public function decodeAuthorizationHeader($string){
public function decodeAuthorizationHeader($string) {
return json_decode(base64_decode(str_replace('Pellicule ', '', $string)), true);
}
protected function _processParams($args){
protected function _processArgs($args) {
if (!$args)
return;
return [];
if (isset($args['isbn']))
return ['isbn' => $args['isbn']];
......
......@@ -14,28 +14,29 @@ class FileSystem {
}
public function download($media) {
$directory = static::$_base_path . $media->getLocation();
if (false === \file_exists($directory))
\mkdir($directory, 0755, true);
public function fileExists($path) {
return \file_exists(static::$_base_path . $path);
}
if (!$url_to_fetch = $media->getUrl())
public function download($url, $path) {
if (!$url || !$path)
return;
$client = $this->newHttpClient();
$directory = pathinfo(static::$_base_path . $path, PATHINFO_DIRNAME);
if (false === \file_exists($directory))
\mkdir($directory, 0755, true);
if (false === ($local_file = fopen($directory . '/' . $media->getLocalFileName() , 'w')))
return;
// can throw exception on invalid url
$request = (new RequestFactory())->createRequest('GET', $url);
if (false === ($local_file = fopen(static::$_base_path . $path , 'w')))
return;
$request = (new RequestFactory())->createRequest('GET', $url_to_fetch);
if ($request)
$client->sendRequest($request,['curl' => ['CURLOPT_FILE', $local_file ]] );
$this->newHttpClient()
->sendRequest($request,
['curl' => ['CURLOPT_FILE', $local_file]]);
fclose($local_file);
// $media->resizeTo('thumbnail');
// $directory = static::$_base_path . $media->getLocation();
//
}
}
......@@ -5,16 +5,19 @@ use \Storm\Model\ModelAbstract;
class Media extends ModelAbstract {
protected static $_file_system;
const
SIZE_FULL = 'fullsize',
TYPE_COVER = 'cover',
TYPE_BACKCOVER = 'back_cover';
protected
$_size;
protected static $_file_system;
protected
$_table_name = 'media',
$_belongs_to = [ 'record' => ['model' => Record::class ] ];
/** @category testing */
public static function setFileSystem($filesystem) {
static::$_file_system = $filesystem;
}
......@@ -34,39 +37,61 @@ class Media extends ModelAbstract {
public function afterSave() {
$this->getFileSystem()->download($this);
if (!$this->fileExists())
$this->getFileSystem()->download($this->getUrl(), $this->getFilePath());
}
public function fileExists() {
return $this->isNew()
? false
: $this->getFileSystem()->fileExists($this->getFilePath());
}
public function getSize(){
return (isset($this->_size))
? $this->_size
: 'fullsize';
public function validate() {
$this->checkAttribute('type',
in_array($this->getType(),
[static::TYPE_COVER, static::TYPE_BACKCOVER]),
'unknown type');
}
public function setSize($string){
$this->_size = $string;
return $this;
public function getSize() {
return static::SIZE_FULL;
}
public function splitId(){
return array_slice(str_split(sprintf("%04d", $this->getId()))
, 0, 4 );
public function getFilePath() {
return $this->getLocation() . $this->getLocalFileName();
}
public function getLocation(){
return '/' . implode('/', array_merge([$this->getSize(), $this->getType()], $this->splitId())) .'/';
public function splitId() {
return array_slice(str_split(sprintf('%04d', $this->getId())),
0, 4);
}
public function getLocalFileName(){
return $this->getId() . '.' . $this->getExtension();
protected function getLocation() {
return $this->isNew()
? ''
: '/' . implode('/', array_merge([$this->getSize(),
$this->getType()],
$this->splitId())) . '/';
}
public function getExtension(){
return pathinfo( $this->getUrl() , PATHINFO_EXTENSION );
protected function getLocalFileName() {
return $this->isNew()
? ''
: $this->getId() . '.' . $this->getExtension();
}
protected function getExtension() {
return $this->hasUrl()
? pathinfo($this->getUrl(), PATHINFO_EXTENSION)
: '';
}
}
......@@ -38,11 +38,11 @@ class Electre extends Provider {
public function fetchRecord($search_request) {
try{
try {
$response = $this->_getToken($this->_client_id, $this->_client_secret);
} catch(\Exception $exception) {
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_GATEWAY_TIMEOUT
,
,
'no_answer_from_gateway');
}
......@@ -74,7 +74,6 @@ class Electre extends Provider {
'no_answer_from_gateway');
}
if ($response->getStatusCode() != StatusCodeInterface::STATUS_OK)
return (new FetchRecordResult())->beError(StatusCodeInterface::STATUS_NOT_FOUND,
'ean_not_found');
......@@ -100,7 +99,7 @@ class Electre extends Provider {
return (new \Pellicule\Models\Media())
->setProvider($this->providerName())
->setUrl($link->href)
->setType( (($link->rel=='coverCopy')
->setType( (($link->rel == 'coverCopy')
? 'back_cover'
: $link->rel));
});
......
<?php
namespace Pellicule\Tests;
class BrowserMockBuilder {
protected $_mock;
public function __construct() {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$this->_mock = \Storm\Test\ObjectWrapper::mock('from ' . $stack[0]['file'] . ':' . $stack[0]['line']);
}
public function expectHttpQuery($request, $response) {
$request = array_merge(['type' => 'get', 'url' => '', 'headers' => [], 'body' => []],
$request);
$with_params = [];
$with_params[] = $request['url'];
$sith_params[] = $request['headers'];
if ($request['type'] == 'post')
$with_params[] = http_build_query($request['body']);
$this
->_mock
->whenCalled($request['type'])
->with($with_params)
->answers($this->_forgePSR7Response($response));
return $this;
}
public function expectPost($post, $response) {
$post = array_merge(['url' => '', 'headers' => [], 'body' => []],
$post);
$this
->_mock
->whenCalled('post')
->with($post['url'],
$post['headers'],
http_build_query($post['body']))
->answers($this->_forgePSR7Response($response));
return $this;
}
public function expectGet($get, $response) {
$get = array_merge(['url' => '', 'headers' => []],
$get);
$this
->_mock
->whenCalled('get')
->with($get['url'],
$get['headers'])
->answers($this->_forgePSR7Response($response));
return $this;
}
public function getMock() {
return $this->_mock;
}
protected function _forgePSR7Response($response) {
$response = array_merge(['status' => 200, 'headers' => null, 'content' => ''],
$response);
return new \Slim\Psr7\Response($response['status'],
$response['headers'],
(new \Slim\Psr7\Factory\StreamFactory())->createStream($response['content']));
}
}
......@@ -7,46 +7,38 @@ use Pellicule\Models\Record;
use Pellicule\Models\Media;
use Pellicule\Providers\Electre;
use Pellicule\Providers\Orb;
use Pellicule\FileSystem;
use \org\bovigo\vfs\vfsStream;
class FileSystemTest extends TestCase {
protected $_root;
public function setUp(){
$this->_root = vfsStream::setup('myDir');
FileSystem::setBasePath(vfsStream::url('myDir'));
$mock_browser = (new BrowserMockBuilder())
->expectGet(['url' => 'http://image.org/city.jpg',
],
['content' => file_get_contents(realpath(dirname(__FILE__)) . '/' .'imagefullsize.jpg')])
->getMock()
->beStrict();
$app = AppFactory::create();
Record::newInstance(['id' => 1,
'isbn' => '2259228194',
'ean' => '9782259228190',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb445155569'])
->assertSave();
Media::newInstance(['id'=> 135,
'record_id'=> 1,
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
])
->assertSave();
parent::setUp();
Media::setFileSystem(null);
$this->_http_client
->whenCalled('get')
->answers($this->_forgePSR7Response(['content' => file_get_contents(realpath(dirname(__FILE__)) . '/' .'imagefullsize.jpg')]));
$this->fixture(Record::class,
['id' => 1,
'isbn' => '2259228194',
'ean' => '9782259228190',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb445155569']);
$this->fixture(Media::class,
['id'=> 135,
'record_id'=> 1,
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
]);
}
/** @test */
public function fullsizeCoverDirectoryShouldBeCreated() {
$this->assertTrue($this->_root->hasChild('fullsize/cover/0/1'));
......@@ -57,11 +49,4 @@ class FileSystemTest extends TestCase {
public function fullsizeCoverFileShouldBeCreated() {
$this->assertTrue($this->_root->hasChild('fullsize/cover/0/1/3/5/135.jpg'));
}
//TBD
/** @test */
// public function thumbnailCoverDirectoryShouldBeCreated() {
// $this->assertTrue($this->_root->hasChild('thumbnail/cover/0/1/3/5/135.jpg'));
//}
}
......@@ -2,81 +2,64 @@
namespace Pellicule\Tests;
use \Pellicule\AppFactory;
use \Pellicule\Models\Record;
use \Pellicule\Models\Media;
use \Pellicule\FileSystem;
use \Pellicule\Providers\Electre;
use \Pellicule\Providers\Orb;
use \Pellicule\Providers\Provider;
use \org\bovigo\vfs\vfsStream;
class MediaGetTest extends TestCase {
class MediaLocalRecordTest extends TestCase {
protected
$_response,
$_root;
public function setUp(){
$this->_root = vfsStream::setup('myDir');
FileSystem::setBasePath(vfsStream::url('myDir'));
$app = AppFactory::create();
Record::newInstance(['id' => 1,
'isbn' => '2259228194',
'ean' => '9782259228190',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb445155569'])
->assertSave();
Media::newInstance(['id'=> 135,
'record_id'=> 1,
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
])
->assertSave();
Media::newInstance(['id'=> 147,
'record_id'=>1,
'type' => 'back_cover',
'url' => 'http://image.org/cityback.jpg',
'provider' => 'Me',
'created_at' => '2020-01-21 08:00:00',
'updated_at' => '2020-02-14 08:00:00'
])
->assertSave();
Record::newInstance(['id'=> 4,
'isbn' => '9782072843334',
'ean' => '2070380513',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb35097231f'])
->assertSave();
Media::newInstance(['id'=> 155,
'record_id'=>4,
'type' => 'cover',
'url' => 'http://image.org/ARDTP-ALODJFEF.jpg',
'provider' => 'Me',
'created_at' => '1980-01-13 08:00:00',
'updated_at' => '1980-01-16 08:00:00'
])
->assertSave();
}
public function urlsToTest(){
return [['/1.0/media/isbn/2259228194']
,['/1.0/media/ark:/12148/cb445155569']
,['/1.0/media/ean/9782259228190']];
}
/** @test
* @dataProvider urlsToTest
parent::setUp();
Media::setFileSystem($this->mock()->whenCalled('fileExists')->answers(true));
$this->fixture(Record::class,
['id' => 1,
'isbn' => '9782259228190',
'ean' => '9782259228190',
'ark' => 'https://catalogue.bnf.fr/ark:/12148/cb445155569']);
$this->fixture(Media::class,
['id'=> 135,
'record_id'=> 1,
'type' => 'cover',
'url' => 'http://image.org/city.jpg',
'provider' => 'Me',
'created_at' => '2020-01-13 08:00:00',
'updated_at' => '2020-01-16 08:00:00'
]);
$this->fixture(Media::class,
['id'=> 147,
'record_id'=>1,
'type' => 'back_cover',
'url' => 'http://image.org/cityback.jpg',
'provider' => 'Me',
'created_at' => '2020-01-21 08:00:00',
'updated_at' => '2020-02-14 08:00:00'
]);
}
public function possibleRecord1Urls() {
return [['/1.0/media/isbn/9782259228190'],
['/1.0/media/ark:/12148/cb445155569'],
['/1.0/media/ean/9782259228190']];
}
/**
* @test
* @dataProvider possibleRecord1Urls
*/
public function whenCalledWithISBNAndISBNFoundResponseShouldContainsMedia($url) {
public function whenCalledWithPossibleRecord1UrlsResponseShouldContainsMedia($url) {
$expected = [ 'media'=>
[
[ 'url' => 'http://image.org/city.jpg',
......@@ -87,25 +70,26 @@ class MediaGetTest extends TestCase {
]
];
$this->assertEquals($expected,
json_decode($this->httpGet($url),TRUE));
}
public function urlsFailuresToTest(){
return [['/1.0/media/isbn/2413004084', 404]
,['/1.0/media/UNEAUTREREQUETE', 501]];
/**
* @test
*/
public function whenCalledWithunknownIsbnResponseCodeShouldBe404() {
$this->assertEquals(404,
$this->httpStatusCode('/1.0/media/isbn/2413004084'));
}
/**
* @test
* @dataProvider urlsFailuresToTest
*/
public function whenCalledWithErroneousDataResponseCodeShouldBeCorrect($url, $status) {
$this->assertEquals($status,
$this->httpHeader($url));
public function whenCalledWithBadParametersResponseCodeShouldBeNotImplemented() {
$this->assertEquals(501,
$this->httpStatusCode('/1.0/media/UNEAUTREREQUETE'));
}
}
......@@ -114,18 +98,18 @@ class MediaGetTest extends TestCase {
class MediaWithErrorOnAuthenticationTest extends TestCase {
/** @test */