AggregatorTestBase.php 12.1 KB
Newer Older
1 2 3 4
<?php

namespace Drupal\aggregator\Tests;

5
use Drupal\aggregator\Entity\Feed;
6
use Drupal\Component\Utility\Html;
7
use Drupal\simpletest\WebTestBase;
8
use Drupal\aggregator\FeedInterface;
9 10

/**
11
 * Defines a base class for testing the Aggregator module.
12
 */
13
abstract class AggregatorTestBase extends WebTestBase {
14

15 16 17 18 19 20 21
  /**
   * A user with permission to administer feeds and create content.
   *
   * @var \Drupal\user\Entity\User
   */
  protected $adminUser;

22
  /**
23
   * Modules to install.
24 25 26
   *
   * @var array
   */
27
  public static $modules = ['block', 'node', 'aggregator', 'aggregator_test', 'views'];
28

29 30 31
  /**
   * {@inheritdoc}
   */
32
  protected function setUp() {
33
    parent::setUp();
34 35 36 37 38 39

    // Create an Article node type.
    if ($this->profile != 'standard') {
      $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
    }

40 41
    $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer news feeds', 'access news feeds', 'create article content'));
    $this->drupalLogin($this->adminUser);
42
    $this->drupalPlaceBlock('local_tasks_block');
43 44 45
  }

  /**
46 47
   * Creates an aggregator feed.
   *
48
   * This method simulates the form submission on path aggregator/sources/add.
49
   *
50
   * @param string $feed_url
51 52
   *   (optional) If given, feed will be created with this URL, otherwise
   *   /rss.xml will be used. Defaults to NULL.
53 54
   * @param array $edit
   *   Array with additional form fields.
55
   *
56
   * @return \Drupal\aggregator\FeedInterface
57 58 59 60
   *   Full feed object if possible.
   *
   * @see getFeedEditArray()
   */
61
  public function createFeed($feed_url = NULL, array $edit = array()) {
62
    $edit = $this->getFeedEditArray($feed_url, $edit);
63
    $this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
64 65 66 67 68
    $this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));

    // Verify that the creation message contains a link to a feed.
    $view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
    $this->assert(isset($view_link), 'The message area contains a link to a feed');
69

70
    $fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']))->fetchField();
71
    $this->assertTrue(!empty($fid), 'The feed found in database.');
72
    return Feed::load($fid);
73 74 75
  }

  /**
76
   * Deletes an aggregator feed.
77
   *
78
   * @param \Drupal\aggregator\FeedInterface $feed
79 80
   *   Feed object representing the feed.
   */
81
  public function deleteFeed(FeedInterface $feed) {
82
    $this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', array(), t('Delete'));
83
    $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->label())), 'Feed deleted successfully.');
84 85 86
  }

  /**
87
   * Returns a randomly generated feed edit array.
88
   *
89
   * @param string $feed_url
90 91
   *   (optional) If given, feed will be created with this URL, otherwise
   *   /rss.xml will be used. Defaults to NULL.
92 93
   * @param array $edit
   *   Array with additional form fields.
94
   *
95
   * @return array
96 97
   *   A feed array.
   */
98
  public function getFeedEditArray($feed_url = NULL, array $edit = array()) {
99
    $feed_name = $this->randomMachineName(10);
100
    if (!$feed_url) {
101
      $feed_url = \Drupal::url('view.frontpage.feed_1', array(), array(
102 103 104 105
        'query' => array('feed' => $feed_name),
        'absolute' => TRUE,
      ));
    }
106
    $edit += array(
107 108
      'title[0][value]' => $feed_name,
      'url[0][value]' => $feed_url,
109 110 111 112 113
      'refresh' => '900',
    );
    return $edit;
  }

114 115 116 117 118 119 120 121 122
  /**
   * Returns a randomly generated feed edit object.
   *
   * @param string $feed_url
   *   (optional) If given, feed will be created with this URL, otherwise
   *   /rss.xml will be used. Defaults to NULL.
   * @param array $values
   *   (optional) Default values to initialize object properties with.
   *
123
   * @return \Drupal\aggregator\FeedInterface
124 125
   *   A feed object.
   */
126
  public function getFeedEditObject($feed_url = NULL, array $values = array()) {
127
    $feed_name = $this->randomMachineName(10);
128
    if (!$feed_url) {
129
      $feed_url = \Drupal::url('view.frontpage.feed_1', array(
130 131 132 133 134 135 136 137 138
        'query' => array('feed' => $feed_name),
        'absolute' => TRUE,
      ));
    }
    $values += array(
      'title' => $feed_name,
      'url' => $feed_url,
      'refresh' => '900',
    );
139
    return Feed::create($values);
140 141
  }

142
  /**
143
   * Returns the count of the randomly created feed array.
144
   *
145
   * @return int
146 147
   *   Number of feed items on default feed created by createFeed().
   */
148
  public function getDefaultFeedItemCount() {
149
    // Our tests are based off of rss.xml, so let's find out how many elements should be related.
150
    $feed_count = db_query_range('SELECT COUNT(DISTINCT nid) FROM {node_field_data} n WHERE n.promote = 1 AND n.status = 1', 0, $this->config('system.rss')->get('items.limit'))->fetchField();
151 152 153 154
    return $feed_count > 10 ? 10 : $feed_count;
  }

  /**
155 156 157 158
   * Updates the feed items.
   *
   * This method simulates a click to
   * admin/config/services/aggregator/update/$fid.
159
   *
160
   * @param \Drupal\aggregator\FeedInterface $feed
161
   *   Feed object representing the feed.
162 163
   * @param int|null $expected_count
   *   Expected number of feed items. If omitted no check will happen.
164
   */
165
  public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
166
    // First, let's ensure we can get to the rss xml.
167
    $this->drupalGet($feed->getUrl());
168
    $this->assertResponse(200, format_string(':url is reachable.', array(':url' => $feed->getUrl())));
169 170

    // Attempt to access the update link directly without an access token.
171
    $this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());
172 173 174 175
    $this->assertResponse(403);

    // Refresh the feed (simulated link click).
    $this->drupalGet('admin/config/services/aggregator');
176
    $this->clickLink('Update items');
177 178

    // Ensure we have the right number of items.
179
    $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()));
180 181 182 183
    $feed->items = array();
    foreach ($result as $item) {
      $feed->items[] = $item->iid;
    }
184 185 186

    if ($expected_count !== NULL) {
      $feed->item_count = count($feed->items);
187
      $this->assertEqual($expected_count, $feed->item_count, format_string('Total items in feed equal to the total items in database (@val1 != @val2)', array('@val1' => $expected_count, '@val2' => $feed->item_count)));
188
    }
189 190 191
  }

  /**
192
   * Confirms an item removal from a feed.
193
   *
194
   * @param \Drupal\aggregator\FeedInterface $feed
195 196
   *   Feed object representing the feed.
   */
197
  public function deleteFeedItems(FeedInterface $feed) {
198 199
    $this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), array(), t('Delete items'));
    $this->assertRaw(t('The news items from %title have been deleted.', array('%title' => $feed->label())), 'Feed items deleted.');
200 201 202
  }

  /**
203
   * Adds and deletes feed items and ensure that the count is zero.
204
   *
205
   * @param \Drupal\aggregator\FeedInterface $feed
206
   *   Feed object representing the feed.
207
   * @param int $expected_count
208 209
   *   Expected number of feed items.
   */
210
  public function updateAndDelete(FeedInterface $feed, $expected_count) {
211
    $this->updateFeedItems($feed, $expected_count);
212
    $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
213
    $this->assertTrue($count);
214
    $this->deleteFeedItems($feed);
215
    $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->id()))->fetchField();
216 217 218 219
    $this->assertTrue($count == 0);
  }

  /**
220
   * Checks whether the feed name and URL are unique.
221
   *
222
   * @param string $feed_name
223
   *   String containing the feed name to check.
224
   * @param string $feed_url
225
   *   String containing the feed url to check.
226
   *
227
   * @return bool
228 229
   *   TRUE if feed is unique.
   */
230
  public function uniqueFeed($feed_name, $feed_url) {
231 232 233 234 235
    $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField();
    return (1 == $result);
  }

  /**
236
   * Creates a valid OPML file from an array of feeds.
237
   *
238
   * @param array $feeds
239
   *   An array of feeds.
240
   *
241
   * @return string
242 243
   *   Path to valid OPML file.
   */
244
  public function getValidOpml(array $feeds) {
245 246
    // Properly escape URLs so that XML parsers don't choke on them.
    foreach ($feeds as &$feed) {
247
      $feed['url[0][value]'] = Html::escape($feed['url[0][value]']);
248 249 250 251 252 253 254 255 256
    }
    /**
     * Does not have an XML declaration, must pass the parser.
     */
    $opml = <<<EOF
<opml version="1.0">
  <head></head>
  <body>
    <!-- First feed to be imported. -->
257
    <outline text="{$feeds[0]['title[0][value]']}" xmlurl="{$feeds[0]['url[0][value]']}" />
258 259

    <!-- Second feed. Test string delimitation and attribute order. -->
260
    <outline xmlurl='{$feeds[1]['url[0][value]']}' text='{$feeds[1]['title[0][value]']}'/>
261 262

    <!-- Test for duplicate URL and title. -->
263 264
    <outline xmlurl="{$feeds[0]['url[0][value]']}" text="Duplicate URL"/>
    <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title[0][value]']}"/>
265 266

    <!-- Test that feeds are only added with required attributes. -->
267 268
    <outline text="{$feeds[2]['title[0][value]']}" />
    <outline xmlurl="{$feeds[2]['url[0][value]']}" />
269 270 271 272 273 274 275 276 277
  </body>
</opml>
EOF;

    $path = 'public://valid-opml.xml';
    return file_unmanaged_save_data($opml, $path);
  }

  /**
278
   * Creates an invalid OPML file.
279
   *
280
   * @return string
281 282
   *   Path to invalid OPML file.
   */
283
  public function getInvalidOpml() {
284 285 286 287 288 289 290 291 292 293 294
    $opml = <<<EOF
<opml>
  <invalid>
</opml>
EOF;

    $path = 'public://invalid-opml.xml';
    return file_unmanaged_save_data($opml, $path);
  }

  /**
295
   * Creates a valid but empty OPML file.
296
   *
297
   * @return string
298 299
   *   Path to empty OPML file.
   */
300
  public function getEmptyOpml() {
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
    $opml = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<opml version="1.0">
  <head></head>
  <body>
    <outline text="Sample text" />
    <outline text="Sample text" url="Sample URL" />
  </body>
</opml>
EOF;

    $path = 'public://empty-opml.xml';
    return file_unmanaged_save_data($opml, $path);
  }

316 317 318 319 320 321 322
  /**
   * Returns a example RSS091 feed.
   *
   * @return string
   *   Path to the feed.
   */
  public function getRSS091Sample() {
323
    return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_rss091.xml';
324 325
  }

326 327 328 329 330 331 332
  /**
   * Returns a example Atom feed.
   *
   * @return string
   *   Path to the feed.
   */
  public function getAtomSample() {
333 334
    // The content of this sample ATOM feed is based directly off of the
    // example provided in RFC 4287.
335
    return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_atom.xml';
336 337
  }

338 339 340 341 342 343 344
  /**
   * Returns a example feed.
   *
   * @return string
   *   Path to the feed.
   */
  public function getHtmlEntitiesSample() {
345
    return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_title_entities.xml';
346 347 348 349 350
  }

  /**
   * Creates sample article nodes.
   *
351
   * @param int $count
352
   *   (optional) The number of nodes to generate. Defaults to five.
353
   */
354
  public function createSampleNodes($count = 5) {
355 356 357
    // Post $count article nodes.
    for ($i = 0; $i < $count; $i++) {
      $edit = array();
358 359
      $edit['title[0][value]'] = $this->randomMachineName();
      $edit['body[0][value]'] = $this->randomMachineName();
360
      $this->drupalPostForm('node/add/article', $edit, t('Save'));
361 362
    }
  }
363 364 365 366

  /**
   * Enable the plugins coming with aggregator_test module.
   */
367
  public function enableTestPlugins() {
368
    $this->config('aggregator.settings')
369 370 371 372
      ->set('fetcher', 'aggregator_test_fetcher')
      ->set('parser', 'aggregator_test_parser')
      ->set('processors', array(
        'aggregator_test_processor' => 'aggregator_test_processor',
373
        'aggregator' => 'aggregator',
374 375 376
      ))
      ->save();
  }
377

378
}