From 0663c54a43b3c97dbf1aa1492bbe184025de6cdc Mon Sep 17 00:00:00 2001 From: dannylamb Date: Tue, 30 Apr 2019 14:53:48 -0300 Subject: [PATCH] Fixing media indexing in Fedora with 5.0 (#62) * Fixing media indexing in Fedora with 5.0 * Using external content for non-fedora binaries * Controller tests --- Milliner/composer.lock | 88 +++---- .../src/Controller/MillinerController.php | 38 +++- Milliner/src/Service/MillinerService.php | 162 ++++++------- .../src/Service/MillinerServiceInterface.php | 15 ++ Milliner/src/app.php | 1 + .../Milliner/Tests/MillinerControllerTest.php | 124 +++++++++- .../Milliner/Tests/SaveExternalTest.php | 215 ++++++++++++++++++ .../Milliner/Tests/SaveMediaTest.php | 74 +++--- .../Islandora/Milliner/Tests/SaveNodeTest.php | 44 ++++ 9 files changed, 579 insertions(+), 182 deletions(-) create mode 100644 Milliner/tests/Islandora/Milliner/Tests/SaveExternalTest.php diff --git a/Milliner/composer.lock b/Milliner/composer.lock index bd89f9ad..d703c8ae 100644 --- a/Milliner/composer.lock +++ b/Milliner/composer.lock @@ -1,7 +1,7 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], "content-hash": "97d026ae3ad1a955ac05743a60324ae5", @@ -594,7 +594,7 @@ } ], "description": "Shared code amongst Islandora Crayfish microservices", - "time": "2019-04-12 20:13:55" + "time": "2019-04-12T20:13:55+00:00" }, { "name": "ml/iri", @@ -1206,16 +1206,16 @@ }, { "name": "symfony/debug", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "43ce8ab34c734dcc8a4af576cb86711daab964c5" + "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/43ce8ab34c734dcc8a4af576cb86711daab964c5", - "reference": "43ce8ab34c734dcc8a4af576cb86711daab964c5", + "url": "https://api.github.com/repos/symfony/debug/zipball/2d279b6bb1d582dd5740d4d3251ae8c18812ed37", + "reference": "2d279b6bb1d582dd5740d4d3251ae8c18812ed37", "shasum": "" }, "require": { @@ -1258,11 +1258,11 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-03-10T17:09:50+00:00" + "time": "2019-04-11T11:27:41+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -1325,16 +1325,16 @@ }, { "name": "symfony/http-foundation", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "61094ca72e8934c6502af5259ef6296eb09aa424" + "reference": "90454ad44c95d75faf3507d56388056001b74baf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/61094ca72e8934c6502af5259ef6296eb09aa424", - "reference": "61094ca72e8934c6502af5259ef6296eb09aa424", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/90454ad44c95d75faf3507d56388056001b74baf", + "reference": "90454ad44c95d75faf3507d56388056001b74baf", "shasum": "" }, "require": { @@ -1375,20 +1375,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-03-25T07:48:46+00:00" + "time": "2019-04-17T14:51:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "30a9e48839413e6a6a8dc8989da0e7dd76c8fcf8" + "reference": "14fa41ccd38570b5e3120a3754bbaa144a15f311" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/30a9e48839413e6a6a8dc8989da0e7dd76c8fcf8", - "reference": "30a9e48839413e6a6a8dc8989da0e7dd76c8fcf8", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/14fa41ccd38570b5e3120a3754bbaa144a15f311", + "reference": "14fa41ccd38570b5e3120a3754bbaa144a15f311", "shasum": "" }, "require": { @@ -1464,11 +1464,11 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-04-02T13:47:51+00:00" + "time": "2019-04-17T15:57:07+00:00" }, { "name": "symfony/inflector", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/inflector.git", @@ -1810,7 +1810,7 @@ }, { "name": "symfony/property-access", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/property-access.git", @@ -1877,7 +1877,7 @@ }, { "name": "symfony/routing", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", @@ -1953,16 +1953,16 @@ }, { "name": "symfony/security", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/security.git", - "reference": "c70729657a1a76e3399e48d9ce557dfad37dd201" + "reference": "fdbff3de6a0486aa10fcc3f0cbfe336d46534f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security/zipball/c70729657a1a76e3399e48d9ce557dfad37dd201", - "reference": "c70729657a1a76e3399e48d9ce557dfad37dd201", + "url": "https://api.github.com/repos/symfony/security/zipball/fdbff3de6a0486aa10fcc3f0cbfe336d46534f27", + "reference": "fdbff3de6a0486aa10fcc3f0cbfe336d46534f27", "shasum": "" }, "require": { @@ -2032,11 +2032,11 @@ ], "description": "Symfony Security Component", "homepage": "https://symfony.com", - "time": "2019-04-01T07:08:40+00:00" + "time": "2019-04-17T14:49:35+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -3487,16 +3487,16 @@ }, { "name": "symfony/browser-kit", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "c0fadd368c1031109e996316e53ffeb886d37ea1" + "reference": "7f2b0843d5045468225f9a9b27a0cb171ae81828" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c0fadd368c1031109e996316e53ffeb886d37ea1", - "reference": "c0fadd368c1031109e996316e53ffeb886d37ea1", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/7f2b0843d5045468225f9a9b27a0cb171ae81828", + "reference": "7f2b0843d5045468225f9a9b27a0cb171ae81828", "shasum": "" }, "require": { @@ -3540,20 +3540,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:06:07+00:00" + "time": "2019-04-06T19:33:58+00:00" }, { "name": "symfony/console", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "24206aff3efe6962593297e57ef697ebb220e384" + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/24206aff3efe6962593297e57ef697ebb220e384", - "reference": "24206aff3efe6962593297e57ef697ebb220e384", + "url": "https://api.github.com/repos/symfony/console/zipball/e2840bb38bddad7a0feaf85931e38fdcffdb2f81", + "reference": "e2840bb38bddad7a0feaf85931e38fdcffdb2f81", "shasum": "" }, "require": { @@ -3612,7 +3612,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-04-01T07:32:59+00:00" + "time": "2019-04-08T14:23:48+00:00" }, { "name": "symfony/contracts", @@ -3684,7 +3684,7 @@ }, { "name": "symfony/css-selector", - "version": "v3.4.24", + "version": "v3.4.26", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -3737,7 +3737,7 @@ }, { "name": "symfony/dom-crawler", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -3794,16 +3794,16 @@ }, { "name": "symfony/finder", - "version": "v4.2.5", + "version": "v4.2.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a" + "reference": "e45135658bd6c14b61850bf131c4f09a55133f69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a", + "url": "https://api.github.com/repos/symfony/finder/zipball/e45135658bd6c14b61850bf131c4f09a55133f69", + "reference": "e45135658bd6c14b61850bf131c4f09a55133f69", "shasum": "" }, "require": { @@ -3839,7 +3839,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:42:05+00:00" + "time": "2019-04-06T13:51:08+00:00" }, { "name": "theseer/fdomdocument", diff --git a/Milliner/src/Controller/MillinerController.php b/Milliner/src/Controller/MillinerController.php index 74835f9a..35fba721 100644 --- a/Milliner/src/Controller/MillinerController.php +++ b/Milliner/src/Controller/MillinerController.php @@ -62,7 +62,8 @@ public function saveNode($uuid, Request $request) ); } catch (\Exception $e) { $this->log->error("", ['Exception' => $e]); - return new Response($e->getMessage(), $e->getCode()); + $code = $e->getCode() == 0 ? 500 : $e->getCode(); + return new Response($e->getMessage(), $code); } } @@ -87,7 +88,8 @@ public function deleteNode($uuid, Request $request) ); } catch (\Exception $e) { $this->log->error("", ['Exception' => $e]); - return new Response($e->getMessage(), $e->getCode()); + $code = $e->getCode() == 0 ? 500 : $e->getCode(); + return new Response($e->getMessage(), $code); } } @@ -122,4 +124,36 @@ public function saveMedia($source_field, Request $request) return new Response($e->getMessage(), $code); } } + + /** + * @param string $uuid + * @param \Symfony\Component\HttpFoundation\Request $request + * @return \Symfony\Component\HttpFoundation\Response + */ + public function saveExternal($uuid, Request $request) + { + $token = $request->headers->get("Authorization", null); + $external_url = $request->headers->get("Content-Location"); + + if (empty($external_url)) { + return new Response("Expected external url in Content-Location header", 400); + } + + try { + $response = $this->milliner->saveExternal( + $uuid, + $external_url, + $token + ); + + return new Response( + $response->getBody(), + $response->getStatusCode() + ); + } catch (\Exception $e) { + $this->log->error("", ['Exception' => $e]); + $code = $e->getCode() == 0 ? 500 : $e->getCode(); + return new Response($e->getMessage(), $code); + } + } } diff --git a/Milliner/src/Service/MillinerService.php b/Milliner/src/Service/MillinerService.php index aab472f3..77101c97 100644 --- a/Milliner/src/Service/MillinerService.php +++ b/Milliner/src/Service/MillinerService.php @@ -80,7 +80,6 @@ public function saveNode( ); } else { return $this->updateNode( - $uuid, $jsonld_url, $urls['fedora'], $token @@ -171,12 +170,11 @@ protected function createNode( * @throws \GuzzleHttp\Exception\RequestException */ protected function updateNode( - $uuid, $jsonld_url, $fedora_url, $token = null ) { - // Get the jsonld from Fedora. + // Get the RDF from Fedora. $headers = empty($token) ? [] : ['Authorization' => $token]; $headers['Accept'] = 'application/ld+json'; $fedora_response = $this->fedora->getResource( @@ -189,7 +187,7 @@ protected function updateNode( $reason = $fedora_response->getReasonPhrase(); throw new \RuntimeException( "Client error: `GET $fedora_url` resulted in a `$status $reason` response: " . - $fedora_response->getBody(), + $fedora_response->getBody(), $status ); } @@ -204,9 +202,15 @@ protected function updateNode( true ); - $fedora_modified = $this->getModifiedTimestamp( - $fedora_jsonld - ); + // Account for the fact that new media haven't got a modified date + // pushed to it from Drupal yet. + try { + $fedora_modified = $this->getModifiedTimestamp( + $fedora_jsonld + ); + } catch (\RuntimeException $e) { + $fedora_modified = 0; + } // Get the jsonld from Drupal. $headers = empty($token) ? [] : ['Authorization' => $token]; @@ -214,7 +218,6 @@ protected function updateNode( $jsonld_url, ['headers' => $headers] ); - $drupal_jsonld = json_decode( $drupal_response->getBody(), true @@ -240,7 +243,7 @@ protected function updateNode( ); } - // Conditional save it in Fedora. + // Conditionally save it in Fedora. $headers['Content-Type'] = 'application/ld+json'; $headers['Prefer'] = 'return=minimal; handling=lenient'; $headers['If-Match'] = $etag; @@ -384,12 +387,15 @@ public function saveMedia( $fedora_file_url = $urls['fedora']; // Now look for the 'describedby' link header on the file in Fedora. - $fedora_response = $this->fedora->getResourceHeaders( + // I'm using the drupal http client because I have the full + // URI and need to squash redirects in case of external content. + $fedora_response = $this->drupal->head( $fedora_file_url, - $headers + ['allow_redirects' => false, 'headers' => $headers] ); $status = $fedora_response->getStatusCode(); - if ($status != 200) { + + if ($status != 200 && $status != 307) { $reason = $fedora_response->getReasonPhrase(); throw new \RuntimeException( "Client error: `HEAD $fedora_file_url` resulted in a `$status $reason` response: " . @@ -397,6 +403,7 @@ public function saveMedia( $status ); } + $fedora_url = $this->getLinkHeader($fedora_response, "describedby"); if (empty($fedora_url)) { throw new \RuntimeException( @@ -405,90 +412,11 @@ public function saveMedia( ); } - // Get the RDF from Fedora. - $headers = empty($token) ? [] : ['Authorization' => $token]; - $headers['Accept'] = 'application/ld+json'; - $fedora_response = $this->fedora->getResource( - $fedora_url, - $headers - ); - $status = $fedora_response->getStatusCode(); - if ($status != 200) { - $reason = $fedora_response->getReasonPhrase(); - throw new \RuntimeException( - "Client error: `GET $fedora_url` resulted in a `$status $reason` response: " . - $fedora_response->getBody(), - $status - ); - } - - // Strip off the W/ prefix to make the ETag strong. - $etags = $fedora_response->getHeader("ETag"); - $etag = ltrim(reset($etags), "W/"); - // Get the modified date from the RDF. - $fedora_jsonld = json_decode( - $fedora_response->getBody(), - true - ); - - // Account for the fact that new media haven't got a modified date - // pushed to it from Drupal yet. - try { - $fedora_modified = $this->getModifiedTimestamp( - $fedora_jsonld - ); - } catch (\RuntimeException $e) { - $fedora_modified = 0; - } - - // Get the jsonld from Drupal. - $headers = empty($token) ? [] : ['Authorization' => $token]; - $drupal_response = $this->drupal->get( - $jsonld_url, - ['headers' => $headers] - ); - $drupal_jsonld = json_decode( - $drupal_response->getBody(), - true - ); - // Mash it into the shape Fedora accepts. - // Be sure to give it the URL of the file being described, not that of - // the RDF itself. - $drupal_jsonld = $this->processJsonld( - $drupal_jsonld, + return $this->updateNode( $jsonld_url, - $fedora_file_url - ); - // Get the modified date from the RDF. - $drupal_modified = $this->getModifiedTimestamp( - $drupal_jsonld - ); - // Abort with 412 if the Drupal RDF is stale. - if ($drupal_modified <= $fedora_modified) { - throw new \RuntimeException( - "Not updating $fedora_url because RDF at $jsonld_url is not newer", - 412 - ); - } - // Conditional save it in Fedora. - $headers['Content-Type'] = 'application/ld+json'; - $headers['Prefer'] = 'return=minimal; handling=lenient'; - $headers['If-Match'] = $etag; - $response = $this->fedora->saveResource( $fedora_url, - json_encode($drupal_jsonld), - $headers + $token ); - $status = $response->getStatusCode(); - if (!in_array($status, [201, 204])) { - $reason = $response->getReasonPhrase(); - throw new \RuntimeException( - "Client error: `PUT $fedora_url` resulted in a `$status $reason` response: " . $response->getBody(), - $status - ); - } - // Return the response from Fedora. - return $response; } /** @@ -548,4 +476,52 @@ public function deleteNode( return new Response(isset($status) ? $status : 404); } + + /** + * {@inheritDoc} + */ + public function saveExternal( + $uuid, + $external_url, + $token = null + ) { + // Mint a new Fedora URL. + $fedora_url = $this->gemini->mintFedoraUrl($uuid, $token); + + $headers = empty($token) ? [] : ['Authorization' => $token]; + $mimetype = $this->drupal->head( + $external_url, + ['headers' => $headers] + )->getHeader('Content-Type')[0]; + + // Save it in Fedora as external content. + $external_rel = "http://fedora.info/definitions/fcrepo#ExternalContent"; + $link = '<' . $external_url . '>; rel="' . $external_rel . '"; handling="redirect"; type="' . $mimetype . '"'; + $headers['Link'] = $link; + $response = $this->fedora->saveResource( + $fedora_url, + null, + $headers + ); + + $status = $response->getStatusCode(); + if (!in_array($status, [201, 204])) { + $reason = $response->getReasonPhrase(); + throw new \RuntimeException( + "Client error: `PUT $fedora_url` resulted in a `$status $reason` response: " . $response->getBody(), + $status + ); + } + + // Map the URLS. + $this->gemini->saveUrls( + $uuid, + $external_url, + $fedora_url, + $token + ); + + // Return the response from Fedora. + return $response; + } } diff --git a/Milliner/src/Service/MillinerServiceInterface.php b/Milliner/src/Service/MillinerServiceInterface.php index 97d2cf8b..2c76373c 100644 --- a/Milliner/src/Service/MillinerServiceInterface.php +++ b/Milliner/src/Service/MillinerServiceInterface.php @@ -50,4 +50,19 @@ public function deleteNode( $uuid, $token = null ); + + /** + * @param $uuid + * @param $external_url + * @param $token + * + * @throws \Exception + * + * @return \GuzzleHttp\Psr7\Response|null + */ + public function saveExternal( + $uuid, + $external_url, + $token = null + ); } diff --git a/Milliner/src/app.php b/Milliner/src/app.php index ccd76beb..1594bf80 100644 --- a/Milliner/src/app.php +++ b/Milliner/src/app.php @@ -37,5 +37,6 @@ $app->post('/node/{uuid}', "milliner.controller:saveNode"); $app->delete('/node/{uuid}', "milliner.controller:deleteNode"); $app->post('/media/{source_field}', "milliner.controller:saveMedia"); +$app->post('/external/{uuid}', "milliner.controller:saveExternal"); return $app; diff --git a/Milliner/tests/Islandora/Milliner/Tests/MillinerControllerTest.php b/Milliner/tests/Islandora/Milliner/Tests/MillinerControllerTest.php index ab590edc..ec1f6e9a 100644 --- a/Milliner/tests/Islandora/Milliner/Tests/MillinerControllerTest.php +++ b/Milliner/tests/Islandora/Milliner/Tests/MillinerControllerTest.php @@ -50,6 +50,8 @@ public function testMethodsReturnMillinerErrors() ->willThrow(new \Exception("Forbidden", 403)); $milliner->deleteNode(Argument::any(), Argument::any()) ->willThrow(new \Exception("Forbidden", 403)); + $milliner->saveExternal(Argument::any(), Argument::any(), Argument::any()) + ->willThrow(new \Exception("Forbidden", 403)); $milliner = $milliner->reveal(); $controller = new MillinerController($milliner, $this->logger); @@ -66,7 +68,7 @@ public function testMethodsReturnMillinerErrors() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/node/1?_format=jsonld', ] ); @@ -85,7 +87,7 @@ public function testMethodsReturnMillinerErrors() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/media/6?_format=json', ] ); @@ -103,7 +105,7 @@ public function testMethodsReturnMillinerErrors() ['uuid' => $uuid], [], [], - ['Authorization' => 'Bearer islandora'] + ['HTTP_AUTHORIZATION' => 'Bearer islandora'] ); $response = $controller->deleteNode($uuid, $request); $status = $response->getStatusCode(); @@ -111,6 +113,25 @@ public function testMethodsReturnMillinerErrors() $status == 403, "Response code must be that of thrown exception. Expected 403, received $status" ); + + // External. + $request = Request::create( + "http://localhost:8000/milliner/external/$uuid", + "POST", + ['uuid' => $uuid], + [], + [], + [ + 'HTTP_AUTHORIZATION' => 'Bearer islandora', + 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/sites/default/files/1.jpg', + ] + ); + $response = $controller->saveExternal($uuid, $request); + $status = $response->getStatusCode(); + $this->assertTrue( + $status == 403, + "Response code must be that of thrown exception. Expected 403, received $status" + ); } /** @@ -129,7 +150,7 @@ public function testSaveNodeReturns400WithoutContentLocation() ['uuid' => $uuid], [], [], - ['Authorization' => 'Bearer islandora'] + ['HTTP_AUTHORIZATION' => 'Bearer islandora'] ); $response = $controller->saveNode($uuid, $request); $status = $response->getStatusCode(); @@ -156,7 +177,7 @@ public function testSaveMediaReturn400WithoutContentLocation() ['source_field' => $source_field], [], [], - ['Authorization' => 'Bearer islandora'] + ['HTTP_AUTHORIZATION' => 'Bearer islandora'] ); $response = $controller->saveMedia($source_field, $request); $status = $response->getStatusCode(); @@ -166,6 +187,32 @@ public function testSaveMediaReturn400WithoutContentLocation() ); } + /** + * @covers ::__construct + * @covers ::saveExternal + */ + public function testSaveExternalReturns400WithoutContentLocation() + { + $milliner = $this->prophesize(MillinerServiceInterface::class)->reveal(); + $controller = new MillinerController($milliner, $this->logger); + + $uuid = "abc123"; + $request = Request::create( + "http://localhost:8000/milliner/external/$uuid", + "POST", + ['uuid' => $uuid], + [], + [], + ['HTTP_AUTHORIZATION' => 'Bearer islandora'] + ); + $response = $controller->saveExternal($uuid, $request); + $status = $response->getStatusCode(); + $this->assertTrue( + $status == 400, + "Response code must be 400 when no Content-Location header is present. Received: $status" + ); + } + /** * @covers ::__construct * @covers ::saveNode @@ -187,7 +234,7 @@ public function testSaveNodeReturnsSuccessOnSuccess() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/node/1?_format=jsonld', ] ); @@ -211,7 +258,7 @@ public function testSaveNodeReturnsSuccessOnSuccess() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/node/1?_format=jsonld', ] ); @@ -243,7 +290,7 @@ public function testSaveMediaReturnsSuccessOnSuccess() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/media/6?_format=json', ] ); @@ -267,7 +314,7 @@ public function testSaveMediaReturnsSuccessOnSuccess() [], [], [ - 'Authorization' => 'Bearer islandora', + 'HTTP_AUTHORIZATION' => 'Bearer islandora', 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/media/6?_format=json', ] ); @@ -279,6 +326,63 @@ public function testSaveMediaReturnsSuccessOnSuccess() ); } + /** + * @covers ::__construct + * @covers ::saveExternal + */ + public function testSaveExternalReturnsSuccessOnSuccess() + { + $milliner = $this->prophesize(MillinerServiceInterface::class); + $milliner->saveExternal(Argument::any(), Argument::any(), Argument::any()) + ->willReturn(new Response(201)); + $milliner = $milliner->reveal(); + $controller = new MillinerController($milliner, $this->logger); + + // Nodes. + $uuid = "abc123"; + $request = Request::create( + "http://localhost:8000/milliner/external/$uuid", + "POST", + ['uuid' => $uuid], + [], + [], + [ + 'HTTP_AUTHORIZATION' => 'Bearer islandora', + 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/sites/default/files/1.jpeg', + ] + ); + $response = $controller->saveExternal($uuid, $request); + $status = $response->getStatusCode(); + $this->assertTrue( + $status == 201, + "Response code must be 201 when milliner returns 201. Received: $status" + ); + + $milliner = $this->prophesize(MillinerServiceInterface::class); + $milliner->saveExternal(Argument::any(), Argument::any(), Argument::any()) + ->willReturn(new Response(204)); + $milliner = $milliner->reveal(); + $controller = new MillinerController($milliner, $this->logger); + + $request = Request::create( + "http://localhost:8000/milliner/external/$uuid", + "POST", + ['uuid' => $uuid], + [], + [], + [ + 'HTTP_AUTHORIZATION' => 'Bearer islandora', + 'HTTP_CONTENT_LOCATION' => 'http://localhost:8000/sites/default/files/1.jpeg', + ] + ); + $response = $controller->saveExternal($uuid, $request); + $status = $response->getStatusCode(); + $this->assertTrue( + $status == 204, + "Response code must be 204 when milliner returns 204. Received: $status" + ); + } + /** * @covers ::__construct * @covers ::deleteNode @@ -298,7 +402,7 @@ public function testDeleteReturnsSuccessOnSuccess() ['uuid' => $uuid], [], [], - ['Authorization' => 'Bearer islandora'] + ['HTTP_AUTHORIZATION' => 'Bearer islandora'] ); $response = $controller->deleteNode($uuid, $request); diff --git a/Milliner/tests/Islandora/Milliner/Tests/SaveExternalTest.php b/Milliner/tests/Islandora/Milliner/Tests/SaveExternalTest.php new file mode 100644 index 00000000..7cee8391 --- /dev/null +++ b/Milliner/tests/Islandora/Milliner/Tests/SaveExternalTest.php @@ -0,0 +1,215 @@ +logger = new Logger('milliner'); + $this->logger->pushHandler(new NullHandler()); + + $this->modifiedDatePredicate = "http://schema.org/dateModified"; + } + + /** + * @covers ::__construct + * @covers ::saveExternal + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionCode 403 + */ + public function testSaveExternalThrowsOnMintError() + { + $gemini = $this->prophesize(GeminiClient::class); + $gemini->mintFedoraUrl(Argument::any(), Argument::any()) + ->willThrow( + new RequestException( + "Unauthorized", + new Request('POST', 'http://localhost:8000/gemini'), + new Response(403, [], "Unauthorized") + ) + ); + $gemini = $gemini->reveal(); + + $drupal = $this->prophesize(Client::class); + $drupal = $drupal->reveal(); + + $fedora = $this->prophesize(IFedoraApi::class); + $fedora = $fedora->reveal(); + + $milliner = new MillinerService( + $fedora, + $drupal, + $gemini, + $this->logger, + $this->modifiedDatePredicate + ); + + $milliner->saveExternal( + "9541c0c1-5bee-4973-a9d0-e55c1658bc81", + 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', + "Bearer islandora" + ); + } + + /** + * @covers ::__construct + * @covers ::saveExternal + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionCode 403 + */ + public function testSaveExternalThrowsOnHeadError() + { + $url = "http://localhost:8080/95/41/c0/c1/9541c0c1-5bee-4973-a9d0-e55c1658bc81"; + $gemini = $this->prophesize(GeminiClient::class); + $gemini->mintFedoraUrl(Argument::any(), Argument::any()) + ->willReturn($url); + $gemini = $gemini->reveal(); + + $drupal = $this->prophesize(Client::class); + $drupal->head(Argument::any(), Argument::any()) + ->willThrow( + new RequestException( + "Unauthorized", + new Request('HEAD', 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg'), + new Response(403, [], null, "1.1", "UNAUTHORIZED") + ) + ); + $drupal = $drupal->reveal(); + + $fedora = $this->prophesize(IFedoraApi::class); + $fedora = $fedora->reveal(); + + $milliner = new MillinerService( + $fedora, + $drupal, + $gemini, + $this->logger, + $this->modifiedDatePredicate + ); + + $milliner->saveExternal( + "9541c0c1-5bee-4973-a9d0-e55c1658bc81", + 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', + "Bearer islandora" + ); + } + + /** + * @covers ::__construct + * @covers ::saveExternal + * @expectedException \RuntimeException + * @expectedExceptionCode 403 + */ + public function testSaveExternalThrowsOnPutError() + { + $url = "http://localhost:8080/95/41/c0/c1/9541c0c1-5bee-4973-a9d0-e55c1658bc81"; + $gemini = $this->prophesize(GeminiClient::class); + $gemini->mintFedoraUrl(Argument::any(), Argument::any()) + ->willReturn($url); + $gemini = $gemini->reveal(); + + $drupal = $this->prophesize(Client::class); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn(new Response(200, ['Content-Type' => 'image/jpeg'])); + $drupal = $drupal->reveal(); + + $fedora = $this->prophesize(IFedoraApi::class); + $fedora->saveResource(Argument::any(), Argument::any(), Argument::any()) + ->willReturn(new Response(403, [], null, "1.1", "UNAUTHORIZED")); + $fedora = $fedora->reveal(); + + $milliner = new MillinerService( + $fedora, + $drupal, + $gemini, + $this->logger, + $this->modifiedDatePredicate + ); + + $milliner->saveExternal( + "9541c0c1-5bee-4973-a9d0-e55c1658bc81", + 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', + "Bearer islandora" + ); + } + + /** + * @covers ::__construct + * @covers ::saveExternal + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionCode 403 + */ + public function testSaveExternalThrowsOnGeminiError() + { + $url = "http://localhost:8080/95/41/c0/c1/9541c0c1-5bee-4973-a9d0-e55c1658bc81"; + $gemini = $this->prophesize(GeminiClient::class); + $gemini->mintFedoraUrl(Argument::any(), Argument::any()) + ->willReturn($url); + $gemini->saveUrls(Argument::any(), Argument::any(), Argument::any(), Argument::any()) + ->willThrow( + new RequestException( + "Unauthorized", + new Request('PUT', 'http://localhost:8000/gemini/9541c0c1-5bee-4973-a9d0-e55c1658bc81'), + new Response(403, [], "Unauthorized") + ) + ); + $gemini = $gemini->reveal(); + + $drupal = $this->prophesize(Client::class); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn(new Response(200, ['Content-Type' => 'image/jpeg'])); + $drupal = $drupal->reveal(); + + $fedora = $this->prophesize(IFedoraApi::class); + $fedora->saveResource(Argument::any(), Argument::any(), Argument::any()) + ->willReturn(new Response(201)); + $fedora = $fedora->reveal(); + + $milliner = new MillinerService( + $fedora, + $drupal, + $gemini, + $this->logger, + $this->modifiedDatePredicate + ); + + $milliner->saveExternal( + "9541c0c1-5bee-4973-a9d0-e55c1658bc81", + 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', + "Bearer islandora" + ); + } +} diff --git a/Milliner/tests/Islandora/Milliner/Tests/SaveMediaTest.php b/Milliner/tests/Islandora/Milliner/Tests/SaveMediaTest.php index 924ff2b2..9c8b4fa4 100644 --- a/Milliner/tests/Islandora/Milliner/Tests/SaveMediaTest.php +++ b/Milliner/tests/Islandora/Milliner/Tests/SaveMediaTest.php @@ -187,16 +187,17 @@ public function testSaveMediaThrowsFedoraHeadError() $drupal = $this->prophesize(Client::class); $drupal->get(Argument::any(), Argument::any()) ->willReturn($drupal_response); + + $head_response = new Response(404); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); $drupal = $drupal->reveal(); - $fedora_response = new Response(404); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_response); $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -239,16 +240,17 @@ public function testSaveMediaThrows500WhenNoDescribedbyHeader() $drupal = $this->prophesize(Client::class); $drupal->get(Argument::any(), Argument::any()) ->willReturn($drupal_response); + + $head_response = new Response(200); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); $drupal = $drupal->reveal(); - $fedora_response = new Response(200); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_response); $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -291,26 +293,27 @@ public function testSaveMediaThrowsFedoraGetError() $drupal = $this->prophesize(Client::class); $drupal->get(Argument::any(), Argument::any()) ->willReturn($drupal_response); - $drupal = $drupal->reveal(); $link = ''; $link .= ';rel="describedby"'; - $fedora_head_response = new Response( + $head_response = new Response( 200, ['Link' => $link] ); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); + $drupal = $drupal->reveal(); + $fedora_get_response = new Response( 404 ); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_head_response); $fedora->getResource(Argument::any(), Argument::any()) ->willReturn($fedora_get_response); $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -363,28 +366,29 @@ public function testSaveMediaThrows412OnStaleData() ->willReturn($drupal_json_response); $drupal->get('http://localhost:8000/media/6?_format=jsonld', Argument::any()) ->willReturn($drupal_jsonld_response); - $drupal = $drupal->reveal(); $link = ''; $link .= '; rel="describedby"'; - $fedora_head_response = new Response( + $head_response = new Response( 200, ['Link' => $link] ); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); + $drupal = $drupal->reveal(); + $fedora_get_response = new Response( 200, ['Content-Type' => 'application/ld+json', 'ETag' => 'W\abc123'], file_get_contents(__DIR__ . '/../../../../static/MediaLDP-RS.jsonld') ); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_head_response); $fedora->getResource(Argument::any(), Argument::any()) ->willReturn($fedora_get_response); $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -437,14 +441,17 @@ public function testSaveMediaThrowsFedoraPutError() ->willReturn($drupal_json_response); $drupal->get('http://localhost:8000/media/6?_format=jsonld', Argument::any()) ->willReturn($drupal_jsonld_response); - $drupal = $drupal->reveal(); $link = ''; $link .= '; rel="describedby"'; - $fedora_head_response = new Response( + $head_response = new Response( 200, ['Link' => $link] ); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); + $drupal = $drupal->reveal(); + $fedora_get_response = new Response( 200, ['Content-Type' => 'application/ld+json', 'ETag' => 'W\abc123'], @@ -454,8 +461,6 @@ public function testSaveMediaThrowsFedoraPutError() 403 ); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_head_response); $fedora->getResource(Argument::any(), Argument::any()) ->willReturn($fedora_get_response); $fedora->saveResource(Argument::any(), Argument::any(), Argument::any()) @@ -463,7 +468,7 @@ public function testSaveMediaThrowsFedoraPutError() $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -514,14 +519,17 @@ public function testSaveMediaReturnsFedoraSuccess() ->willReturn($drupal_json_response); $drupal->get('http://localhost:8000/media/6?_format=jsonld', Argument::any()) ->willReturn($drupal_jsonld_response); - $drupal = $drupal->reveal(); $link = ''; $link .= '; rel="describedby"'; - $fedora_head_response = new Response( + $head_response = new Response( 200, ['Link' => $link] ); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); + $drupal = $drupal->reveal(); + $fedora_get_response = new Response( 200, ['Content-Type' => 'application/ld+json', 'ETag' => 'W\abc123'], @@ -531,8 +539,6 @@ public function testSaveMediaReturnsFedoraSuccess() 204 ); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_head_response); $fedora->getResource(Argument::any(), Argument::any()) ->willReturn($fedora_get_response); $fedora->saveResource(Argument::any(), Argument::any(), Argument::any()) @@ -540,7 +546,7 @@ public function testSaveMediaReturnsFedoraSuccess() $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); @@ -597,14 +603,18 @@ public function testSaveMediaReturnsNoModifiedDate() ->willReturn($drupal_json_response); $drupal->get('http://localhost:8000/media/6?_format=jsonld', Argument::any()) ->willReturn($drupal_jsonld_response); - $drupal = $drupal->reveal(); $link = ''; $link .= '; rel="describedby"'; - $fedora_head_response = new Response( + $head_response = new Response( 200, ['Link' => $link] ); + $drupal->head(Argument::any(), Argument::any()) + ->willReturn($head_response); + + $drupal = $drupal->reveal(); + $fedora_get_response = new Response( 200, ['Content-Type' => 'application/ld+json', 'ETag' => 'W\abc123'], @@ -614,8 +624,6 @@ public function testSaveMediaReturnsNoModifiedDate() 204 ); $fedora = $this->prophesize(IFedoraApi::class); - $fedora->getResourceHeaders(Argument::any(), Argument::any()) - ->willReturn($fedora_head_response); $fedora->getResource(Argument::any(), Argument::any()) ->willReturn($fedora_get_response); $fedora->saveResource(Argument::any(), Argument::any(), Argument::any()) @@ -623,7 +631,7 @@ public function testSaveMediaReturnsNoModifiedDate() $fedora = $fedora->reveal(); $mapping = [ - 'drupal' => 'http://localhost:8000/media/6?_format=jsonld', + 'drupal' => 'http://localhost:8000/sites/default/files/2017-07/sample_0.jpeg', 'fedora' => 'http://localhost:8080/fcrepo/rest/ff/b1/5b/4f/ffb15b4f-54db-44ce-ad0b-3588889a3c9b', ]; $gemini = $this->prophesize(GeminiClient::class); diff --git a/Milliner/tests/Islandora/Milliner/Tests/SaveNodeTest.php b/Milliner/tests/Islandora/Milliner/Tests/SaveNodeTest.php index 8e49d0d5..66cd3918 100644 --- a/Milliner/tests/Islandora/Milliner/Tests/SaveNodeTest.php +++ b/Milliner/tests/Islandora/Milliner/Tests/SaveNodeTest.php @@ -4,6 +4,8 @@ use GuzzleHttp\Client; use GuzzleHttp\Psr7\Response; +use GuzzleHttp\Psr7\Request; +use GuzzleHttp\Exception\RequestException; use Islandora\Chullo\IFedoraApi; use Islandora\Crayfish\Commons\Client\GeminiClient; use Islandora\Milliner\Service\MillinerService; @@ -42,6 +44,48 @@ protected function setUp() $this->modifiedDatePredicate = "http://schema.org/dateModified"; } + /** + * @covers ::__construct + * @covers ::saveNode + * @expectedException \GuzzleHttp\Exception\RequestException + * @expectedExceptionCode 403 + */ + public function testCreateNodeThrowsOnMintError() + { + $gemini = $this->prophesize(GeminiClient::class); + $gemini->getUrls(Argument::any(), Argument::any()) + ->willReturn([]); + $gemini->mintFedoraUrl(Argument::any(), Argument::any()) + ->willThrow( + new RequestException( + "Unauthorized", + new Request('POST', 'http://localhost:8000/gemini'), + new Response(403, [], "Unauthorized") + ) + ); + $gemini = $gemini->reveal(); + + $drupal = $this->prophesize(Client::class); + $drupal = $drupal->reveal(); + + $fedora = $this->prophesize(IFedoraApi::class); + $fedora = $fedora->reveal(); + + $milliner = new MillinerService( + $fedora, + $drupal, + $gemini, + $this->logger, + $this->modifiedDatePredicate + ); + + $milliner->saveNode( + "9541c0c1-5bee-4973-a9d0-e55c1658bc81", + "http://localhost:8000/node/1?_format=jsonld", + "Bearer islandora" + ); + } + /** * @covers ::__construct * @covers ::saveNode