activitypub-1.0.x-dev/tests/src/Functional/ActivityPubTestBase.php

tests/src/Functional/ActivityPubTestBase.php
<?php

namespace Drupal\Tests\activitypub\Functional;

use Drupal\activitypub\Entity\ActivityPubActivityInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Url;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Tests\BrowserTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;

abstract class ActivityPubTestBase extends BrowserTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'activitypub',
    'activitypub_test',
    'webfinger',
    'nodeinfo',
    'page_cache',
    'dblog',
    'block',
    'node',
    'user',
    'path',
    'path_alias',
  ];

  /**
   * The HTTP client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $httpClient;

  /**
   * The name for the first account.
   *
   * @var string
   */
  protected $accountNameOne = 'NameOne';

  /**
   * The name for the second account.
   *
   * @var string
   */
  protected $accountNameTwo = 'NameTwo';

  /**
   * The default theme to use.
   *
   * @var string
   */
  protected $defaultTheme = 'starterkit_theme';

  /**
   * A user with all permissions.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $adminUser;

  /**
   * An authenticated user with less permissions.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $authenticatedUserOne;

  /**
   * An authenticated user with less permissions.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $authenticatedUserTwo;

  /**
   * An authenticated user with less permissions.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $authenticatedUserThree;

  /**
   * The authenticated user permissions.
   *
   * @var array
   */
  protected $authenticatedUserPermissions = [
    'allow users to enable activitypub',
    'administer nodes',
    'bypass node access',
    'access user profiles',
    'publish to site-wide actor',
    'administer url aliases',
  ];

  /**
   * Protected bundles.
   *
   * @var array
   */
  protected $bundles = [];

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->bundles = node_type_get_names();
    /** @var \Drupal\user\RoleInterface $role */
    $role = Role::load(RoleInterface::ANONYMOUS_ID);
    $this->grantPermissions($role, ['access user profiles']);

    $this->adminUser = $this->createUser([], 'administrator', TRUE);
    $this->authenticatedUserOne = $this->createUser($this->authenticatedUserPermissions, 'fediverseOne');
    $this->authenticatedUserTwo = $this->createUser($this->authenticatedUserPermissions, 'fediverseTwo');
    $this->authenticatedUserThree = $this->createUser($this->authenticatedUserPermissions, 'nonFediverse');

    // Set site-wide actor
    $this->setSitewideActor();

    $this->httpClient = $this->container->get('http_client_factory')
      ->fromOptions(['base_uri' => $this->baseUrl]);
  }

  /**
   * Setup language.
   *
   * @param $language
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function setupLanguage($language) {
    ConfigurableLanguage::createFromLangcode($language)->save();
    \Drupal::configFactory()->getEditable('system.site')->set('default_langcode', $language)->save();
    $this->rebuildContainer();
  }

  /**
   * Helper method to activate ActivityPub for authenticated user One and Two.
   *
   * @param $assert_session
   * @param bool $enable_second_account
   */
  protected function enableActivityPub($assert_session, bool $enable_second_account = FALSE) {
    $this->drupalLogin($this->authenticatedUserOne);
    $edit = [
      'activitypub_enable' => TRUE,
    ];

    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub');
    $assert_session->responseContains('ActivityPub is not enabled for your account.');
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/settings');

    $this->submitForm($edit, 'Save');
    $assert_session->responseContains('Please enter your ActivityPub username.');
    $edit = [
      'activitypub_enable' => TRUE,
      'activitypub_name' => $this->accountNameOne,
    ];
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/settings');
    $this->submitForm($edit, 'Save');

    if ($enable_second_account) {
      $this->drupalLogin($this->authenticatedUserTwo);
      $edit = [
        'activitypub_enable' => TRUE,
        'activitypub_name' => $this->accountNameTwo,
      ];
      $this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub/settings');
      $this->submitForm($edit, 'Save');
    }
  }

  /**
   * Create a type config entity.
   *
   * @param string $activity
   * @param string $bundle
   * @param string $object
   * @param array $mapping
   * @param string $id
   * @param string $plugin_id
   * @param string $target_entity_type_id
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createType(string $activity, string $bundle, string $object, array $mapping = [], string $id = 'map', string $plugin_id = 'activitypub_dynamic_types', string $target_entity_type_id = 'node') {
    if (!isset($this->bundles[$bundle])) {
      $this->bundles[$bundle] = TRUE;
      $this->drupalCreateContentType(['type' => $bundle]);
    }

    /** @var \Drupal\activitypub\Entity\ActivityPubTypeInterface $entity */
    $values = [
      'status' => TRUE,
      'label' => $bundle . ' - ' . $object,
      'id' => $id,
      'plugin' => [
        'id' => $plugin_id,
        'configuration' => [
          'activity' => $activity,
          'object' => $object,
          'target_bundle' => $bundle,
          'target_entity_type_id' => $target_entity_type_id,
          'field_mapping' => [
            [
              'field_name' => 'created',
              'property' => 'published',
            ],
            [
              'field_name' => 'body',
              'property' => 'content',
            ],
          ],
        ],
      ],
    ];

    if (!empty($mapping)) {
      foreach ($mapping as $field_name => $property) {
        $values['plugin']['configuration']['field_mapping'][] = [
          'field_name' => $field_name,
          'property' => $property,
        ];
      }
    }

    $entity = \Drupal::entityTypeManager()->getStorage('activitypub_type')->create($values);
    $entity->save();
  }

  /**
   * Enables an ActivityPub type.
   *
   * @param $type
   * @param bool $status
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function setTypeStatus($type, bool $status = TRUE) {
    /** @var \Drupal\activitypub\Entity\ActivityPubTypeInterface $entity */
    $entity = \Drupal::entityTypeManager()->getStorage('activitypub_type')->load($type);
    $entity->setStatus($status)->save();
  }

  /**
   * Generate dummy Activity for a user id.
   *
   * @param $uid
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createActivity($uid) {
    $values = [
      'status' => TRUE,
      'uid' => $uid,
      'collection' => ActivityPubActivityInterface::INBOX,
      'type' => 'Type',
      'actor' => 'actor',
      'object' => 'object',
    ];
    $entity = \Drupal::entityTypeManager()->getStorage('activitypub_activity')->create($values);
    $entity->save();
  }

  /**
   * Get resource url.
   *
   * @param $account
   * @param $addAcctUriScheme
   *
   * @return string.
   */
  protected function getResourceUrl($account, $addAcctUriScheme = TRUE) {
    $base_url = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
    return ($addAcctUriScheme ? 'acct:' : '') . $account . '@' . str_replace(['http://', 'https://'], '', $base_url);
  }

  /**
   * Get a payload.
   *
   * @param string $actor
   * @param int $id
   * @param string $content
   *
   * @return array
   */
  protected function getPayload(string $actor = ACTIVITYPUB_TEST_USER, int $id = 1, string $content = 'My first message') {
    return [
      'type' => 'Create',
      '@context' => 'https://www.w3.org/ns/activitystreams',
      'id' => 'https://example.com/first-post/' . $id,
      'actor' => $actor,
      'to' => ['https://www.w3.org/ns/activitystreams#Public'],
      'object' => [
        'type' => 'Note',
        'id' => 'https://example.com/first-post/' . $id,
        'to' => ['https://www.w3.org/ns/activitystreams#Public'],
        'attributedTo' => $actor,
        'published' => '2020-03-12T09:27:03Z',
        'content' => $content,
      ],
    ];
  }

  /**
   * Follow a user.
   *
   * @param $uid
   * @param $actor
   * @param $object
   * @param string $collection
   * @param string $type
   * @param null $entity_type_id
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function followUserProgrammatically($uid, $actor, $object, string $collection = ActivityPubActivityInterface::OUTBOX, string $type = 'Follow', $entity_type_id = NULL) {
    /** @var \Drupal\activitypub\Entity\Storage\ActivityPubActorStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage('activitypub_activity');

    $follow_values = [
      'status' => 1,
      'uid' => $uid,
      'actor' => $actor,
      'object' => $object,
      'type' => $type,
      'collection' => $collection,
    ];

    if ($entity_type_id) {
      $follow_values['entity_id'] = $uid;
      $follow_values['entity_type_id'] = $entity_type_id;
    }

    $f = $storage->create($follow_values);
    $f->save();
  }

  /**
   * Add link field.
   */
  protected function drupalAddLinkField($type) {
    $this->drupalLogin($this->adminUser);
    $edit = ['new_storage_type' => 'link'];
    $this->drupalGet('admin/structure/types/manage/' . $type . '/fields/add-field');
    $this->submitForm($edit, 'Continue');
    $edit = ['label' => 'Link field', 'field_name' => 'link'];
    $this->submitForm($edit, 'Continue');
    $this->submitForm([], 'Save settings');
    $this->drupalLogout();
  }

  /**
   * Send inbox request.
   *
   * @param $url
   * @param $payload
   *
   * @return \Psr\Http\Message\ResponseInterface|null
   */
  protected function sendInboxRequest($url, $payload) {

    $headers = [
      'accept' => 'application/activity+json',
    ];

    try {
      $response = $this->httpClient->post($url, ['json' => $payload, 'headers' => $headers]);
    }
    catch (RequestException | GuzzleException $e) {
      $response = $e->getResponse();
    }

    return $response;
  }

  /**
   * Runs the inbox queue.
   */
  protected function runInboxQueue() {
    if (\Drupal::config('activitypub.settings')->get('process_inbox_handler') == 'drush') {
      \Drupal::service('activitypub.process.client')->handleInboxQueue();
    }
  }

  /**
   * Runs the complete outbox queue.
   */
  protected function runOutboxQueue() {
    if (\Drupal::config('activitypub.settings')->get('process_outbox_handler') == 'drush') {
      \Drupal::service('activitypub.process.client')->prepareOutboxQueue();
      \Drupal::service('activitypub.process.client')->handleOutboxQueue();
    }
  }

  /**
   * Check outbox.
   *
   * @param $outbox
   * @param $page
   * @param $assert_session
   * @param $count
   */
  protected function checkOutbox($outbox, $page, $assert_session, $count) {
    $this->drupalGet($outbox);
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($count, $content->totalItems);
    $this->drupalGet($content->first);
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($count, count($content->orderedItems));
  }

  /**
   * Clear queue.
   *
   * @param $queue_name
   */
  protected function clearQueue($queue_name) {
    \Drupal::database()->delete('queue')->condition('name', $queue_name)->execute();
  }

  /**
   * Get queued items.
   *
   * @param bool $dump
   *
   * @return array
   */
  protected function getQueuedItems(bool $dump = FALSE) {
    $items = [];
    $records = \Drupal::database()->select('queue', 'q')->fields('q')->orderBy('name', 'DESC');
    foreach ($records->execute() as $r) {
      $items[] = $r;
      if ($dump) {
        activitypub_test_dump_debug(print_r($r, 1), 'queued-items');
      }
    }
    return $items;
  }

  /**
   * Dump watchdog helper.
   */
  protected function dumpWatchdog() {
    $messages = \Drupal::database()->select('watchdog', 'w')
      ->fields('w')
      ->orderBy('wid', 'DESC')
      ->execute()
      ->fetchAll();
    foreach ($messages as $message) {
      activitypub_test_dump_debug(print_r($message, 1), 'watchdog');
    }
  }

  /**
   * Dump activities helper.
   *
   * @param bool $tiny
   * @param string $suffix
   */
  protected function dumpActivities(bool $tiny = FALSE, string $suffix = '') {
    try {
      /** @var \Drupal\activitypub\Entity\ActivityPubActivityInterface $activity */
      $dump = [];
      $tiny_dump = [];
      foreach (\Drupal::entityTypeManager()->getStorage('activitypub_activity')->loadMultiple() as $activity) {
        if ($tiny) {
          $properties = [];
          $properties[] = 'aid:' . $activity->id();
          $properties[] = 'uid: ' . $activity->getOwnerId();
          $properties[] = $activity->getType();
          $properties[] = $activity->getCollection();
          $properties[] = $activity->getActor();
          $properties[] = $activity->getObject();
          $tiny_dump[] = implode("\n", $properties);
        }
        else {
          $dump[] = print_r($activity->toArray(), 1) . "\n";
        }
      }
      activitypub_test_dump_debug('---------------------------------------', 'activities' . $suffix);
      activitypub_test_dump_debug(($tiny ? implode("\n", $tiny_dump) : implode("\n", $dump)), 'activities' . $suffix);
    }
    catch (\Exception $ignored) {}
  }

  /**
   * Flush cache bins.
   *
   * I don't understand why the cache is not cleared in the test because it
   * works fine manually.
   */
  protected function flushBins() {
    $module_handler = \Drupal::moduleHandler();
    $module_handler->invokeAll('cache_flush');
    foreach (Cache::getBins() as $cache_backend) {
      $cache_backend->deleteAll();
    }
  }

  /**
   * Follow a user.
   *
   * @param $actor
   * @param $object
   * @param int $actor_nr
   * @param bool $run_queue
   */
  protected function followUser($actor, $object, $actor_nr = 1, $run_queue = FALSE) {
    if ($actor_nr == 2) {
      $this->drupalLogin($this->authenticatedUserTwo);
    }
    else {
      $this->drupalLogin($this->authenticatedUserOne);
    }
    $edit = [
      'config_id' => 'follow',
      'actor' => $actor,
      'object' => $object,
      'type' => 'Follow',
      'collection' => 'outbox',
      'status' => 0,
    ];
    $this->drupalGet('activitypub/add');
    $this->submitForm($edit, 'Save');
    $this->drupalLogout();

    if ($run_queue) {
      $this->runOutboxQueue();
    }
  }

  /**
   * View activities of user 1 and 2.
   */
  protected function viewActivityOverview() {
    $this->drupalLogin($this->authenticatedUserOne);
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub');
    $this->drupalLogout();
    $this->drupalLogin($this->authenticatedUserTwo);
    $this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub');
    $this->drupalLogout();
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/' . $this->accountNameOne . '/outbox', ['query' => ['page' => 0]]);
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/' . $this->accountNameOne . '/following', ['query' => ['page' => 0]]);
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/' . $this->accountNameOne . '/followers', ['query' => ['page' => 0]]);
    $this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub/' . $this->accountNameTwo . '/following', ['query' => ['page' => 0]]);
    $this->drupalGet('user/' . $this->authenticatedUserTwo->id() . '/activitypub/' . $this->accountNameTwo . '/followers', ['query' => ['page' => 0]]);
  }

  /**
   * Set the outbox handler.
   */
  protected function setOutboxHandler() {
    $this->drupalLogin($this->adminUser);
    $edit = [
      'process_outbox_handler' => 'drush',
    ];
    $this->drupalGet('admin/config/services/activitypub');
    $this->submitForm($edit, 'Save configuration');
    $this->drupalLogout();
  }

  /**
   * Set the inbox handler.
   */
  protected function setInboxHandler() {
    $this->drupalLogin($this->adminUser);
    $edit = [
      'process_inbox_handler' => 'drush',
    ];
    $this->drupalGet('admin/config/services/activitypub');
    $this->submitForm($edit, 'Save configuration');
    $this->drupalLogout();
  }

  /**
   * Set the user id of the site-wide actor.
   */
  protected function setSitewideActor()
  {
    $this->drupalLogin($this->adminUser);
    $edit = [
      'site_wide_uid' => $this->authenticatedUserOne->id(),
    ];
    $this->drupalGet('admin/config/services/activitypub');
    $this->submitForm($edit, 'Save configuration');
    $this->drupalLogout();
  }

  /**
   * Assert the number of items.
   *
   * @param $total
   * @param $assert_session
   * @param $page
   */
  protected function assertOutboxItems($total, $assert_session, $page) {
    $this->drupalGet('user/' . $this->authenticatedUserOne->id() . '/activitypub/' . $this->accountNameOne . '/outbox', ['query' => ['page' => 0]]);
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($total, $content->totalItems);
    self::assertEquals($total, count($content->orderedItems));
  }

  /**
   * Assert followers and following.
   *
   * @param $id
   * @param $name
   * @param $followers
   * @param $following
   * @param $followers_urls
   * @param $following_urls
   * @param $assert_session
   * @param $page
   */
  protected function assertFollowersAndFollowing($id, $name, $followers, $following, $followers_urls, $following_urls, $assert_session, $page) {
    $this->drupalGet('user/' . $id . '/activitypub/' . $name . '/followers');
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($followers, $content->totalItems);

    $this->drupalGet('user/' . $id . '/activitypub/' . $name . '/followers', ['query' => ['page' => 0]]);
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($followers, $content->totalItems);
    self::assertEquals($followers, count($content->orderedItems));

    if (is_array($followers_urls)) {
      foreach ($followers_urls as $u) {
        self::assertTrue(in_array($u, $content->orderedItems));
      }
    }
    elseif (!empty($followers_urls)) {
      self::assertTrue(in_array($followers_urls, $content->orderedItems));
    }

    $this->drupalGet('user/' . $id . '/activitypub/' . $name . '/following');
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($following, $content->totalItems);

    $this->drupalGet('user/' . $id . '/activitypub/' . $name . '/following', ['query' => ['page' => 0]]);
    $assert_session->statusCodeEquals(200);
    $content = json_decode($page->getContent());
    self::assertEquals($following, $content->totalItems);
    self::assertEquals($following, count($content->orderedItems));

    if (is_array($following_urls)) {
      foreach ($following_urls as $u) {
        self::assertTrue(in_array($u, $content->orderedItems));
      }
    }
    elseif (!empty($following_urls)) {
      self::assertTrue(in_array($following_urls, $content->orderedItems));
    }
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc