Drupal PHP Cross Reference Content Management Systems

Source: /modules/aggregator/aggregator.test - 970 lines - 37103 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /**
   4   * @file
   5   * Tests for aggregator.module.
   6   */
   7  
   8  class AggregatorTestCase extends DrupalWebTestCase {
   9    function setUp() {
  10      parent::setUp('aggregator', 'aggregator_test');
  11      $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content'));
  12      $this->drupalLogin($web_user);
  13    }
  14  
  15    /**
  16     * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed).
  17     *
  18     * @param $feed_url
  19     *   If given, feed will be created with this URL, otherwise /rss.xml will be used.
  20     * @return $feed
  21     *   Full feed object if possible.
  22     *
  23     * @see getFeedEditArray()
  24     */
  25    function createFeed($feed_url = NULL) {
  26      $edit = $this->getFeedEditArray($feed_url);
  27      $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save'));
  28      $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), format_string('The feed !name has been added.', array('!name' => $edit['title'])));
  29  
  30      $feed = db_query("SELECT *  FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetch();
  31      $this->assertTrue(!empty($feed), 'The feed found in database.');
  32      return $feed;
  33    }
  34  
  35    /**
  36     * Delete an aggregator feed.
  37     *
  38     * @param $feed
  39     *   Feed object representing the feed.
  40     */
  41    function deleteFeed($feed) {
  42      $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, array(), t('Delete'));
  43      $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), 'Feed deleted successfully.');
  44    }
  45  
  46    /**
  47     * Return a randomly generated feed edit array.
  48     *
  49     * @param $feed_url
  50     *   If given, feed will be created with this URL, otherwise /rss.xml will be used.
  51     * @return
  52     *   A feed array.
  53     */
  54    function getFeedEditArray($feed_url = NULL) {
  55      $feed_name = $this->randomName(10);
  56      if (!$feed_url) {
  57        $feed_url = url('rss.xml', array(
  58          'query' => array('feed' => $feed_name),
  59          'absolute' => TRUE,
  60        ));
  61      }
  62      $edit = array(
  63        'title' => $feed_name,
  64        'url' => $feed_url,
  65        'refresh' => '900',
  66      );
  67      return $edit;
  68    }
  69  
  70    /**
  71     * Return the count of the randomly created feed array.
  72     *
  73     * @return
  74     *   Number of feed items on default feed created by createFeed().
  75     */
  76    function getDefaultFeedItemCount() {
  77      // Our tests are based off of rss.xml, so let's find out how many elements should be related.
  78      $feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, variable_get('feed_default_items', 10))->fetchField();
  79      return $feed_count > 10 ? 10 : $feed_count;
  80    }
  81  
  82    /**
  83     * Update feed items (simulate click to admin/config/services/aggregator/update/$fid).
  84     *
  85     * @param $feed
  86     *   Feed object representing the feed.
  87     * @param $expected_count
  88     *   Expected number of feed items.
  89     */
  90    function updateFeedItems(&$feed, $expected_count) {
  91      // First, let's ensure we can get to the rss xml.
  92      $this->drupalGet($feed->url);
  93      $this->assertResponse(200, format_string('!url is reachable.', array('!url' => $feed->url)));
  94  
  95      // Attempt to access the update link directly without an access token.
  96      $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid);
  97      $this->assertResponse(403);
  98  
  99      // Refresh the feed (simulated link click).
 100      $this->drupalGet('admin/config/services/aggregator');
 101      $this->clickLink('update items');
 102  
 103      // Ensure we have the right number of items.
 104      $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid));
 105      $items = array();
 106      $feed->items = array();
 107      foreach ($result as $item) {
 108        $feed->items[] = $item->iid;
 109      }
 110      $feed->item_count = count($feed->items);
 111      $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)));
 112    }
 113  
 114    /**
 115     * Confirm item removal from a feed.
 116     *
 117     * @param $feed
 118     *   Feed object representing the feed.
 119     */
 120    function removeFeedItems($feed) {
 121      $this->drupalPost('admin/config/services/aggregator/remove/' . $feed->fid, array(), t('Remove items'));
 122      $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), 'Feed items removed.');
 123    }
 124  
 125    /**
 126     * Add and remove feed items and ensure that the count is zero.
 127     *
 128     * @param $feed
 129     *   Feed object representing the feed.
 130     * @param $expected_count
 131     *   Expected number of feed items.
 132     */
 133    function updateAndRemove($feed, $expected_count) {
 134      $this->updateFeedItems($feed, $expected_count);
 135      $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
 136      $this->assertTrue($count);
 137      $this->removeFeedItems($feed);
 138      $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
 139      $this->assertTrue($count == 0);
 140    }
 141  
 142    /**
 143     * Pull feed categories from aggregator_category_feed table.
 144     *
 145     * @param $feed
 146     *   Feed object representing the feed.
 147     */
 148    function getFeedCategories($feed) {
 149      // add the categories to the feed so we can use them
 150      $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->fid));
 151      foreach ($result as $category) {
 152        $feed->categories[] = $category->cid;
 153      }
 154    }
 155  
 156    /**
 157     * Pull categories from aggregator_category table.
 158     */
 159    function getCategories() {
 160      $categories = array();
 161      $result = db_query('SELECT * FROM {aggregator_category}');
 162      foreach ($result as $category) {
 163        $categories[$category->cid] = $category;
 164      }
 165      return $categories;
 166    }
 167  
 168  
 169    /**
 170     * Check if the feed name and URL is unique.
 171     *
 172     * @param $feed_name
 173     *   String containing the feed name to check.
 174     * @param $feed_url
 175     *   String containing the feed URL to check.
 176     * @return
 177     *   TRUE if feed is unique.
 178     */
 179    function uniqueFeed($feed_name, $feed_url) {
 180      $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField();
 181      return (1 == $result);
 182    }
 183  
 184    /**
 185     * Create a valid OPML file from an array of feeds.
 186     *
 187     * @param $feeds
 188     *   An array of feeds.
 189     * @return
 190     *   Path to valid OPML file.
 191     */
 192    function getValidOpml($feeds) {
 193      // Properly escape URLs so that XML parsers don't choke on them.
 194      foreach ($feeds as &$feed) {
 195        $feed['url'] = htmlspecialchars($feed['url']);
 196      }
 197      /**
 198       * Does not have an XML declaration, must pass the parser.
 199       */
 200      $opml = <<<EOF
 201  <opml version="1.0">
 202    <head></head>
 203    <body>
 204      <!-- First feed to be imported. -->
 205      <outline text="{$feeds[0]['title']}" xmlurl="{$feeds[0]['url']}" />
 206  
 207      <!-- Second feed. Test string delimitation and attribute order. -->
 208      <outline xmlurl='{$feeds[1]['url']}' text='{$feeds[1]['title']}'/>
 209  
 210      <!-- Test for duplicate URL and title. -->
 211      <outline xmlurl="{$feeds[0]['url']}" text="Duplicate URL"/>
 212      <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title']}"/>
 213  
 214      <!-- Test that feeds are only added with required attributes. -->
 215      <outline text="{$feeds[2]['title']}" />
 216      <outline xmlurl="{$feeds[2]['url']}" />
 217    </body>
 218  </opml>
 219  EOF;
 220  
 221      $path = 'public://valid-opml.xml';
 222      return file_unmanaged_save_data($opml, $path);
 223    }
 224  
 225    /**
 226     * Create an invalid OPML file.
 227     *
 228     * @return
 229     *   Path to invalid OPML file.
 230     */
 231    function getInvalidOpml() {
 232      $opml = <<<EOF
 233  <opml>
 234    <invalid>
 235  </opml>
 236  EOF;
 237  
 238      $path = 'public://invalid-opml.xml';
 239      return file_unmanaged_save_data($opml, $path);
 240    }
 241  
 242    /**
 243     * Create a valid but empty OPML file.
 244     *
 245     * @return
 246     *   Path to empty OPML file.
 247     */
 248    function getEmptyOpml() {
 249      $opml = <<<EOF
 250  <?xml version="1.0" encoding="utf-8"?>
 251  <opml version="1.0">
 252    <head></head>
 253    <body>
 254      <outline text="Sample text" />
 255      <outline text="Sample text" url="Sample URL" />
 256    </body>
 257  </opml>
 258  EOF;
 259  
 260      $path = 'public://empty-opml.xml';
 261      return file_unmanaged_save_data($opml, $path);
 262    }
 263  
 264    function getRSS091Sample() {
 265      return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_rss091.xml';
 266    }
 267  
 268    function getAtomSample() {
 269      // The content of this sample ATOM feed is based directly off of the
 270      // example provided in RFC 4287.
 271      return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_atom.xml';
 272    }
 273  
 274    /**
 275     * Creates sample article nodes.
 276     *
 277     * @param $count
 278     *   (optional) The number of nodes to generate.
 279     */
 280    function createSampleNodes($count = 5) {
 281      $langcode = LANGUAGE_NONE;
 282      // Post $count article nodes.
 283      for ($i = 0; $i < $count; $i++) {
 284        $edit = array();
 285        $edit['title'] = $this->randomName();
 286        $edit["body[$langcode][0][value]"] = $this->randomName();
 287        $this->drupalPost('node/add/article', $edit, t('Save'));
 288      }
 289    }
 290  }
 291  
 292  /**
 293   * Tests aggregator configuration settings.
 294   */
 295  class AggregatorConfigurationTestCase extends AggregatorTestCase {
 296    public static function getInfo() {
 297      return array(
 298        'name' => 'Aggregator configuration',
 299        'description' => 'Test aggregator settings page.',
 300        'group' => 'Aggregator',
 301      );
 302    }
 303  
 304    /**
 305     * Tests the settings form to ensure the correct default values are used.
 306     */
 307    function testSettingsPage() {
 308      $edit = array(
 309        'aggregator_allowed_html_tags' => '<a>',
 310        'aggregator_summary_items' => 10,
 311        'aggregator_clear' => 3600,
 312        'aggregator_category_selector' => 'select',
 313        'aggregator_teaser_length' => 200,
 314      );
 315      $this->drupalPost('admin/config/services/aggregator/settings', $edit, t('Save configuration'));
 316      $this->assertText(t('The configuration options have been saved.'));
 317  
 318      foreach ($edit as $name => $value) {
 319        $this->assertFieldByName($name, $value, format_string('"@name" has correct default value.', array('@name' => $name)));
 320      }
 321    }
 322  }
 323  
 324  class AddFeedTestCase extends AggregatorTestCase {
 325    public static function getInfo() {
 326      return array(
 327        'name' => 'Add feed functionality',
 328        'description' => 'Add feed test.',
 329        'group' => 'Aggregator'
 330      );
 331    }
 332  
 333    /**
 334     * Create a feed, ensure that it is unique, check the source, and delete the feed.
 335     */
 336    function testAddFeed() {
 337      $feed = $this->createFeed();
 338  
 339      // Check feed data.
 340      $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/add/feed', array('absolute' => TRUE)), 'Directed to correct url.');
 341      $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The feed is unique.');
 342  
 343      // Check feed source.
 344      $this->drupalGet('aggregator/sources/' . $feed->fid);
 345      $this->assertResponse(200, 'Feed source exists.');
 346      $this->assertText($feed->title, 'Page title');
 347      $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize');
 348      $this->assertResponse(200, 'Feed categorization page exists.');
 349  
 350      // Delete feed.
 351      $this->deleteFeed($feed);
 352    }
 353  
 354    /**
 355     * Tests feeds with very long URLs.
 356     */
 357    function testAddLongFeed() {
 358      // Create a feed with a URL of > 255 characters.
 359      $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb";
 360      $feed = $this->createFeed($long_url);
 361  
 362      // Create a second feed of > 255 characters, where the only difference is
 363      // after the 255th character.
 364      $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889";
 365      $feed_2 = $this->createFeed($long_url_2);
 366  
 367      // Check feed data.
 368      $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The first long URL feed is unique.');
 369      $this->assertTrue($this->uniqueFeed($feed_2->title, $feed_2->url), 'The second long URL feed is unique.');
 370  
 371      // Check feed source.
 372      $this->drupalGet('aggregator/sources/' . $feed->fid);
 373      $this->assertResponse(200, 'Long URL feed source exists.');
 374      $this->assertText($feed->title, 'Page title');
 375      $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize');
 376      $this->assertResponse(200, 'Long URL feed categorization page exists.');
 377  
 378      // Delete feeds.
 379      $this->deleteFeed($feed);
 380      $this->deleteFeed($feed_2);
 381    }
 382  }
 383  
 384  class CategorizeFeedTestCase extends AggregatorTestCase {
 385    public static function getInfo() {
 386      return array(
 387        'name' => 'Categorize feed functionality',
 388        'description' => 'Categorize feed test.',
 389        'group' => 'Aggregator'
 390      );
 391    }
 392  
 393    /**
 394     * Create a feed and make sure you can add more than one category to it.
 395     */
 396    function testCategorizeFeed() {
 397  
 398      // Create 2 categories.
 399      $category_1 = array('title' => $this->randomName(10), 'description' => '');
 400      $this->drupalPost('admin/config/services/aggregator/add/category', $category_1, t('Save'));
 401      $this->assertRaw(t('The category %title has been added.', array('%title' => $category_1['title'])), format_string('The category %title has been added.', array('%title' => $category_1['title'])));
 402  
 403      $category_2 = array('title' => $this->randomName(10), 'description' => '');
 404      $this->drupalPost('admin/config/services/aggregator/add/category', $category_2, t('Save'));
 405      $this->assertRaw(t('The category %title has been added.', array('%title' => $category_2['title'])), format_string('The category %title has been added.', array('%title' => $category_2['title'])));
 406  
 407      // Get categories from database.
 408      $categories = $this->getCategories();
 409  
 410      // Create a feed and assign 2 categories to it.
 411      $feed = $this->getFeedEditArray();
 412      $feed['block'] = 5;
 413      foreach ($categories as $cid => $category) {
 414        $feed['category'][$cid] = $cid;
 415      }
 416  
 417      // Use aggregator_save_feed() function to save the feed.
 418      aggregator_save_feed($feed);
 419      $db_feed = db_query("SELECT *  FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed['title'], ':url' => $feed['url']))->fetch();
 420  
 421      // Assert the feed has two categories.
 422      $this->getFeedCategories($db_feed);
 423      $this->assertEqual(count($db_feed->categories), 2, 'Feed has 2 categories');
 424    }
 425  }
 426  
 427  class UpdateFeedTestCase extends AggregatorTestCase {
 428    public static function getInfo() {
 429      return array(
 430        'name' => 'Update feed functionality',
 431        'description' => 'Update feed test.',
 432        'group' => 'Aggregator'
 433      );
 434    }
 435  
 436    /**
 437     * Create a feed and attempt to update it.
 438     */
 439    function testUpdateFeed() {
 440      $remamining_fields = array('title', 'url', '');
 441      foreach ($remamining_fields as $same_field) {
 442        $feed = $this->createFeed();
 443  
 444        // Get new feed data array and modify newly created feed.
 445        $edit = $this->getFeedEditArray();
 446        $edit['refresh'] =  1800; // Change refresh value.
 447        if (isset($feed->{$same_field})) {
 448          $edit[$same_field] = $feed->{$same_field};
 449        }
 450        $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, $edit, t('Save'));
 451        $this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), format_string('The feed %name has been updated.', array('%name' => $edit['title'])));
 452  
 453        // Check feed data.
 454        $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/', array('absolute' => TRUE)));
 455        $this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), 'The feed is unique.');
 456  
 457        // Check feed source.
 458        $this->drupalGet('aggregator/sources/' . $feed->fid);
 459        $this->assertResponse(200, 'Feed source exists.');
 460        $this->assertText($edit['title'], 'Page title');
 461  
 462        // Delete feed.
 463        $feed->title = $edit['title']; // Set correct title so deleteFeed() will work.
 464        $this->deleteFeed($feed);
 465      }
 466    }
 467  }
 468  
 469  class RemoveFeedTestCase extends AggregatorTestCase {
 470    public static function getInfo() {
 471      return array(
 472        'name' => 'Remove feed functionality',
 473        'description' => 'Remove feed test.',
 474        'group' => 'Aggregator'
 475      );
 476    }
 477  
 478    /**
 479     * Remove a feed and ensure that all it services are removed.
 480     */
 481    function testRemoveFeed() {
 482      $feed = $this->createFeed();
 483  
 484      // Delete feed.
 485      $this->deleteFeed($feed);
 486  
 487      // Check feed source.
 488      $this->drupalGet('aggregator/sources/' . $feed->fid);
 489      $this->assertResponse(404, 'Deleted feed source does not exists.');
 490  
 491      // Check database for feed.
 492      $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField();
 493      $this->assertFalse($result, 'Feed not found in database');
 494    }
 495  }
 496  
 497  class UpdateFeedItemTestCase extends AggregatorTestCase {
 498    public static function getInfo() {
 499      return array(
 500        'name' => 'Update feed item functionality',
 501        'description' => 'Update feed items from a feed.',
 502        'group' => 'Aggregator'
 503      );
 504    }
 505  
 506    /**
 507     * Test running "update items" from the 'admin/config/services/aggregator' page.
 508     */
 509    function testUpdateFeedItem() {
 510      $this->createSampleNodes();
 511  
 512      // Create a feed and test updating feed items if possible.
 513      $feed = $this->createFeed();
 514      if (!empty($feed)) {
 515        $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
 516        $this->removeFeedItems($feed);
 517      }
 518  
 519      // Delete feed.
 520      $this->deleteFeed($feed);
 521  
 522      // Test updating feed items without valid timestamp information.
 523      $edit = array(
 524        'title' => "Feed without publish timestamp",
 525        'url' => $this->getRSS091Sample(),
 526      );
 527  
 528      $this->drupalGet($edit['url']);
 529      $this->assertResponse(array(200), format_string('URL !url is accessible', array('!url' => $edit['url'])));
 530  
 531      $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save'));
 532      $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), format_string('The feed !name has been added.', array('!name' => $edit['title'])));
 533  
 534      $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject();
 535  
 536      aggregator_refresh($feed);
 537      $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
 538  
 539      // Sleep for 3 second.
 540      sleep(3);
 541      db_update('aggregator_feed')
 542        ->condition('fid', $feed->fid)
 543        ->fields(array(
 544          'checked' => 0,
 545          'hash' => '',
 546          'etag' => '',
 547          'modified' => 0,
 548        ))
 549        ->execute();
 550      aggregator_refresh($feed);
 551  
 552      $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField();
 553      $this->assertTrue($before === $after, format_string('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after)));
 554    }
 555  }
 556  
 557  class RemoveFeedItemTestCase extends AggregatorTestCase {
 558    public static function getInfo() {
 559      return array(
 560        'name' => 'Remove feed item functionality',
 561        'description' => 'Remove feed items from a feed.',
 562        'group' => 'Aggregator'
 563      );
 564    }
 565  
 566    /**
 567     * Test running "remove items" from the 'admin/config/services/aggregator' page.
 568     */
 569    function testRemoveFeedItem() {
 570      // Create a bunch of test feeds.
 571      $feed_urls = array();
 572      // No last-modified, no etag.
 573      $feed_urls[] = url('aggregator/test-feed', array('absolute' => TRUE));
 574      // Last-modified, but no etag.
 575      $feed_urls[] = url('aggregator/test-feed/1', array('absolute' => TRUE));
 576      // No Last-modified, but etag.
 577      $feed_urls[] = url('aggregator/test-feed/0/1', array('absolute' => TRUE));
 578      // Last-modified and etag.
 579      $feed_urls[] = url('aggregator/test-feed/1/1', array('absolute' => TRUE));
 580  
 581      foreach ($feed_urls as $feed_url) {
 582        $feed = $this->createFeed($feed_url);
 583        // Update and remove items two times in a row to make sure that removal
 584        // resets all 'modified' information (modified, etag, hash) and allows for
 585        // immediate update.
 586        $this->updateAndRemove($feed, 4);
 587        $this->updateAndRemove($feed, 4);
 588        $this->updateAndRemove($feed, 4);
 589        // Delete feed.
 590        $this->deleteFeed($feed);
 591      }
 592    }
 593  }
 594  
 595  class CategorizeFeedItemTestCase extends AggregatorTestCase {
 596    public static function getInfo() {
 597      return array(
 598        'name' => 'Categorize feed item functionality',
 599        'description' => 'Test feed item categorization.',
 600        'group' => 'Aggregator'
 601      );
 602    }
 603  
 604    /**
 605     * If a feed has a category, make sure that the children inherit that
 606     * categorization.
 607     */
 608    function testCategorizeFeedItem() {
 609      $this->createSampleNodes();
 610  
 611      // Simulate form submission on "admin/config/services/aggregator/add/category".
 612      $edit = array('title' => $this->randomName(10), 'description' => '');
 613      $this->drupalPost('admin/config/services/aggregator/add/category', $edit, t('Save'));
 614      $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), format_string('The category %title has been added.', array('%title' => $edit['title'])));
 615  
 616      $category = db_query("SELECT * FROM {aggregator_category} WHERE title = :title", array(':title' => $edit['title']))->fetch();
 617      $this->assertTrue(!empty($category), 'The category found in database.');
 618  
 619      $link_path = 'aggregator/categories/' . $category->cid;
 620      $menu_link = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetch();
 621      $this->assertTrue(!empty($menu_link), 'The menu link associated with the category found in database.');
 622  
 623      $feed = $this->createFeed();
 624      db_insert('aggregator_category_feed')
 625        ->fields(array(
 626          'cid' => $category->cid,
 627          'fid' => $feed->fid,
 628        ))
 629        ->execute();
 630      $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
 631      $this->getFeedCategories($feed);
 632      $this->assertTrue(!empty($feed->categories), 'The category found in the feed.');
 633  
 634      // For each category of a feed, ensure feed items have that category, too.
 635      if (!empty($feed->categories) && !empty($feed->items)) {
 636        foreach ($feed->categories as $category) {
 637          $categorized_count = db_select('aggregator_category_item')
 638            ->condition('iid', $feed->items, 'IN')
 639            ->countQuery()
 640            ->execute()
 641            ->fetchField();
 642  
 643          $this->assertEqual($feed->item_count, $categorized_count, 'Total items in feed equal to the total categorized feed items in database');
 644        }
 645      }
 646  
 647      // Delete feed.
 648      $this->deleteFeed($feed);
 649    }
 650  }
 651  
 652  class ImportOPMLTestCase extends AggregatorTestCase {
 653    public static function getInfo() {
 654      return array(
 655        'name' => 'Import feeds from OPML functionality',
 656        'description' => 'Test OPML import.',
 657        'group' => 'Aggregator',
 658      );
 659    }
 660  
 661    /**
 662     * Open OPML import form.
 663     */
 664    function openImportForm() {
 665      db_delete('aggregator_category')->execute();
 666  
 667      $category = $this->randomName(10);
 668      $cid = db_insert('aggregator_category')
 669        ->fields(array(
 670          'title' => $category,
 671          'description' => '',
 672        ))
 673        ->execute();
 674  
 675      $this->drupalGet('admin/config/services/aggregator/add/opml');
 676      $this->assertText('A single OPML document may contain a collection of many feeds.', 'Found OPML help text.');
 677      $this->assertField('files[upload]', 'Found file upload field.');
 678      $this->assertField('remote', 'Found Remote URL field.');
 679      $this->assertField('refresh', 'Found Refresh field.');
 680      $this->assertFieldByName("category[$cid]", $cid, 'Found category field.');
 681    }
 682  
 683    /**
 684     * Submit form filled with invalid fields.
 685     */
 686    function validateImportFormFields() {
 687      $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
 688  
 689      $edit = array();
 690      $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
 691      $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), 'Error if no fields are filled.');
 692  
 693      $path = $this->getEmptyOpml();
 694      $edit = array(
 695        'files[upload]' => $path,
 696        'remote' => file_create_url($path),
 697      );
 698      $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
 699      $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), 'Error if both fields are filled.');
 700  
 701      $edit = array('remote' => 'invalidUrl://empty');
 702      $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
 703      $this->assertText(t('This URL is not valid.'), 'Error if the URL is invalid.');
 704  
 705      $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
 706      $this->assertEqual($before, $after, 'No feeds were added during the three last form submissions.');
 707    }
 708  
 709    /**
 710     * Submit form with invalid, empty and valid OPML files.
 711     */
 712    function submitImportForm() {
 713      $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
 714  
 715      $form['files[upload]'] = $this->getInvalidOpml();
 716      $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import'));
 717      $this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.');
 718  
 719      $edit = array('remote' => file_create_url($this->getEmptyOpml()));
 720      $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
 721      $this->assertText(t('No new feed has been added.'), 'Attempting to load empty OPML from remote URL.');
 722  
 723      $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
 724      $this->assertEqual($before, $after, 'No feeds were added during the two last form submissions.');
 725  
 726      db_delete('aggregator_feed')->execute();
 727      db_delete('aggregator_category')->execute();
 728      db_delete('aggregator_category_feed')->execute();
 729  
 730      $category = $this->randomName(10);
 731      db_insert('aggregator_category')
 732        ->fields(array(
 733          'cid' => 1,
 734          'title' => $category,
 735          'description' => '',
 736        ))
 737        ->execute();
 738  
 739      $feeds[0] = $this->getFeedEditArray();
 740      $feeds[1] = $this->getFeedEditArray();
 741      $feeds[2] = $this->getFeedEditArray();
 742      $edit = array(
 743        'files[upload]' => $this->getValidOpml($feeds),
 744        'refresh'       => '900',
 745        'category[1]'   => $category,
 746      );
 747      $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import'));
 748      $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), 'Verifying that a duplicate URL was identified');
 749      $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), 'Verifying that a duplicate title was identified');
 750  
 751      $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField();
 752      $this->assertEqual($after, 2, 'Verifying that two distinct feeds were added.');
 753  
 754      $feeds_from_db = db_query("SELECT f.title, f.url, f.refresh, cf.cid FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} cf ON f.fid = cf.fid");
 755      $refresh = $category = TRUE;
 756      foreach ($feeds_from_db as $feed) {
 757        $title[$feed->url] = $feed->title;
 758        $url[$feed->title] = $feed->url;
 759        $category = $category && $feed->cid == 1;
 760        $refresh = $refresh && $feed->refresh == 900;
 761      }
 762  
 763      $this->assertEqual($title[$feeds[0]['url']], $feeds[0]['title'], 'First feed was added correctly.');
 764      $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['url'], 'Second feed was added correctly.');
 765      $this->assertTrue($refresh, 'Refresh times are correct.');
 766      $this->assertTrue($category, 'Categories are correct.');
 767    }
 768  
 769    function testOPMLImport() {
 770      $this->openImportForm();
 771      $this->validateImportFormFields();
 772      $this->submitImportForm();
 773    }
 774  }
 775  
 776  class AggregatorCronTestCase extends AggregatorTestCase {
 777    public static function getInfo() {
 778      return array(
 779        'name' => 'Update on cron functionality',
 780        'description' => 'Update feeds on cron.',
 781        'group' => 'Aggregator'
 782      );
 783    }
 784  
 785    /**
 786     * Add feeds update them on cron.
 787     */
 788    public function testCron() {
 789      // Create feed and test basic updating on cron.
 790      global $base_url;
 791      $key = variable_get('cron_key', 'drupal');
 792      $this->createSampleNodes();
 793      $feed = $this->createFeed();
 794      $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
 795      $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
 796      $this->removeFeedItems($feed);
 797      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
 798      $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
 799      $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
 800  
 801      // Test feed locking when queued for update.
 802      $this->removeFeedItems($feed);
 803      db_update('aggregator_feed')
 804        ->condition('fid', $feed->fid)
 805        ->fields(array(
 806          'queued' => REQUEST_TIME,
 807        ))
 808        ->execute();
 809      $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
 810      $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
 811      db_update('aggregator_feed')
 812        ->condition('fid', $feed->fid)
 813        ->fields(array(
 814          'queued' => 0,
 815        ))
 816        ->execute();
 817      $this->drupalGet($base_url . '/cron.php', array('external' => TRUE, 'query' => array('cron_key' => $key)));
 818      $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.');
 819    }
 820  }
 821  
 822  class AggregatorRenderingTestCase extends AggregatorTestCase {
 823    public static function getInfo() {
 824      return array(
 825        'name' => 'Checks display of aggregator items',
 826        'description' => 'Checks display of aggregator items on the page.',
 827        'group' => 'Aggregator'
 828      );
 829    }
 830  
 831    /**
 832     * Add a feed block to the page and checks its links.
 833     *
 834     * TODO: Test the category block as well.
 835     */
 836    public function testBlockLinks() {
 837      // Create feed.
 838      $this->createSampleNodes();
 839      $feed = $this->createFeed();
 840      $this->updateFeedItems($feed, $this->getDefaultFeedItemCount());
 841  
 842      // Place block on page (@see block.test:moveBlockToRegion())
 843      // Need admin user to be able to access block admin.
 844      $this->admin_user = $this->drupalCreateUser(array(
 845        'administer blocks',
 846        'access administration pages',
 847        'administer news feeds',
 848        'access news feeds',
 849      ));
 850      $this->drupalLogin($this->admin_user);
 851  
 852      // Prepare to use the block admin form.
 853      $block = array(
 854        'module' => 'aggregator',
 855        'delta' => 'feed-' . $feed->fid,
 856        'title' => $feed->title,
 857      );
 858      $region = 'footer';
 859      $edit = array();
 860      $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region;
 861      // Check the feed block is available in the block list form.
 862      $this->drupalGet('admin/structure/block');
 863      $this->assertFieldByName('blocks[' . $block['module'] . '_' . $block['delta'] . '][region]', '', 'Aggregator feed block is available for positioning.');
 864      // Position it.
 865      $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 866      $this->assertText(t('The block settings have been updated.'), format_string('Block successfully moved to %region_name region.', array( '%region_name' => $region)));
 867      // Confirm that the block is now being displayed on pages.
 868      $this->drupalGet('node');
 869      $this->assertText(t($block['title']), 'Feed block is displayed on the page.');
 870  
 871      // Find the expected read_more link.
 872      $href = 'aggregator/sources/' . $feed->fid;
 873      $links = $this->xpath('//a[@href = :href]', array(':href' => url($href)));
 874      $this->assert(isset($links[0]), format_string('Link to href %href found.', array('%href' => $href)));
 875  
 876      // Visit that page.
 877      $this->drupalGet($href);
 878      $correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->title));
 879      $this->assertFalse(empty($correct_titles), 'Aggregator feed page is available and has the correct title.');
 880  
 881      // Set the number of news items to 0 to test that the block does not show
 882      // up.
 883      $feed->block = 0;
 884      aggregator_save_feed((array) $feed);
 885      // It is nescessary to flush the cache after saving the number of items.
 886      drupal_flush_all_caches();
 887      // Check that the block is no longer displayed.
 888      $this->drupalGet('node');
 889      $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.');
 890    }
 891  
 892    /**
 893     * Create a feed and check that feed's page.
 894     */
 895    public function testFeedPage() {
 896      // Increase the number of items published in the rss.xml feed so we have
 897      // enough articles to test paging.
 898      variable_set('feed_default_items', 30);
 899  
 900      // Create a feed with 30 items.
 901      $this->createSampleNodes(30);
 902      $feed = $this->createFeed();
 903      $this->updateFeedItems($feed, 30);
 904  
 905      // Check for the presence of a pager.
 906      $this->drupalGet('aggregator/sources/' . $feed->fid);
 907      $elements = $this->xpath("//ul[@class=:class]", array(':class' => 'pager'));
 908      $this->assertTrue(!empty($elements), 'Individual source page contains a pager.');
 909  
 910      // Reset the number of items in rss.xml to the default value.
 911      variable_set('feed_default_items', 10);
 912    }
 913  }
 914  
 915  /**
 916   * Tests for feed parsing.
 917   */
 918  class FeedParserTestCase extends AggregatorTestCase {
 919    public static function getInfo() {
 920      return array(
 921        'name' => 'Feed parser functionality',
 922        'description' => 'Test the built-in feed parser with valid feed samples.',
 923        'group' => 'Aggregator',
 924      );
 925    }
 926  
 927    function setUp() {
 928      parent::setUp();
 929      // Do not remove old aggregator items during these tests, since our sample
 930      // feeds have hardcoded dates in them (which may be expired when this test
 931      // is run).
 932      variable_set('aggregator_clear', AGGREGATOR_CLEAR_NEVER);
 933    }
 934  
 935    /**
 936     * Test a feed that uses the RSS 0.91 format.
 937     */
 938    function testRSS091Sample() {
 939      $feed = $this->createFeed($this->getRSS091Sample());
 940      aggregator_refresh($feed);
 941      $this->drupalGet('aggregator/sources/' . $feed->fid);
 942      $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->title)));
 943      $this->assertText('First example feed item title');
 944      $this->assertLinkByHref('http://example.com/example-turns-one');
 945      $this->assertText('First example feed item description.');
 946  
 947      // Several additional items that include elements over 255 characters.
 948      $this->assertRaw("Second example feed item title.");
 949      $this->assertText('Long link feed item title');
 950      $this->assertText('Long link feed item description');
 951      $this->assertLinkByHref('http://example.com/tomorrow/and/tomorrow/and/tomorrow/creeps/in/this/petty/pace/from/day/to/day/to/the/last/syllable/of/recorded/time/and/all/our/yesterdays/have/lighted/fools/the/way/to/dusty/death/out/out/brief/candle/life/is/but/a/walking/shadow/a/poor/player/that/struts/and/frets/his/hour/upon/the/stage/and/is/heard/no/more/it/is/a/tale/told/by/an/idiot/full/of/sound/and/fury/signifying/nothing');
 952      $this->assertText('Long author feed item title');
 953      $this->assertText('Long author feed item description');
 954      $this->assertLinkByHref('http://example.com/long/author');
 955    }
 956  
 957    /**
 958     * Test a feed that uses the Atom format.
 959     */
 960    function testAtomSample() {
 961      $feed = $this->createFeed($this->getAtomSample());
 962      aggregator_refresh($feed);
 963      $this->drupalGet('aggregator/sources/' . $feed->fid);
 964      $this->assertResponse(200, format_string('Feed %name exists.', array('%name' => $feed->title)));
 965      $this->assertText('Atom-Powered Robots Run Amok');
 966      $this->assertLinkByHref('http://example.org/2003/12/13/atom03');
 967      $this->assertText('Some text.');
 968      $this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.');
 969    }
 970  }

title

Description

title

Description

title

Description

title

title

Body