activitypub-1.0.x-dev/tests/src/Functional/FollowTest.php
tests/src/Functional/FollowTest.php
<?php
namespace Drupal\Tests\activitypub\Functional;
use Drupal\activitypub\Entity\ActivityPubActivityInterface;
use Drupal\Core\Url;
/**
* Tests Follow functionality.
*
* @group activitypub
*/
class FollowTest extends ActivityPubTestBase {
/**
* Test follow functionality.
*/
public function testFollow() {
$this->testFollowHelper();
}
/**
* Test follow helper.
*
* @throws \Behat\Mink\Exception\ExpectationException
* @throws \Behat\Mink\Exception\ResponseTextException
* @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\Entity\EntityStorageException
*/
protected function testFollowHelper() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->setTypeStatus('follow');
$this->setTypeStatus('undo');
// Set outbox handler.
$this->setOutboxHandler();
// Setup ActivityPub actors.
$this->enableActivityPub($assert_session, TRUE);
$this->drupalLogout();
$actor_href = Url::fromRoute('activitypub.user.self', ['user' => $this->authenticatedUserOne->id(), 'activitypub_actor' => $this->accountNameOne], ['absolute' => TRUE])->toString();
$object_href = Url::fromRoute('activitypub.user.self', ['user' => $this->authenticatedUserTwo->id(), 'activitypub_actor' => $this->accountNameTwo], ['absolute' => TRUE])->toString();
$payload = [
'type' => 'Follow',
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $actor_href . '/follow/1',
'actor' => $actor_href,
'object' => $object_href,
];
$inbox = Url::fromRoute('activitypub.inbox', ['user' => $this->authenticatedUserTwo->id(), 'activitypub_actor' => $this->accountNameTwo])->toString();
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(202, $response->getStatusCode());
/** @var \Drupal\activitypub\Entity\Storage\ActivityPubActorStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage('activitypub_activity');
/** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity */
$activity = $storage->load(1);
self::assertEquals(FALSE, $activity->isPublished());
self::assertEquals("Follow", $activity->getType());
self::assertEquals($payload['id'], $activity->getExternalId());
self::assertEquals($actor_href, $activity->getActor());
self::assertEquals($object_href, $activity->getObject());
self::assertEquals($this->authenticatedUserTwo->id(), $activity->getTargetEntityId());
self::assertEquals($this->authenticatedUserTwo->getEntityTypeId(), $activity->getTargetEntityTypeId());
$activity = $storage->load(2);
self::assertEquals(FALSE, $activity->isPublished());
self::assertEquals("Accept", $activity->getType());
self::assertEquals($object_href, $activity->getActor());
self::assertEquals($actor_href, $activity->getObject());
self::assertEquals($payload['id'], $activity->getExternalId());
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(1, $count);
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 0, 0, '', '', $assert_session, $page);
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 0, 0, '', '', $assert_session, $page);
$this->runOutboxQueue();
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
$storage->resetCache([1, 2]);
$activity = $storage->load(1);
self::assertEquals(TRUE, $activity->isPublished());
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 1, 0, $actor_href, '', $assert_session, $page);
// Allow to block incoming domains.
$this->drupalLogin($this->authenticatedUserTwo);
$edit = [
'activitypub_blocked_domains' => 'http://blocked.domain*',
];
$this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub/settings');
$this->submitForm($edit, 'Save');
$assert_session->responseContains('http://blocked.domain*');
$this->drupalLogout();
$payload = [
'type' => 'Follow',
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $actor_href . '/follow/2',
'actor' => 'http://blocked.domain/user/spammer',
'object' => $object_href,
];
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(403, $response->getStatusCode());
$payload['actor'] = 'http://allowed.domain/user/good';
$payload['id'] = 'http://allowed.domain/user/good/follow/2';
$payload['object'] = (object) [
'object' => $object_href,
];
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(202, $response->getStatusCode());
$activity = $storage->load(4);
self::assertEquals("Follow", $activity->getType());
self::assertEquals($payload['actor'], $activity->getActor());
self::assertEquals($object_href, $activity->getObject());
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(1, $count);
$activity = $storage->load(5);
self::assertEquals("outbox", $activity->getCollection());
self::assertEquals("Accept", $activity->getType());
self::assertEquals($payload['actor'], $activity->getObject());
self::assertEquals($object_href, $activity->getActor());
$this->runOutboxQueue();
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 2, 0, [$payload['actor'], $actor_href], '', $assert_session, $page);
// Remove follower (via undo).
$payload['type'] = 'Undo';
$payload['id'] = 'http://allowed.domain/user/good/follow/3';
$payload['actor'] = 'http://allowed.domain/user/good';
$payload['object'] = (object) [
'type' => 'Follow',
'actor' => $payload['actor'],
'object' => $object_href,
];
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(202, $response->getStatusCode());
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 1, 0, $actor_href, '', $assert_session, $page);
$this->drupalGet('nodeinfo/content');
$content = json_decode($page->getContent());
self::assertEquals(2, $content->usage->users->total);
self::assertEquals(0, $content->usage->localPosts);
// Send a DELETE where actor and object are equal, this activity will not be
// saved.
$payload = [
'type' => 'Delete',
'id' => 'https://mastodon.social/user/deleted#delete',
'actor' => 'https://mastodon.social/user/deleted',
'object' => 'https://mastodon.social/user/deleted',
];
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(202, $response->getStatusCode());
$ids = $storage->getQuery()->accessCheck()->condition('uid', $this->authenticatedUserTwo->id())->execute();
self::assertEquals(2, count($ids));
// Make sure there's only one follower.
$payload = [
'type' => 'Follow',
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $actor_href . '/follow/1',
'actor' => $actor_href,
'object' => $object_href,
];
$inbox = Url::fromRoute('activitypub.inbox', ['user' => $this->authenticatedUserTwo->id(), 'activitypub_actor' => $this->accountNameTwo])->toString();
$response = $this->sendInboxRequest($inbox, $payload);
self::assertEquals(202, $response->getStatusCode());
$this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub/' . $this->accountNameTwo . '/followers', ['query' => ['page' => 0]]);
$assert_session->statusCodeEquals(200);
$content = json_decode($page->getContent());
self::assertEquals(1, count($content->orderedItems));
$ids = $storage->getQuery()->accessCheck()->condition('uid', $this->authenticatedUserTwo->id())->execute();
self::assertEquals(2, count($ids));
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
// Test follow started from internal.
$this->followUser($object_href, $actor_href, 2);
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(1, $count);
$activity = $storage->load(6);
self::assertEquals("outbox", $activity->getCollection());
self::assertEquals("Follow", $activity->getType());
self::assertEquals($object_href, $activity->getActor());
self::assertEquals($actor_href, $activity->getObject());
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 0, 0, '', '', $assert_session, $page);
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 1, 0, $actor_href, '', $assert_session, $page);
$this->runOutboxQueue();
$this->runOutboxQueue();
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
$activity = $storage->load(7);
self::assertEquals("inbox", $activity->getCollection());
self::assertEquals("Follow", $activity->getType());
self::assertEquals($object_href, $activity->getActor());
self::assertEquals($actor_href, $activity->getObject());
$activity = $storage->load(8);
self::assertEquals("outbox", $activity->getCollection());
self::assertEquals("Accept", $activity->getType());
self::assertEquals($actor_href, $activity->getActor());
self::assertEquals($object_href, $activity->getObject());
$activity = $storage->load(9);
self::assertEquals("inbox", $activity->getCollection());
self::assertEquals("Accept", $activity->getType());
self::assertEquals($actor_href, $activity->getActor());
self::assertEquals($object_href, $activity->getObject());
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 1, 0, $object_href, '', $assert_session, $page);
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 1, 1, $actor_href, $actor_href, $assert_session, $page);
//$this->viewActivityOverview();
// Undo follow.
$this->drupalLogin($this->authenticatedUserTwo);
$this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub');
$this->drupalGet('activitypub/6/undo');
$this->drupalLogout();
$activity = $storage->load(10);
self::assertEquals("outbox", $activity->getCollection());
self::assertEquals("Undo", $activity->getType());
self::assertEquals($object_href, $activity->getActor());
self::assertEquals($actor_href, $activity->getObject());
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(1, $count);
$this->runOutboxQueue();
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
//$this->viewActivityOverview();
$storage->resetCache();
$activity = $storage->load(6);
self::assertNull($activity);
$activity = $storage->load(7);
self::assertNull($activity);
$activity = $storage->load(8);
self::assertNull($activity);
$activity = $storage->load(9);
self::assertNull($activity);
$activity = $storage->load(10);
self::assertNotNull($activity);
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 0, 0, '', '', $assert_session, $page);
// Account 2 check
$this->assertFollowersAndFollowing($this->authenticatedUserTwo->id(), $this->accountNameTwo, 1, 0, $actor_href, '', $assert_session, $page);
// Follow an external user as user 1, then undo it. We cheat a little by
// simply saving an activity already, this test is really to figure out
// that the 'undo' call works.
$actor_random = ACTIVITYPUB_TEST_USER;
$activity_values = [
'collection' => ActivityPubActivityInterface::OUTBOX,
'config_id' => 'follow',
'type' => 'Follow',
'actor' => $actor_href,
'object' => $actor_random,
'queued' => FALSE,
'processed' => TRUE,
'status' => TRUE,
'uid' => $this->authenticatedUserOne->id(),
];
/** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $follow */
$follow = $storage->create($activity_values);
$follow->save();
$follow->set('queued', FALSE)->save();
$follow_id = $follow->id();
$activity_values['collection'] = ActivityPubActivityInterface::INBOX;
$activity_values['actor'] = $actor_random;
$activity_values['object'] = $actor_href;
$activity_values['config_id'] = '';
$activity_values['type'] = 'Accept';
$activity_values['status'] = FALSE;
$activity_values['processed'] = FALSE;
$accept = $storage->create($activity_values);
$accept->save();
$accept_id = $accept->id();
// Clear queues (the follow will have created one).
$this->clearQueue(ACTIVITYPUB_OUTBOX_QUEUE);
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 0, 1, '', $actor_random, $assert_session, $page);
// Undo follow.
$this->drupalLogin($this->authenticatedUserOne);
$this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub');
$this->drupalGet('activitypub/11/undo');
$this->drupalLogout();
$undo_activity = $storage->load(13);
self::assertEquals("outbox", $undo_activity->getCollection());
self::assertEquals("Undo", $undo_activity->getType());
self::assertEquals($actor_href, $undo_activity->getActor());
self::assertEquals($actor_random, $undo_activity->getObject());
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(1, $count);
$this->runOutboxQueue();
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
$count = \Drupal::queue(ACTIVITYPUB_OUTBOX_SEND_QUEUE)->numberOfItems();
self::assertEquals(0, $count);
// Account 1 check
$this->assertFollowersAndFollowing($this->authenticatedUserOne->id(), $this->accountNameOne, 0, 0, '', '', $assert_session, $page);
$storage->resetCache();
/** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $un */
$un = $storage->load($undo_activity->id());
self::assertTrue($un->isProcessed());
self::assertFalse($un->isQueued());
$f = $storage->load($follow_id);
self::assertNull($f);
$a = $storage->load($accept_id);
self::assertNull($a);
$this->drupalLogin($this->authenticatedUserOne);
$this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub');
$assert_session->pageTextNotContains('Follow');
}
}
