diff --git a/src/Activities/AcceptHandler.php b/src/Activities/AcceptHandler.php index 45bc154..26aa1b5 100644 --- a/src/Activities/AcceptHandler.php +++ b/src/Activities/AcceptHandler.php @@ -60,7 +60,11 @@ class AcceptHandler implements EventSubscriberInterface if ( ! $follow ) { return; } - if ( ! $follow->hasField( 'object') || ! $follow['object'] == $localActor ) { + if ( ! ( $follow->hasField( 'actor') && $localActor->equals( $follow['actor'] ) ) ) { + return; + } + $remoteActor = $event->getRequest()->attributes->get('actor'); + if ( ! $remoteActor->equals( $follow['object'] ) ) { return; } if ( $localActor->hasField( 'following' ) ) { @@ -76,7 +80,8 @@ class AcceptHandler implements EventSubscriberInterface $localActor = $this->objectsService->update( $localActor['id'], $updatedLocalActor ); $following = $localActor['following']; } - $this->collectionsService->addItem( $following, $activity['actor'] ); + $newFollowing = $follow['object']; + $this->collectionsService->addItem( $following, $newFollowing->asArray() ); } public function handleOutbox( OutboxActivityEvent $event ) @@ -97,14 +102,14 @@ class AcceptHandler implements EventSubscriberInterface if ( is_array( $followId ) && array_key_exists( 'id', $followId ) ) { $followId = $followId['id']; } - if ( !is_string( $followId ) ) { + if ( ! is_string( $followId ) ) { return; } $follow = $this->objectsService->dereference( $followId ); if ( ! $follow ) { return; } - $follow = $follow->asArray( 0 ); + $follow = $follow->asArray(); } if ( !$follow || !array_key_exists( 'object', $follow ) ) { return; @@ -113,11 +118,22 @@ class AcceptHandler implements EventSubscriberInterface if ( is_array( $followObjectId ) && array_key_exists( 'id', $followObjectId ) ) { $followObjectId = $followObjectId['id']; } - $actor = $event->getActor(); - if ( $followObjectId !== $actor['id'] ) { + $localActor = $event->getActor(); + if ( $followObjectId !== $localActor['id'] ) { return; } - $this->collectionsService->addItem( $actor['followers'], $activity['actor'] ); + $followers = $localActor['followers']; + if ( ! $followers ) { + $updatedLocalActor = $localActor->asArray(); + $updatedLocalActor['followers'] = array( + '@context' => $this->contextProvider->getContext(), + 'id' => rtrim( $updatedLocalActor['id'], '/' ) . '/followers', + 'type' => 'OrderedCollection', + 'orderedItems' => array(), + ); + $localActor = $this->objectsService->update( $localActor['id'], $updatedLocalActor ); + $followers = $localActor['followers']; + } + $this->collectionsService->addItem( $followers, $follow['actor'] ); } } - diff --git a/src/ActivityPub.php b/src/ActivityPub.php index 3cb5b98..91df93b 100644 --- a/src/ActivityPub.php +++ b/src/ActivityPub.php @@ -4,6 +4,7 @@ namespace ActivityPub; +use ActivityPub\Activities\AcceptHandler; use ActivityPub\Activities\CreateHandler; use ActivityPub\Activities\DeleteHandler; use ActivityPub\Activities\NonActivityHandler; @@ -86,6 +87,7 @@ class ActivityPub $dispatcher->addSubscriber( $this->module->get( CreateHandler::class ) ); $dispatcher->addSubscriber( $this->module->get( UpdateHandler::class ) ); $dispatcher->addSubscriber( $this->module->get( DeleteHandler::class ) ); + $dispatcher->addSubscriber( $this->module->get( AcceptHandler::class ) ); } /** @@ -97,7 +99,7 @@ class ActivityPub */ public function updateSchema() { - $entityManager = $this->module->get( EntityManager::class ); + $entityManager = @$this->module->get( EntityManager::class ); $schemaTool = new SchemaTool( $entityManager ); $classes = $entityManager->getMetadataFactory()->getAllMetadata(); $schemaTool->updateSchema( $classes ); diff --git a/src/Config/ActivityPubModule.php b/src/Config/ActivityPubModule.php index 51d7ea7..7d05d54 100644 --- a/src/Config/ActivityPubModule.php +++ b/src/Config/ActivityPubModule.php @@ -4,6 +4,7 @@ namespace ActivityPub\Config; +use ActivityPub\Activities\AcceptHandler; use ActivityPub\Activities\CreateHandler; use ActivityPub\Activities\DeleteHandler; use ActivityPub\Activities\NonActivityHandler; @@ -133,6 +134,11 @@ class ActivityPubModule $this->injector->register( DeleteHandler::class, DeleteHandler::class ) ->addArgument( new Reference( SimpleDateTimeProvider::class ) ) ->addArgument( new Reference( ObjectsService::class ) ); + + $this->injector->register( AcceptHandler::class, AcceptHandler::class ) + ->addArgument( new Reference( ObjectsService::class ) ) + ->addArgument( new Reference( CollectionsService::class ) ) + ->addArgument( new Reference( ContextProvider::class ) ); } /** diff --git a/test/Activities/AcceptHandlerTest.php b/test/Activities/AcceptHandlerTest.php index a4956a2..0cc822b 100644 --- a/test/Activities/AcceptHandlerTest.php +++ b/test/Activities/AcceptHandlerTest.php @@ -2,20 +2,344 @@ namespace ActivityPub\Test\Activities; +use ActivityPub\Activities\AcceptHandler; +use ActivityPub\Activities\InboxActivityEvent; +use ActivityPub\Activities\OutboxActivityEvent; +use ActivityPub\Objects\CollectionsService; +use ActivityPub\Objects\ContextProvider; +use ActivityPub\Objects\ObjectsService; use ActivityPub\Test\TestConfig\APTestCase; +use ActivityPub\Test\TestUtils\TestActivityPubObject; +use Symfony\Component\EventDispatcher\EventDispatcher; class AcceptHandlerTest extends APTestCase { + public static function getObjects() + { + return array( + 'https://example.com/follows/1' => array( + 'id' => 'https://example.com/follows/1', + 'type' => 'Follow', + 'actor' => array( + 'id' => 'https://example.com/actors/1', + 'following' => array( + 'type' => 'OrderedCollection', + ), + ), + 'object' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + 'https://example.com/actors/1' => array( + 'id' => 'https://example.com/actors/1', + 'following' => array( + 'type' => 'OrderedCollection', + ) + ), + 'https://example.com/follows/2' => array( + 'id' => 'https://example.com/follows/2', + 'type' => 'Follow', + 'actor' => array( + 'id' => 'https://example.com/actors/2' + ), + 'object' => array( + 'id' => 'https://elsewhere.com/actors/1', + ), + ), + 'https://elsewhere.com/follows/1' => array( + 'id' => 'https://elsewhere.com/follows/1', + 'type' => 'Follow', + 'actor' => array( + 'id' => 'https://elsewhere.com/actors/1', + ), + 'object' => 'https://example.com/actors/1', + ), + 'https://example.com/actors/3' => array( + 'id' => 'https://example.com/actors/3', + ), + 'https://example.com/follows/3' => array( + 'id' => 'https://example.com/follows/3', + 'actor' => array( + 'id' => 'https://example.com/actors/3', + ), + 'object' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + ); + } + public function testHandleInbox() { - // TODO implement me - $this->assertTrue( false ); + $testCases = array( + array( + 'id' => 'basicInboxTest', + 'eventName' => InboxActivityEvent::NAME, + 'event' => new InboxActivityEvent( + array( + 'id' => 'https://elsewhere.com/accepts/1', + 'type' => 'Accept', + 'actor' => array( + 'id' => 'https://elsewhere.com/actors/1', + ), + 'object' => array( + 'id' => 'https://example.com/follows/1', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'following' => array( + 'type' => 'OrderedCollection', + ), + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/inbox', + array( 'actor' => TestActivityPubObject::fromArray( array ( + 'id' => 'https://elsewhere.com/actors/1' + ) ) ) + ) + ), + 'expectedNewFollowing' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + array( + 'id' => 'acceptForSomeoneElsesFollow', + 'eventName' => InboxActivityEvent::NAME, + 'event' => new InboxActivityEvent( + array( + 'id' => 'https://elsewhere.com/accepts/1', + 'type' => 'Accept', + 'actor' => array( + 'id' => 'https://elsewhere.com/actors/1', + ), + 'object' => array( + 'id' => 'https://example.com/follows/2', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'following' => array( + 'type' => 'OrderedCollection', + ), + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/inbox', + array( 'actor' => TestActivityPubObject::fromArray( array ( + 'id' => 'https://elsewhere.com/actors/1' + ) ) ) + ) + ), + ), + array( + 'id' => 'noFollowingTest', + 'eventName' => InboxActivityEvent::NAME, + 'event' => new InboxActivityEvent( + array( + 'id' => 'https://elsewhere.com/accepts/2', + 'type' => 'Accept', + 'actor' => array( + 'id' => 'https://elsewhere.com/actors/2', + ), + 'object' => array( + 'id' => 'https://example.com/follows/3', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/3', + ) ), + self::requestWithAttributes( + 'https://example.com/actors/3/inbox', + array( 'actor' => TestActivityPubObject::fromArray( array ( + 'id' => 'https://elsewhere.com/actors/1' + ) ) ) + ) + ), + 'expectedNewFollowing' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + ); + foreach( $testCases as $testCase ) { + $objectsService = $this->getMock( ObjectsService::class ); + $objectsService->method( 'dereference')->willReturnCallback( function( $id ) { + $objects = self::getObjects(); + if ( array_key_exists( $id, $objects ) ) { + return TestActivityPubObject::fromArray( $objects[$id] ); + } else { + return null; + } + }); + $objectsService->method( 'update')->willReturnCallback( function( $id, $arr ) { + return TestActivityPubObject::fromArray( $arr ); + }); + $collectionsService = $this->getMockBuilder( CollectionsService::class ) + ->disableOriginalConstructor() + ->setMethods( array( 'addItem' ) ) + ->getMock(); + if ( array_key_exists( 'expectedNewFollowing', $testCase ) ) { + $collectionsService->expects( $this->once() ) + ->method( 'addItem' ) + ->with( + $this->anything(), + $this->equalTo( $testCase['expectedNewFollowing'] ) + ); + } else { + $collectionsService->expects( $this->never() ) + ->method( 'addItem' ); + } + $contextProvider = new ContextProvider(); + $acceptHandler = new AcceptHandler( $objectsService, $collectionsService, $contextProvider ); + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addSubscriber( $acceptHandler ); + $eventDispatcher->dispatch( $testCase['eventName'], $testCase['event'] ); + } } public function testHandleOutbox() { - // TODO implement me - $this->assertTrue( false ); + $testCases = array( + array( + 'id' => 'notAutoAccepted', + 'eventName' => OutboxActivityEvent::NAME, + 'event' => new OutboxActivityEvent( + array( + 'id' => 'https://example.com/accepts/1', + 'type' => 'Accept', + 'object' => array( + 'id' => 'https://elsewhere.com/follows/1', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'followers' => array( + 'type' => 'OrderedCollection', + ) + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/outbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'followers' => array( + 'type' => 'OrderedCollection', + ) + ) ), + ) + ) + ), + 'expectedNewFollower' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + array( + 'id' => 'autoAccepted', + 'eventName' => OutboxActivityEvent::NAME, + 'event' => new OutboxActivityEvent( + array( + 'id' => 'https://example.com/accepts/1', + 'type' => 'Accept', + 'actor' => array( + 'id' => 'https://example.com/actor/1', + ), + 'object' => array( + 'id' => 'https://elsewhere.com/follows/1', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'followers' => array( + 'type' => 'OrderedCollection', + ) + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/outbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + 'followers' => array( + 'type' => 'OrderedCollection', + ) + ) ), + 'follow' => array( + 'id' => 'https://elsewhere.com/follow/4', + 'type' => 'Follow', + 'actor' => array( + 'id' => 'https://elsewhere.com/actors/1', + ), + 'object' => array( + 'id' => 'https://example.com/actors/1', + ), + ), + ) + ) + ), + 'expectedNewFollower' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + array( + 'id' => 'noFollowersCollection', + 'eventName' => OutboxActivityEvent::NAME, + 'event' => new OutboxActivityEvent( + array( + 'id' => 'https://example.com/accepts/1', + 'type' => 'Accept', + 'object' => array( + 'id' => 'https://elsewhere.com/follows/1', + ), + ), + TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + ) ), + self::requestWithAttributes( + 'https://example.com/actors/1/outbox', + array( + 'actor' => TestActivityPubObject::fromArray( array( + 'id' => 'https://example.com/actors/1', + ) ), + ) + ) + ), + 'expectedNewFollower' => array( + 'id' => 'https://elsewhere.com/actors/1', + ) + ), + ); + foreach( $testCases as $testCase ) { + $objectsService = $this->getMock( ObjectsService::class ); + $objectsService->method( 'dereference')->willReturnCallback( function( $id ) { + $objects = self::getObjects(); + if ( array_key_exists( $id, $objects ) ) { + return TestActivityPubObject::fromArray( $objects[$id] ); + } else { + return null; + } + }); + $objectsService->method( 'update')->willReturnCallback( function( $id, $arr ) { + return TestActivityPubObject::fromArray( $arr ); + }); + $collectionsService = $this->getMockBuilder( CollectionsService::class ) + ->disableOriginalConstructor() + ->setMethods( array( 'addItem' ) ) + ->getMock(); + if ( array_key_exists( 'expectedNewFollower', $testCase ) ) { + $collectionsService->expects( $this->once() ) + ->method( 'addItem' ) + ->with( + $this->anything(), + $this->equalTo( $testCase['expectedNewFollower'] ) + ); + } else { + $collectionsService->expects( $this->never() ) + ->method( 'addItem' ); + } + $contextProvider = new ContextProvider(); + $acceptHandler = new AcceptHandler( $objectsService, $collectionsService, $contextProvider ); + $eventDispatcher = new EventDispatcher(); + $eventDispatcher->addSubscriber( $acceptHandler ); + $eventDispatcher->dispatch( $testCase['eventName'], $testCase['event'] ); + } } } diff --git a/test/Activities/DeleteHandlerTest.php b/test/Activities/DeleteHandlerTest.php index 1bfe38e..7ab8ed6 100644 --- a/test/Activities/DeleteHandlerTest.php +++ b/test/Activities/DeleteHandlerTest.php @@ -152,13 +152,6 @@ class DeleteHandlerTest extends APTestCase } } - public static function requestWithAttributes( $uri, $attributes ) - { - $request = Request::create( $uri ); - $request->attributes->add( $attributes ); - return $request; - } - private static function getObjects() { return array( diff --git a/test/Activities/UpdateHandlerTest.php b/test/Activities/UpdateHandlerTest.php index 5ef5c13..be20dd9 100644 --- a/test/Activities/UpdateHandlerTest.php +++ b/test/Activities/UpdateHandlerTest.php @@ -225,12 +225,5 @@ class UpdateHandlerTest extends APTestCase } } } - - public static function requestWithAttributes( $uri, $attributes ) - { - $request = Request::create( $uri ); - $request->attributes->add( $attributes ); - return $request; - } } diff --git a/test/Objects/CollectionsServiceDbTest.php b/test/Objects/CollectionsServiceDbTest.php index 1becfa2..2454376 100644 --- a/test/Objects/CollectionsServiceDbTest.php +++ b/test/Objects/CollectionsServiceDbTest.php @@ -46,7 +46,7 @@ class CollectionsServiceDbTest extends SQLiteTestCase */ private $collectionsService; - public function setUp() + protected function setUp() { parent::setUp(); $dbConfig = Setup::createAnnotationMetadataConfiguration( @@ -401,7 +401,7 @@ class CollectionsServiceDbTest extends SQLiteTestCase ); foreach ( $testCases as $testCase ) { - self::setUp(); + $this->setUp(); $collection = $this->objectsService->persist( $testCase['collection'] ); $this->collectionsService->addItem( $collection, $testCase['item'] ); $expectedDataSet = new ArrayDataSet( $testCase['expectedDataSet'] ); @@ -415,7 +415,7 @@ class CollectionsServiceDbTest extends SQLiteTestCase ); $this->assertTablesEqual( $expectedObjects, $actualObjects, "Error on test $testCase[id]"); $this->assertTablesEqual( $expectedFields, $actualFields, "Error on test $testCase[id]"); - self::tearDown(); + $this->tearDown(); } } diff --git a/test/Objects/CollectionsServiceTest.php b/test/Objects/CollectionsServiceTest.php index f782734..5d9b289 100644 --- a/test/Objects/CollectionsServiceTest.php +++ b/test/Objects/CollectionsServiceTest.php @@ -6,7 +6,7 @@ use ActivityPub\Auth\AuthService; use ActivityPub\Objects\CollectionsService; use ActivityPub\Objects\ContextProvider; use ActivityPub\Objects\ObjectsService; -use ActivityPub\Test\TestConfig\SQLiteTestCase; +use ActivityPub\Test\TestConfig\APTestCase; use ActivityPub\Test\TestUtils\TestActivityPubObject; use ActivityPub\Utils\SimpleDateTimeProvider; use Doctrine\ORM\EntityManager; @@ -15,7 +15,7 @@ use GuzzleHttp\Psr7\Response as Psr7Response; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -class CollectionsServiceTest extends SQLiteTestCase +class CollectionsServiceTest extends APTestCase { /** * @var CollectionsService @@ -24,7 +24,6 @@ class CollectionsServiceTest extends SQLiteTestCase public function setUp() { - parent::setUp(); $authService = new AuthService(); $contextProvider = new ContextProvider(); $httpClient = $this->getMock( Client::class ); @@ -50,11 +49,6 @@ class CollectionsServiceTest extends SQLiteTestCase ); } - protected function getDataSet() - { - return new ArrayDataSet( array( 'objects' => array(), 'fields' => array() ) ); - } - public function testCollectionPaging() { $testCases = array( diff --git a/test/TestConfig/APTestCase.php b/test/TestConfig/APTestCase.php index f0e578e..c40b4dc 100644 --- a/test/TestConfig/APTestCase.php +++ b/test/TestConfig/APTestCase.php @@ -3,6 +3,7 @@ namespace ActivityPub\Test\TestConfig; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Request; abstract class APTestCase extends TestCase { @@ -10,4 +11,17 @@ abstract class APTestCase extends TestCase { return parent::getMock( $originalClassName, $methods, $arguments, $mockClassName, $callOriginalConstructor, $callOriginalClone, $callAutoload, $cloneArguments, $callOriginalMethods, $proxyTarget ); // TODO: Change the autogenerated stub } + + /** + * @param string $uri + * @param array $attributes + * @return Request + */ + public static function requestWithAttributes( $uri, $attributes ) + { + $request = Request::create( $uri ); + $request->attributes->add( $attributes ); + return $request; + } + } diff --git a/test/TestConfig/SQLiteTestCase.php b/test/TestConfig/SQLiteTestCase.php index 371e495..0ce9d97 100644 --- a/test/TestConfig/SQLiteTestCase.php +++ b/test/TestConfig/SQLiteTestCase.php @@ -18,8 +18,8 @@ abstract class SQLiteTestCase extends APTestCase final public function getConnection() { - if ( $this->conn === null ) { - if ( $this->pdo === null ) { + if ( ! isset( $this->conn ) ) { + if ( ! isset( $this->pdo ) ) { $this->dbPath = $this->getDbPath(); $this->pdo = new \PDO( "sqlite:{$this->dbPath}" ); }