diff --git a/CloudObjects/SDK/ObjectRetriever.php b/CloudObjects/SDK/ObjectRetriever.php index 6df451a..ee41b9e 100644 --- a/CloudObjects/SDK/ObjectRetriever.php +++ b/CloudObjects/SDK/ObjectRetriever.php @@ -680,9 +680,30 @@ class ObjectRetriever implements CustomCacheAndLogInterface { $this->putIntoCache($cacheId, $fileData, 0); $this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts); + } catch (ConnectException $e) { + // connection failed or timed out + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (connection failed).', $ts); + throw new CoreAPIException($e->getMessage(), $e); + } catch (RequestException $e) { + if ($e->hasResponse() && $e->getResponse()->getStatusCode() == 404) { + // not found (404) + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API.', $ts); + return null; + } else + if ($e->hasResponse()) { + // other API error (4xx, 5xx) + $statusCode = $e->getResponse()->getStatusCode(); + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API ['.$statusCode.'].', $ts); + throw new CoreAPIException($e->getMessage(), $e); + } else { + // API error without response + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (no status code).', $ts); + throw new CoreAPIException($e->getMessage(), $e); + } } catch (Exception $e) { - $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts); - // ignore exception - treat as non-existing file + // other exceptions + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> could not be retrieved from Core API (unexpected exception).', $ts); + throw new CoreAPIException($e->getMessage(), $e); } } diff --git a/tests/OfflineTests/ObjectRetrieverMockTest.php b/tests/OfflineTests/ObjectRetrieverMockTest.php index 1abe406..6601616 100644 --- a/tests/OfflineTests/ObjectRetrieverMockTest.php +++ b/tests/OfflineTests/ObjectRetrieverMockTest.php @@ -115,6 +115,35 @@ class ObjectRetrieverMockTest extends \PHPUnit\Framework\TestCase { $this->assertNull($content); } + public function testWhenAPIThrowsErrorForAttachment() { + $this->setMockResponse( + new Response(200, ['Content-Type' => 'application/ld+json'], + '{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'), + new Response(500) + ); + + $this->expectException(CoreAPIException::class); + $this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'file.txt'); + } + + public function testWhenAPITimesOutForAttachment() { + $this->setMockResponse( + new Response(200, ['Content-Type' => 'application/ld+json'], + '{"@id":"coid://cloudobjects.io/Test","coid://cloudobjects.io/isAtRevision":"1-abc"}'), + new ConnectException( + 'cURL error 28: Operation timed out', + new Request('GET', 'cloudobjects.io') + ) + ); + + try { + $this->retriever->getAttachment(new IRI('coid://cloudobjects.io/Test'), 'file.txt'); + $this->fail('Expected CoreAPIException was not thrown.'); + } catch (CoreAPIException $e) { + $this->assertInstanceOf(ConnectException::class, $e->getPrevious()); + } + } + private function createFallbackStoreDirectory() : string { $directory = sys_get_temp_dir().DIRECTORY_SEPARATOR.'clobj-fallback-'.uniqid(); mkdir($directory); diff --git a/tests/OnlineTests/ObjectRetrieverCacheTest.php b/tests/OnlineTests/ObjectRetrieverCacheTest.php index 63564ad..c52911d 100644 --- a/tests/OnlineTests/ObjectRetrieverCacheTest.php +++ b/tests/OnlineTests/ObjectRetrieverCacheTest.php @@ -51,4 +51,34 @@ class ObjectRetrieverCacheTest extends \PHPUnit\Framework\TestCase { $this->assertEquals($object2->getRevision(), $object3->getRevision()); } + public function testAttachmentCacheInRuntimeStorage() { + $cacheStorage = new VolatileRuntimeStorage; + $logger = new InMemoryLogger; + + $coid = new IRI('coid://cloudobjects.io/Test'); + $cacheId = (string)$coid.'#file.txt'; + + $retriever = new ObjectRetriever([ + 'cache_storage' => $cacheStorage, + 'logger' => $logger + ]); + + $content1 = $retriever->getAttachment($coid, 'file.txt'); + + $this->assertEquals('Test file content', trim($content1)); + $this->assertNotNull($cacheStorage->fetch($retriever->getCacheKey($cacheId))); + $this->assertStringContainsString('from Core API', $logger->getLastLogMessage()); + + // Reinitialize retriever with same cache storage to verify that cache is used + $retriever = new ObjectRetriever([ + 'cache_storage' => $cacheStorage, + 'logger' => $logger + ]); + + $content2 = $retriever->getAttachment($coid, 'file.txt'); + + $this->assertEquals('Test file content', trim($content2)); + $this->assertStringContainsString('from object cache', $logger->getLastLogMessage()); + } + } diff --git a/tests/OnlineTests/ObjectRetrieverPublicTest.php b/tests/OnlineTests/ObjectRetrieverPublicTest.php index 7aa38e5..0f54b47 100644 --- a/tests/OnlineTests/ObjectRetrieverPublicTest.php +++ b/tests/OnlineTests/ObjectRetrieverPublicTest.php @@ -33,6 +33,15 @@ class ObjectRetrieverPublicTest extends \PHPUnit\Framework\TestCase { $this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); } + public function testGetTestObject() { + $coid = new IRI('coid://cloudobjects.io/Test'); + $object = $this->retriever->getObjectNode($coid); + $this->assertNotNull($object); + $this->assertEquals((string)$coid, $object->getId()); + $this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertEquals('Test Object', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); + } + public function testGetRelatedObject() { $coid = new IRI('coid://cloudobjects.io'); $object = $this->retriever->getCloudObject($coid);