427 lines
18 KiB
PHP
427 lines
18 KiB
PHP
<?php
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
namespace CloudObjects\SDK;
|
|
|
|
use ML\IRI\IRI;
|
|
use GuzzleHttp\Client, GuzzleHttp\Handler\MockHandler,
|
|
GuzzleHttp\HandlerStack, GuzzleHttp\Psr7\Request, GuzzleHttp\Psr7\Response;
|
|
use GuzzleHttp\Exception\ConnectException, GuzzleHttp\Exception\RequestException;
|
|
use CloudObjects\SDK\Exceptions\CoreAPIException;
|
|
|
|
class ObjectRetrieverMockTest extends \PHPUnit\Framework\TestCase {
|
|
|
|
private $retriever;
|
|
|
|
/**
|
|
* @param Response|\Throwable $response A response, or an exception to throw instead.
|
|
*/
|
|
private function setMockResponse($response) {
|
|
$mock = new MockHandler([$response]);
|
|
$handler = HandlerStack::create($mock);
|
|
$this->retriever->setClient(new Client(['handler' => $handler]));
|
|
}
|
|
|
|
protected function setUp(): void {
|
|
$this->retriever = new ObjectRetriever;
|
|
}
|
|
|
|
public function testGetRootResource() {
|
|
$this->setMockResponse(new Response(200,
|
|
['Content-Type' => 'application/ld+json'],
|
|
'{"@context":{"cloudobjects":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":"cloudobjects:Namespace","cloudobjects:hasPublicListing":"true","cloudobjects:revision":"1-325baa62b76105f56dc09386f5a2ec91","rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}'
|
|
));
|
|
|
|
$coid = new IRI('coid://cloudobjects.io');
|
|
|
|
// Test node interface first
|
|
$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('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
$this->assertNull($object->getProperty('urn:example:nonexistingvalue'));
|
|
|
|
// Test CloudObject interface
|
|
$object = $this->retriever->getCloudObject($coid);
|
|
$this->assertNotNull($object);
|
|
$this->assertEquals((string)$coid, $object->getAsNode()->getId());
|
|
$this->assertEquals($coid, $object->getCOID());
|
|
$this->assertNotNull($object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
|
|
$this->assertEquals('CloudObjects', $object->getString('http://www.w3.org/2000/01/rdf-schema#label'));
|
|
$this->assertNull($object->getString('urn:example:nonexistingvalue'));
|
|
}
|
|
|
|
public function testGetNonExistingResource() {
|
|
$this->setMockResponse(new Response(404));
|
|
|
|
// should return "null"
|
|
$coid = new IRI('coid://nonexistingresource.example.com');
|
|
|
|
$object = $this->retriever->getObjectNode($coid);
|
|
$this->assertNull($object);
|
|
}
|
|
|
|
public function testWhenAPIThrowsError() {
|
|
$this->setMockResponse(new Response(500));
|
|
|
|
$coid = new IRI('coid://nonexistingresource.example.com');
|
|
|
|
$this->expectException(CoreAPIException::class);
|
|
$this->retriever->getObjectNode($coid);
|
|
}
|
|
|
|
public function testWhenAPITimesOut() {
|
|
$this->setMockResponse(new ConnectException(
|
|
'cURL error 28: Operation timed out',
|
|
new Request('GET', 'cloudobjects.io')
|
|
));
|
|
|
|
$coid = new IRI('coid://cloudobjects.io');
|
|
|
|
try {
|
|
$this->retriever->getObjectNode($coid);
|
|
$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);
|
|
return $directory;
|
|
}
|
|
|
|
private function removeDirectory(string $directory) {
|
|
$files = new \RecursiveIteratorIterator(
|
|
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS),
|
|
\RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
foreach ($files as $file)
|
|
$file->isDir() ? rmdir($file->getPathname()) : unlink($file->getPathname());
|
|
rmdir($directory);
|
|
}
|
|
|
|
public function testFallbackStoreSavesObjectInScope() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => 'example.com, cloudobjects.io',
|
|
]);
|
|
$body = '{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Test"}';
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'], $body));
|
|
|
|
try {
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
|
|
$location = $directory.'/cloudobjects.io/Test/object.jsonld';
|
|
$this->assertFileExists($location);
|
|
$this->assertEquals($body, file_get_contents($location));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testFallbackStoreSkipsObjectOutOfScope() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => [ 'example.com' ],
|
|
]);
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
|
'{"@id":"coid://cloudobjects.io/Test"}'));
|
|
|
|
try {
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertFileDoesNotExist($directory.'/cloudobjects.io/Test/object.jsonld');
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testFallbackStoreSavesAllObjectsWithWildcard() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => '*',
|
|
]);
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
|
'{"@id":"coid://cloudobjects.io"}'));
|
|
|
|
try {
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io'));
|
|
$this->assertFileExists($directory.'/cloudobjects.io/object.jsonld');
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
private function createRetrieverWithStoredObject(string $directory) {
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => '*',
|
|
]);
|
|
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
|
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
|
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Stored"}');
|
|
}
|
|
|
|
public function testFallbackStoreUsedWhenAPIReturns500() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithStoredObject($directory);
|
|
$this->setMockResponse(new Response(500));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertNotNull($object);
|
|
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testFallbackStoreUsedWhenAPITimesOut() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithStoredObject($directory);
|
|
$this->setMockResponse(new ConnectException(
|
|
'cURL error 28: Operation timed out',
|
|
new Request('GET', 'cloudobjects.io')
|
|
));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertNotNull($object);
|
|
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testFallbackStoreUsedWhenRequestFailsWithoutResponse() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithStoredObject($directory);
|
|
$this->setMockResponse(new RequestException(
|
|
'Request failed',
|
|
new Request('GET', 'cloudobjects.io')
|
|
));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertNotNull($object);
|
|
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testErrorWhenRequestFailsWithoutResponseAndObjectNotInFallbackStore() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => '*',
|
|
]);
|
|
$this->setMockResponse(new RequestException(
|
|
'Request failed',
|
|
new Request('GET', 'cloudobjects.io')
|
|
));
|
|
|
|
try {
|
|
$this->expectException(CoreAPIException::class);
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testFallbackStoreNotUsedOnClientError() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithStoredObject($directory);
|
|
$this->setMockResponse(new Response(403));
|
|
|
|
try {
|
|
$this->expectException(CoreAPIException::class);
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testErrorWhenAPIThrowsErrorAndObjectNotInFallbackStore() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'fallback_store_directory' => $directory,
|
|
'fallback_store_scope' => '*',
|
|
]);
|
|
$this->setMockResponse(new Response(500));
|
|
|
|
try {
|
|
$this->expectException(CoreAPIException::class);
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
private function createRetrieverWithPermanentObject(string $directory, array $options = []) {
|
|
$this->retriever = new ObjectRetriever(array_merge([
|
|
'permanent_store_directory' => $directory,
|
|
'permanent_store_scope' => '*',
|
|
], $options));
|
|
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
|
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
|
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Stored"}');
|
|
}
|
|
|
|
public function testPermanentStoreSavesObjectInScope() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'permanent_store_directory' => $directory,
|
|
'permanent_store_scope' => 'example.com, cloudobjects.io',
|
|
]);
|
|
$body = '{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Test"}';
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'], $body));
|
|
|
|
try {
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
|
|
$location = $directory.'/cloudobjects.io/Test/object.jsonld';
|
|
$this->assertFileExists($location);
|
|
$this->assertEquals($body, file_get_contents($location));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testPermanentStoreSkipsObjectOutOfScope() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'permanent_store_directory' => $directory,
|
|
'permanent_store_scope' => [ 'example.com' ],
|
|
]);
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
|
'{"@id":"coid://cloudobjects.io/Test"}'));
|
|
|
|
try {
|
|
$this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertFileDoesNotExist($directory.'/cloudobjects.io/Test/object.jsonld');
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testPermanentStoreUsedWithoutContactingAPI() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithPermanentObject($directory);
|
|
$mock = new MockHandler([ new Response(500) ]);
|
|
$this->retriever->setClient(new Client(['handler' => HandlerStack::create($mock)]));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertNotNull($object);
|
|
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
// The mocked response has not been consumed
|
|
$this->assertCount(1, $mock);
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testPermanentStoreNotOverwritten() {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithPermanentObject($directory);
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
|
'[{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Updated"}]'));
|
|
|
|
try {
|
|
$this->retriever->fetchAllObjectsInNamespace(new IRI('coid://cloudobjects.io'));
|
|
$this->assertStringContainsString('Stored',
|
|
file_get_contents($directory.'/cloudobjects.io/Test/object.jsonld'));
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testPermanentStoreTakesPrecedenceOverFallbackStore() {
|
|
$permanentDirectory = $this->createFallbackStoreDirectory();
|
|
$fallbackDirectory = $this->createFallbackStoreDirectory();
|
|
$this->createRetrieverWithPermanentObject($permanentDirectory, [
|
|
'fallback_store_directory' => $fallbackDirectory,
|
|
'fallback_store_scope' => '*',
|
|
]);
|
|
mkdir($fallbackDirectory.'/cloudobjects.io/Test', 0777, true);
|
|
file_put_contents($fallbackDirectory.'/cloudobjects.io/Test/object.jsonld',
|
|
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Fallback"}');
|
|
$this->setMockResponse(new Response(500));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertEquals('Stored', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($permanentDirectory);
|
|
$this->removeDirectory($fallbackDirectory);
|
|
}
|
|
}
|
|
|
|
private function createStaticObjectDirectory() : string {
|
|
$directory = $this->createFallbackStoreDirectory();
|
|
mkdir($directory.'/cloudobjects.io/Test', 0777, true);
|
|
file_put_contents($directory.'/cloudobjects.io/Test/object.jsonld',
|
|
'{"@id":"coid://cloudobjects.io/Test","http://www.w3.org/2000/01/rdf-schema#label":"Static"}');
|
|
return $directory;
|
|
}
|
|
|
|
public function testStaticObjectDirectoryUsedWithoutContactingAPI() {
|
|
$directory = $this->createStaticObjectDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'static_object_directory' => $directory,
|
|
]);
|
|
$mock = new MockHandler([ new Response(500) ]);
|
|
$this->retriever->setClient(new Client(['handler' => HandlerStack::create($mock)]));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertEquals('Static', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
// The mocked response has not been consumed
|
|
$this->assertCount(1, $mock);
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testStaticObjectDirectoryWithDeprecatedOptionName() {
|
|
$directory = $this->createStaticObjectDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'static_config_path' => $directory,
|
|
]);
|
|
$this->setMockResponse(new Response(500));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Test'));
|
|
$this->assertEquals('Static', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
public function testStaticObjectDirectoryFallsThroughToAPI() {
|
|
$directory = $this->createStaticObjectDirectory();
|
|
$this->retriever = new ObjectRetriever([
|
|
'static_object_directory' => $directory,
|
|
]);
|
|
$this->setMockResponse(new Response(200, ['Content-Type' => 'application/ld+json'],
|
|
'{"@id":"coid://cloudobjects.io/Other","http://www.w3.org/2000/01/rdf-schema#label":"API"}'));
|
|
|
|
try {
|
|
$object = $this->retriever->getObjectNode(new IRI('coid://cloudobjects.io/Other'));
|
|
$this->assertEquals('API', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue());
|
|
} finally {
|
|
$this->removeDirectory($directory);
|
|
}
|
|
}
|
|
|
|
}
|