Extract objectFromArray to service; implement CollectionService; test
This commit is contained in:
parent
499e4d235d
commit
ed46ec1e1f
@ -10,6 +10,7 @@ use ActivityPub\Controllers\OutboxController;
|
||||
use ActivityPub\Crypto\HttpSignatureService;
|
||||
use ActivityPub\Database\PrefixNamingStrategy;
|
||||
use ActivityPub\Http\ControllerResolver;
|
||||
use ActivityPub\Objects\ContextProvider;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use ActivityPub\Utils\SimpleDateTimeProvider;
|
||||
@ -24,6 +25,8 @@ use Symfony\Component\DependencyInjection\Reference;
|
||||
*/
|
||||
class ActivityPubModule
|
||||
{
|
||||
const COLLECTION_PAGE_SIZE = 20;
|
||||
|
||||
/**
|
||||
* @var ContainerBuilder
|
||||
*/
|
||||
@ -37,6 +40,10 @@ class ActivityPubModule
|
||||
'authFunction' => function() {
|
||||
return false;
|
||||
},
|
||||
'context' => array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
),
|
||||
);
|
||||
$options = array_merge( $defaults, $options );
|
||||
$this->validateOptions( $options );
|
||||
@ -76,10 +83,16 @@ class ActivityPubModule
|
||||
$this->injector->register( AuthListener::class, AuthListener::class )
|
||||
->addArgument( $options['authFunction'] );
|
||||
|
||||
$this->injector->register( CollectionsService::class, CollectionsService::class );
|
||||
|
||||
$this->injector->register( AuthService::class, AuthService::class );
|
||||
|
||||
$this->injector->register( ContextProvider::class, ContextProvider::class )
|
||||
->addArgument( $options['context'] );
|
||||
|
||||
$this->injector->register( CollectionsService::class, CollectionsService::class )
|
||||
->addArgument( self::COLLECTION_PAGE_SIZE )
|
||||
->addArgument( new Reference( AuthService::class ) )
|
||||
->addArgument( new Reference( ContextProvider::class ) );
|
||||
|
||||
$this->injector->register( GetObjectController::class, GetObjectController::class )
|
||||
->addArgument( new Reference( ObjectsService::class ) )
|
||||
->addArgument( new Reference( CollectionsService::class ) )
|
||||
|
@ -1,12 +1,35 @@
|
||||
<?php
|
||||
namespace ActivityPub\Objects;
|
||||
|
||||
use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Objects\ContextProvider;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CollectionsService
|
||||
{
|
||||
const PAGE_SIZE = 20;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $pageSize;
|
||||
|
||||
/**
|
||||
* @var AuthService
|
||||
*/
|
||||
private $authService;
|
||||
|
||||
/**
|
||||
* @var ContextProvider
|
||||
*/
|
||||
private $contextProvider;
|
||||
|
||||
public function __construct( int $pageSize, AuthService $authService,
|
||||
ContextProvider $contextProvider )
|
||||
{
|
||||
$this->pageSize = $pageSize;
|
||||
$this->authService = $authService;
|
||||
$this->contextProvider = $contextProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array representation of the $collection
|
||||
@ -17,28 +40,77 @@ class CollectionsService
|
||||
public function pageAndFilterCollection( Request $request,
|
||||
ActivityPubObject $collection )
|
||||
{
|
||||
// expected behavior:
|
||||
// - request with no 'offset' param returns the collection object,
|
||||
// with the first page appended as with Pleroma
|
||||
// - request with an 'offset' param returns the collection page starting
|
||||
// at that offset with the next PAGE_SIZE items
|
||||
if ( $request->query->has( 'offset' ) ) {
|
||||
// return a filtered collection page
|
||||
return $this->getCollectionPage(
|
||||
$collection, $request, $request->query->get( 'offset' ), $this->pageSize
|
||||
);
|
||||
}
|
||||
// else return the collection itself with the first page
|
||||
$colArr = array();
|
||||
foreach ( $collection->getFields() as $field ) {
|
||||
if ( ! in_array( $field->getName(), array( 'items', 'orderedItems' ) ) ) {
|
||||
if ( $field->hasValue() ) {
|
||||
$colArr[$field->getName()] = $field->getValue();
|
||||
} else {
|
||||
$colArr[$field->getName()] = $field->getTargetObject()->asArray( 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
$firstPage = $this->getCollectionPage(
|
||||
$collection, $request, 0, $this->pageSize
|
||||
);
|
||||
$colArr['first'] = $firstPage;
|
||||
return $colArr;
|
||||
}
|
||||
|
||||
private function getCollectionPage( ActivityPubObject $collection,
|
||||
Request $request,
|
||||
int $offset,
|
||||
int $pageSize )
|
||||
{
|
||||
$itemsKey = 'items';
|
||||
$pageType = 'CollectionPage';
|
||||
if ( $this->isOrdered( $collection ) ) {
|
||||
$isOrdered = $this->isOrdered( $collection );
|
||||
if ( $isOrdered ) {
|
||||
$itemsKey = 'orderedItems';
|
||||
$pageType = 'OrderedCollectionPage';
|
||||
}
|
||||
// Create and return the page as an array
|
||||
if ( ! $collection->hasField( $itemsKey ) ) {
|
||||
throw new InvalidArgumentException(
|
||||
"Collection does not have an \"$field\" key"
|
||||
);
|
||||
}
|
||||
$collectionItems = $collection->getFieldValue( $itemsKey );
|
||||
$pageItems = array();
|
||||
$idx = $offset;
|
||||
$count = 0;
|
||||
while ( $count < $pageSize ) {
|
||||
$item = $collectionItems->getFieldValue( $idx );
|
||||
if ( ! $item ) {
|
||||
break;
|
||||
}
|
||||
if ( is_string( $item ) ) {
|
||||
$pageItems[] = $item;
|
||||
$count++;
|
||||
} else if ( $this->authService->requestAuthorizedToView( $request, $item ) ) {
|
||||
$pageItems[] = $item->asArray( 1 );
|
||||
$count++;
|
||||
}
|
||||
$idx++;
|
||||
}
|
||||
$page = array(
|
||||
'@context' => $this->contextProvider->getContext(),
|
||||
'id' => $collection['id'] . "?offset=$offset",
|
||||
'type' => $pageType,
|
||||
$itemsKey => $pageItems,
|
||||
'partOf' => $collection['id'],
|
||||
);
|
||||
if ( $collectionItems->getFieldValue( $idx ) ) {
|
||||
$page['next'] = $collection['id'] . "?offset=$idx";
|
||||
}
|
||||
if ( $isOrdered ) {
|
||||
$page['startIndex'] = $offset;
|
||||
}
|
||||
return $page;
|
||||
}
|
||||
|
||||
private function isOrdered( ActivityPubObject $collection )
|
||||
|
26
src/Objects/ContextProvider.php
Normal file
26
src/Objects/ContextProvider.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace ActivityPub\Objects;
|
||||
|
||||
class ContextProvider
|
||||
{
|
||||
const DEFAULT_CONTEXT = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
);
|
||||
|
||||
private $ctx;
|
||||
|
||||
public function __construct( $ctx = null )
|
||||
{
|
||||
if ( ! $ctx ) {
|
||||
$ctx = self::DEFAULT_CONTEXT;
|
||||
}
|
||||
$this->ctx = $ctx;
|
||||
}
|
||||
|
||||
public function getContext()
|
||||
{
|
||||
return $this->ctx;
|
||||
}
|
||||
}
|
||||
?>
|
@ -8,6 +8,7 @@ use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Entities\Field;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use ActivityPub\Test\TestUtils\TestDateTimeProvider;
|
||||
use ActivityPub\Test\TestUtils\TestUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\HttpKernelInterface;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
@ -41,26 +42,13 @@ oYi+1hqp1fIekaxsyQIDAQAB
|
||||
$objectsService = $this->createMock( ObjectsService::class );
|
||||
$objectsService->method( 'dereference' )
|
||||
->will( $this->returnValueMap( array(
|
||||
array( self::KEY_ID, self::objectFromArray( self::KEY ) )
|
||||
array( self::KEY_ID, TestUtils::objectFromArray( self::KEY ) )
|
||||
) ) );
|
||||
$this->signatureListener = new SignatureListener(
|
||||
$httpSignatureService, $objectsService
|
||||
);
|
||||
}
|
||||
|
||||
private static function objectFromArray( $array ) {
|
||||
$object = new ActivityPubObject();
|
||||
foreach ( $array as $name => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$child = $this->objectFromArray( $value );
|
||||
Field::withObject( $object, $name, $child );
|
||||
} else {
|
||||
Field::withValue( $object, $name, $value );
|
||||
}
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
private function getEvent()
|
||||
{
|
||||
$kernel = $this->createMock( HttpKernelInterface::class );
|
||||
|
@ -5,8 +5,10 @@ use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Controllers\GetObjectController;
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Entities\Field;
|
||||
use ActivityPub\Objects\ContextProvider;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Objects\ObjectsService;
|
||||
use ActivityPub\Test\TestUtils\TestUtils;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@ -57,30 +59,18 @@ class GetObjectControllerTest extends TestCase
|
||||
$objectsService->method( 'dereference' )->will(
|
||||
$this->returnCallback( function( $uri ) {
|
||||
if ( array_key_exists( $uri, self::OBJECTS ) ) {
|
||||
return $this->objectFromArray( self::OBJECTS[$uri] );
|
||||
return TestUtils::objectFromArray( self::OBJECTS[$uri] );
|
||||
}
|
||||
})
|
||||
);
|
||||
$collectionsService = new CollectionsService();
|
||||
$authService = new AuthService();
|
||||
$contextProvider = new ContextProvider();
|
||||
$collectionsService = new CollectionsService( 4, $authService, $contextProvider );
|
||||
$this->getObjectController = new GetObjectController(
|
||||
$objectsService, $collectionsService, $authService
|
||||
);
|
||||
}
|
||||
|
||||
private function objectFromArray( $array ) {
|
||||
$object = new ActivityPubObject();
|
||||
foreach ( $array as $name => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$child = $this->objectFromArray( $value );
|
||||
Field::withObject( $object, $name, $child );
|
||||
} else {
|
||||
Field::withValue( $object, $name, $value );
|
||||
}
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
public function testItRendersPersistedObject()
|
||||
{
|
||||
$request = Request::create( 'https://example.com/objects/1' );
|
||||
|
@ -38,9 +38,10 @@ class EntityTest extends SQLiteTestCase
|
||||
'path' => $this->getDbPath(),
|
||||
);
|
||||
$this->entityManager = EntityManager::create( $dbParams, $dbConfig );
|
||||
$this->dateTimeProvider = new TestDateTimeProvider(
|
||||
new DateTime( "12:00" ), new DateTime( "12:01" )
|
||||
);
|
||||
$this->dateTimeProvider = new TestDateTimeProvider( array(
|
||||
'objects-service.create' => new DateTime( "12:00" ),
|
||||
'objects-service.update' => new DateTime( "12:01" ),
|
||||
) );
|
||||
}
|
||||
|
||||
private function getTime( $context ) {
|
||||
@ -51,12 +52,12 @@ class EntityTest extends SQLiteTestCase
|
||||
|
||||
public function testItCreatesAnObjectWithAPrivateKey()
|
||||
{
|
||||
$object = new ActivityPubObject( $this->dateTimeProvider->getTime( 'create' ) );
|
||||
$object = new ActivityPubObject( $this->dateTimeProvider->getTime( 'objects-service.create' ) );
|
||||
$privateKey = 'a private key';
|
||||
$object->setPrivateKey( $privateKey );
|
||||
$this->entityManager->persist( $object );
|
||||
$this->entityManager->flush();
|
||||
$now = $this->getTime( 'create' );
|
||||
$now = $this->getTime( 'objects-service.create' );
|
||||
$expected = new ArrayDataSet( array(
|
||||
'objects' => array(
|
||||
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now ),
|
||||
@ -79,7 +80,7 @@ class EntityTest extends SQLiteTestCase
|
||||
|
||||
public function itUpdatesAPrivateKey()
|
||||
{
|
||||
$object = new ActivityPubObject( $this->dateTimeProvider->getTime( 'create' ) );
|
||||
$object = new ActivityPubObject( $this->dateTimeProvider->getTime( 'objects-service.create' ) );
|
||||
$privateKey = 'a private key';
|
||||
$object->setPrivateKey( $privateKey );
|
||||
$this->entityManager->persist( $object );
|
||||
@ -88,7 +89,7 @@ class EntityTest extends SQLiteTestCase
|
||||
$object->setPrivateKey( $newPrivateKey );
|
||||
$this->entityManager->persiste( $object );
|
||||
$this->entityManager->flush();
|
||||
$now = $this->getTime( 'create' );
|
||||
$now = $this->getTime( 'objects-service.create' );
|
||||
$expected = new ArrayDataSet( array(
|
||||
'objects' => array(
|
||||
array( 'id' => 1, 'created' => $now, 'lastUpdated' => $now ),
|
||||
|
@ -1,14 +1,93 @@
|
||||
<?php
|
||||
namespace ActivityPub\Test\Objects;
|
||||
|
||||
use ActivityPub\Auth\AuthService;
|
||||
use ActivityPub\Objects\ContextProvider;
|
||||
use ActivityPub\Objects\CollectionsService;
|
||||
use ActivityPub\Test\TestUtils\TestUtils;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
class CollectionsServiceTest extends TestCase
|
||||
{
|
||||
private $collectionsService;
|
||||
|
||||
public function setUp()
|
||||
{
|
||||
$authService = new AuthService();
|
||||
$contextProvider = new ContextProvider();
|
||||
$this->collectionsService = new CollectionsService(
|
||||
4, $authService, $contextProvider
|
||||
);
|
||||
}
|
||||
|
||||
public function testCollectionsService()
|
||||
{
|
||||
// TODO implement me
|
||||
$this->assertTrue( false );
|
||||
$testCases = array(
|
||||
array(
|
||||
'id' => 'lessThanOnePage',
|
||||
'collection' => array(
|
||||
'@context' => array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
),
|
||||
'id' => 'https://example.com/objects/1',
|
||||
'type' => 'OrderedCollection',
|
||||
'orderedItems' => array(
|
||||
array(
|
||||
'id' => 'https://example.com/objects/2',
|
||||
),
|
||||
array(
|
||||
'id' => 'https://example.com/objects/3',
|
||||
),
|
||||
array(
|
||||
'id' => 'https://example.com/objects/4',
|
||||
),
|
||||
)
|
||||
),
|
||||
'request' => Request::create(
|
||||
'https://example.com/objects/1',
|
||||
Request::METHOD_GET
|
||||
),
|
||||
'expectedResult' => array(
|
||||
'@context' => array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
),
|
||||
'id' => 'https://example.com/objects/1',
|
||||
'type' => 'OrderedCollection',
|
||||
'first' => array(
|
||||
'@context' => array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
),
|
||||
'id' => 'https://example.com/objects/1?offset=0',
|
||||
'type' => 'OrderedCollectionPage',
|
||||
'partOf' => 'https://example.com/objects/1',
|
||||
'startIndex' => 0,
|
||||
'orderedItems' => array(
|
||||
array(
|
||||
'id' => 'https://example.com/objects/2',
|
||||
),
|
||||
array(
|
||||
'id' => 'https://example.com/objects/3',
|
||||
),
|
||||
array(
|
||||
'id' => 'https://example.com/objects/4',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
foreach ( $testCases as $testCase ) {
|
||||
$actual = $this->collectionsService->pageAndFilterCollection(
|
||||
$testCase['request'], TestUtils::objectFromArray( $testCase['collection'] )
|
||||
);
|
||||
$this->assertEquals(
|
||||
$testCase['expectedResult'], $actual, "Error on test $testCase[id]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
@ -40,9 +40,10 @@ class ObjectsServiceTest extends SQLiteTestCase
|
||||
'path' => $this->getDbPath(),
|
||||
);
|
||||
$this->entityManager = EntityManager::create( $dbParams, $dbConfig );
|
||||
$this->dateTimeProvider = new TestDateTimeProvider(
|
||||
array( 'objects-service.create' => new DateTime( "12:00" ), 'objects-service.update' => new DateTime( "12:01" ) )
|
||||
);
|
||||
$this->dateTimeProvider = new TestDateTimeProvider( array(
|
||||
'objects-service.create' => new DateTime( "12:00" ),
|
||||
'objects-service.update' => new DateTime( "12:01" ),
|
||||
) );
|
||||
$this->httpClient = new Client( array( 'http_errors' => false ) );
|
||||
$this->objectsService = new ObjectsService(
|
||||
$this->entityManager, $this->dateTimeProvider, $this->httpClient
|
||||
|
22
test/TestUtils/TestUtils.php
Normal file
22
test/TestUtils/TestUtils.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
namespace ActivityPub\Test\TestUtils;
|
||||
|
||||
use ActivityPub\Entities\ActivityPubObject;
|
||||
use ActivityPub\Entities\Field;
|
||||
|
||||
class TestUtils
|
||||
{
|
||||
public static function objectFromArray( $array ) {
|
||||
$object = new ActivityPubObject();
|
||||
foreach ( $array as $name => $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
$child = self::objectFromArray( $value );
|
||||
Field::withObject( $object, $name, $child );
|
||||
} else {
|
||||
Field::withValue( $object, $name, $value );
|
||||
}
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
}
|
||||
?>
|
Loading…
Reference in New Issue
Block a user