Skip to content
Snippets Groups Projects
feeds.test 30.83 KiB
<?php
// $Id$

/**
 * @file
 * Feeds tests.
 */

// Require FeedsWebTestCase class definition.
require_once(dirname(__FILE__) .'/feeds.test.inc');

/**
 * Test aggregating a feed as node items.
 */
class FeedsRSStoNodesTest extends FeedsWebTestCase {

  /**
   * Describe this test.
   */
  public function getInfo() {
    return array(
      'name' => t('RSS import to nodes'),
      'description' => t('Tests a feed configuration that is attached to a content type, uses HTTP fetcher, common syndication parser and a node processor. Repeats the same test for an importer configuration that is not attached to a content type and for a configuration that is attached to a content type and uses the file fetcher.'),
      'group' => t('Feeds'),
    );
  }

  /**
   * Set up test.
   */
  public function setUp() {
    parent::setUp('feeds', 'feeds_ui', 'ctools');
    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'administer nodes',
        )
      )
    );
  }

  /**
   * Test node creation, refreshing/deleting feeds and feed items.
   */
  public function test() {

    // Create a feed.
    $this->createFeedConfiguration('Syndication', 'syndication');
    $this->addMappings('syndication',
      array(
        array(
          'source' => 'title',
          'target' => 'title',
          'unique' => FALSE,
        ),
        array(
          'source' => 'description',
          'target' => 'body',
          'unique' => FALSE,
        ),
        array(
          'source' => 'timestamp',
          'target' => 'created',
          'unique' => FALSE,
        ),
        array(
          'source' => 'url',
          'target' => 'url',
          'unique' => TRUE,
        ),
        array(
          'source' => 'guid',
          'target' => 'guid',
          'unique' => TRUE,
        ),
      )
    );

    $nid = $this->createFeedNode();
    // Assert 10 items aggregated after creation of the node.
    $this->assertText('Created 10 Story nodes.');

    // Navigate to feed node, there should be Feeds tabs visible.
    $this->drupalGet('node/'. $nid);
    $this->assertRaw('node/'. $nid .'/import');
    $this->assertRaw('node/'. $nid .'/delete-items');

    // Navigate to a non-feed node, there should be no Feeds tabs visible.
    $story_nid = db_result(db_query_range('SELECT nid FROM {node} WHERE type = "story"', 0, 1));
    $this->drupalGet('node/'. $story_nid);
    $this->assertNoRaw('node/'. $story_nid .'/import');
    $this->assertNoRaw('node/'. $story_nid .'/delete-items');
    $this->assertEqual("Created/updated by FeedsNodeProcessor", db_result(db_query("SELECT nr.log FROM {node} n JOIN {node_revisions} nr ON n.vid = nr.vid WHERE n.nid = %d", $story_nid)));

    // Assert accuracy of aggregated information.
    $this->drupalGet('node');
    $this->assertText('Open Atrium Translation Workflow: Two Way Translation Updates');
    $this->assertText('Tue, 10/06/2009');
    $this->assertText('A new translation process for Open Atrium &amp; integration with Localize Drupal');
    $this->assertText('Week in DC Tech: October 5th Edition');
    $this->assertText('Mon, 10/05/2009');
    $this->assertText('There are some great technology events happening this week');
    $this->assertText('Mapping Innovation at the World Bank with Open Atrium');
    $this->assertText('Fri, 10/02/2009');
    $this->assertText('Open Atrium is being used as a base platform for collaboration');
    $this->assertText('September GeoDC Meetup Tonight');
    $this->assertText('Wed, 09/30/2009');
    $this->assertText('Today is the last Wednesday of the month');
    $this->assertText('Week in DC Tech: September 28th Edition');
    $this->assertText('Mon, 09/28/2009');
    $this->assertText('Looking to geek out this week? There are a bunch of');
    $this->assertText('Open Data for Microfinance: The New MIXMarket.org');
    $this->assertText('Thu, 09/24/2009');
    $this->assertText('There are profiles for every country that the MIX Market is hosting.');
    $this->assertText('Integrating the Siteminder Access System in an Open Atrium-based Intranet');
    $this->assertText('Tue, 09/22/2009');
    $this->assertText('In addition to authentication, the Siteminder system');
    $this->assertText('Week in DC Tech: September 21 Edition');
    $this->assertText('Mon, 09/21/2009');
    $this->assertText('an interesting variety of technology events happening in Washington, DC ');
    $this->assertText('s Software Freedom Day: Impressions &amp; Photos');
    $this->assertText('Mon, 09/21/2009');
    $this->assertText('Presenting on Features in Drupal and Open Atrium');
    $this->assertText('Scaling the Open Atrium UI');
    $this->assertText('Fri, 09/18/2009');
    $this->assertText('The first major change is switching');

    // Assert DB status.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Import again.
    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
    $this->assertText('There is no new content.');

    // Assert DB status, there still shouldn't be more than 10 items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Enable update existing and import updated feed file.
    $this->setSettings('syndication', 'FeedsNodeProcessor', array('update_existing' => TRUE));
    $feed_url = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/developmentseed_changes.rss2';
    $this->editFeedNode($nid, $feed_url);
    $this->drupalPost('node/' . $nid . '/import', array(), 'Import');
    $this->assertText('Updated 2 Story nodes.');

    // Assert accuracy of aggregated content (check 2 updates, one original).
    $this->drupalGet('node');
    $this->assertText('Managing News Translation Workflow: Two Way Translation Updates');
    $this->assertText('Presenting on Features in Drupal and Managing News');
    $this->assertText('Scaling the Open Atrium UI');

    // Import again.
    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
    $this->assertText('There is no new content.');

    // Assert DB status, there still shouldn't be more than 10 items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Now delete all items.
    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
    $this->assertText('Deleted 10 nodes.');

    // Assert DB status, now there should be no items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 0, 'Accurate number of items in database.');

    // Import again, we should find new content.
    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
    $this->assertText('Created 10 Story nodes.');

    // Assert DB status, there should be 10 again.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Login with new user with only access content permissions.
    $this->drupalLogin(
      $this->drupalCreateUser()
    );
    // Navigate to feed node, there should be no Feeds tabs visible.
    $this->drupalGet('node/'. $nid);
    $this->assertNoRaw('node/'. $nid .'/import');
    $this->assertNoRaw('node/'. $nid .'/delete-items');

    // Now create a second feed configuration that is not attached to a content
    // type and run tests on importing/purging.

    // Login with sufficient permissions.
    $this->drupalLogin(
      $this->drupalCreateUser(array('administer feeds', 'administer nodes'))
    );
    // Remove all items again so that next test can check for them.
    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');

    // Create a feed, not attached to content type.
    $this->createFeedConfiguration('Syndication standalone', 'syndication_standalone');
    $edit = array(
      'content_type' => '',
    );
    $this->drupalPost('admin/build/feeds/edit/syndication_standalone/settings', $edit, 'Save');
    $this->addMappings('syndication_standalone',
      array(
        array(
          'source' => 'title',
          'target' => 'title',
          'unique' => FALSE,
        ),
        array(
          'source' => 'description',
          'target' => 'body',
          'unique' => FALSE,
        ),
        array(
          'source' => 'timestamp',
          'target' => 'created',
          'unique' => FALSE,
        ),
        array(
          'source' => 'url',
          'target' => 'url',
          'unique' => TRUE,
        ),
        array(
          'source' => 'guid',
          'target' => 'guid',
          'unique' => TRUE,
        ),
      )
    );

    // Import, assert 10 items aggregated after creation of the node.
    $this->importURL('syndication_standalone');
    $this->assertText('Created 10 Story nodes.');

    // Assert accuracy of aggregated information.
    $this->drupalGet('node');
    $this->assertText('Open Atrium Translation Workflow: Two Way Translation Updates');
    $this->assertText('Tue, 10/06/2009');
    $this->assertText('A new translation process for Open Atrium &amp; integration with Localize Drupal');
    $this->assertText('Week in DC Tech: October 5th Edition');
    $this->assertText('Mon, 10/05/2009');
    $this->assertText('There are some great technology events happening this week');
    $this->assertText('Mapping Innovation at the World Bank with Open Atrium');
    $this->assertText('Fri, 10/02/2009');
    $this->assertText('Open Atrium is being used as a base platform for collaboration');
    $this->assertText('September GeoDC Meetup Tonight');
    $this->assertText('Wed, 09/30/2009');
    $this->assertText('Today is the last Wednesday of the month');
    $this->assertText('Week in DC Tech: September 28th Edition');
    $this->assertText('Mon, 09/28/2009');
    $this->assertText('Looking to geek out this week? There are a bunch of');
    $this->assertText('Open Data for Microfinance: The New MIXMarket.org');
    $this->assertText('Thu, 09/24/2009');
    $this->assertText('There are profiles for every country that the MIX Market is hosting.');
    $this->assertText('Integrating the Siteminder Access System in an Open Atrium-based Intranet');
    $this->assertText('Tue, 09/22/2009');
    $this->assertText('In addition to authentication, the Siteminder system');
    $this->assertText('Week in DC Tech: September 21 Edition');
    $this->assertText('Mon, 09/21/2009');
    $this->assertText('an interesting variety of technology events happening in Washington, DC ');
    $this->assertText('s Software Freedom Day: Impressions &amp; Photos');
    $this->assertText('Mon, 09/21/2009');
    $this->assertText('Presenting on Features in Drupal and Open Atrium');
    $this->assertText('Scaling the Open Atrium UI');
    $this->assertText('Fri, 09/18/2009');
    $this->assertText('The first major change is switching');

    // Assert DB status.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Import again.
    $this->drupalPost('import/syndication_standalone', array(), 'Import');
    $this->assertText('There is no new content.');

    // Assert DB status, there still shouldn't be more than 10 items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');
    // Enable update existing and import updated feed file.
    $this->setSettings('syndication_standalone', 'FeedsNodeProcessor', array('update_existing' => TRUE));
    $feed_url = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed_changes.rss2';
    $this->importURL('syndication_standalone', $feed_url);
    $this->assertText('Updated 2 Story nodes.');

    // Assert accuracy of aggregated information (check 2 updates, one orig).
    $this->drupalGet('node');
    $this->assertText('Managing News Translation Workflow: Two Way Translation Updates');
    $this->assertText('Presenting on Features in Drupal and Managing News');
    $this->assertText('Scaling the Open Atrium UI');

    // Import again.
    $this->drupalPost('import/syndication_standalone', array(), 'Import');
    $this->assertText('There is no new content.');

    // Assert DB status, there still shouldn't be more than 10 items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Now delete all items.
    $this->drupalPost('import/syndication_standalone/delete-items', array(), 'Delete');
    $this->assertText('Deleted 10 nodes.');

    // Assert DB status, now there should be no items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 0, 'Accurate number of items in database.');

    // Import again, we should find new content.
    $this->drupalPost('import/syndication_standalone', array(), 'Import');
    $this->assertText('Created 10 Story nodes.');

    // Assert DB status, there should be 10 again.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_node_item}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Login with new user with only access content permissions.
    $this->drupalLogin(
      $this->drupalCreateUser()
    );
    // Navigate to feed import form, access should be denied.
    $this->drupalGet('import/syndication_standalone');
    $this->assertResponse(403);

    // Use File Fetcher.
    $this->drupalLogin(
      $this->drupalCreateUser(array('administer feeds', 'administer nodes'))
    );
    $this->setPlugin('syndication', 'FeedsFileFetcher');
    // Create a feed node.
    $edit = array(
      'files[feeds]' => $this->absolutePath() .'/tests/feeds/drupalplanet.rss2',
    );
    $this->drupalPost('node/add/page', $edit, 'Save');
    $this->assertText('has been created.');
    $this->assertText('Created 25 Story nodes.');
  }
}

/**
 * Test aggregating a feed as data records.
 */
class FeedsRSStoDataTest extends FeedsWebTestCase {

  /**
   * Describe this test.
   */
  public function getInfo() {
    return array(
      'name' => t('RSS import to data records'),
      'description' => t('Tests a feed configuration that is attached to a content type, uses common syndication parser and a node processor. <strong>Requires Data module and Views module</strong>.'),
      'group' => t('Feeds'),
    );
  }

  /**
   * Set up test.
   */
  public function setUp() {
    parent::setUp('feeds', 'feeds_ui', 'ctools', 'data', 'data_ui', 'views');
    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'create page content',
        )
      )
    );
  }

  /**
   * Test node creation, refreshing/deleting feeds and feed items.
   */
  public function test() {

    // Create a feed.
    $this->createFeedConfiguration('Data feed', 'rss');

    // Go to edit page and select the data processor.
    $edit = array(
      'plugin_key' => 'FeedsDataProcessor',
    );
    $this->drupalPost('admin/build/feeds/edit/rss/processor', $edit, 'Save');
    $this->assertPlugins('rss', 'FeedsHTTPFetcher', 'FeedsSyndicationParser', 'FeedsDataProcessor');

    // Go to mapping page and create a couple of mappings.
    $mappings = array(
      array(
        'source' => 'guid',
        'target' => 'new:text',
        'unique' => TRUE,
      ),
      array(
        'source' => 'url',
        'target' => 'new:text',
        'unique' => TRUE,
      ),
      array(
        'source' => 'timestamp',
        'target' => 'timestamp', // timestamp is an existing target.
        'unique' => FALSE,
      ),
      array(
        'source' => 'title',
        'target' => 'new:varchar',
        'unique' => FALSE,
      ),
      array(
        'source' => 'description',
        'target' => 'new:text',
        'unique' => FALSE,
      ),
    );
    $this->addMappings('rss', $mappings);

    // Verify the mapping configuration.
    $config = unserialize(db_result(db_query("SELECT config FROM {feeds_importer} WHERE id = 'rss'")));
    $stored_mappings = $config['processor']['config']['mappings'];
    foreach ($mappings as $i => $mapping) {
      $this->assertEqual($mapping['source'], $stored_mappings[$i]['source']);
      // This is intentional: the target of the stored mapping should have the
      // same key as the source, this has to do with the fact that feeds data
      // creates storage as the mapping is created.
      $this->assertEqual($mapping['source'], $stored_mappings[$i]['target']);
      $this->assertEqual($mapping['unique'], $stored_mappings[$i]['unique']);
    }

    // Create standard feed node.
    $nid = $this->createFeedNode('rss');
    // Assert 10 items aggregated after creation of the node.
    $this->assertText('Created 10 items.');

    // Login with a user with administer data permissions and review aggregated
    // content.
    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer data tables',
          'administer feeds',
        )
      )
    );

    // Assert accuracy of aggregated information.
    $this->drupalGet('admin/content/data/view/feeds_data_rss');
    $this->assertText('Open Atrium Translation Workflow: Two Way Translation Updates');
    $this->assertText('A new translation process for Open Atrium &amp; integration with Localize Drupal');
    $this->assertText('Week in DC Tech: October 5th Edition');
    $this->assertText('There are some great technology events happening this week');
    $this->assertText('Mapping Innovation at the World Bank with Open Atrium');
    $this->assertText('is being used as a base platform for collaboration at the World Bank because of its feature flexibility');
    $this->assertText('September GeoDC Meetup Tonight');
    $this->assertText('Today is the last Wednesday of the month');
    $this->assertText('Week in DC Tech: September 28th Edition');
    $this->assertText('Looking to geek out this week? There are a bunch of');
    $this->assertText('Open Data for Microfinance: The New MIXMarket.org');
    $this->assertText('There are profiles for every country that the MIX Market is hosting.');
    $this->assertText('Integrating the Siteminder Access System in an Open Atrium-based Intranet');
    $this->assertText('In addition to authentication, the Siteminder system');
    $this->assertText('Week in DC Tech: September 21 Edition');
    $this->assertText('an interesting variety of technology events happening in Washington, DC ');
    $this->assertText('s Software Freedom Day: Impressions &amp; Photos');
    $this->assertText('Presenting on Features in Drupal and Open Atrium');
    $this->assertText('Scaling the Open Atrium UI');
    $this->assertText('The first major change is switching');

    // Assert DB status.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_data_rss}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Import again.
    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
    $this->assertText('There are no new items.');

    // Assert DB status, there still shouldn't be more than 10 items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_data_rss}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // Now delete all items.
    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
    $this->assertText('Deleted 10 items.');

    // Assert DB status, now there should be no items.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_data_rss}"));
    $this->assertEqual($count, 0, 'Accurate number of items in database.');

    // Import again, we should find new content.
    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
    $this->assertText('Created 10 items.');

    // Assert DB status, there should be 10 again.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_data_rss}"));
    $this->assertEqual($count, 10, 'Accurate number of items in database.');

    // @todo Standalone import form testing.
    // @todo Create a second feed and test.
  }
}

/**
 * Test aggregating a feed as data records.
 */
class FeedsCSVtoUsersTest extends FeedsWebTestCase {

  /**
   * Describe this test.
   */
  public function getInfo() {
    return array(
      'name' => t('CSV import to users'),
      'description' => t('Tests a standalone import configuration that uses file fetcher and CSV parser to import users from a CSV file.'),
      'group' => t('Feeds'),
    );
  }

  /**
   * Set up test.
   */
  public function setUp() {
    parent::setUp('feeds', 'feeds_ui', 'ctools');

    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'administer users',
        )
      )
    );
  }

  /**
   * Test node creation, refreshing/deleting feeds and feed items.
   */
  public function test() {

    // Create a feed.
    $this->createFeedConfiguration('User import', 'user_import');

    // Set and configure plugins.
    $this->setPlugin('user_import', 'FeedsFileFetcher');
    $this->setPlugin('user_import', 'FeedsCSVParser');
    $this->setPlugin('user_import', 'FeedsUserProcessor');

    // Go to mapping page and create a couple of mappings.
    $mappings = array(
      '0' => array(
        'source' => 'name',
        'target' => 'name',
        'unique' => 0,
      ),
      '1' => array(
        'source' => 'mail',
        'target' => 'mail',
        'unique' => 1,
      ),
      '2' => array(
        'source' => 'since',
        'target' => 'created',
        'unique' => FALSE,
      ),
    );
    $this->addMappings('user_import', $mappings);

    // Change some of the basic configuration.
    $edit = array(
      'content_type' => '',
      'import_period' => FEEDS_SCHEDULE_NEVER,
    );
    $this->drupalPost('admin/build/feeds/edit/user_import/settings', $edit, 'Save');

    // Import CSV file.
    $this->importFile('user_import', $this->absolutePath() .'/tests/feeds/users.csv');

    // Assert result.
    $this->assertText('Created 4 users.');
    // 1 user has an invalid email address.
    $this->assertText('There were 1 users that could not be imported because either their name or their email was empty or not valid. Check import data and mapping settings on User processor.');
    $this->drupalGet('admin/user/user');
    $this->assertText('Morticia');
    $this->assertText('Fester');
    $this->assertText('Gomez');
    $this->assertText('Pugsley');

    // @todo Test status setting, update existing and role settings.
  }
}

/**
 * Test cron scheduling.
 */
class FeedsSchedulerTestCase extends FeedsWebTestCase {

  /**
   * Describe this test.
   */
  public function getInfo() {
    return array(
      'name' => t('Scheduler'),
      'description' => t('Tests for feeds scheduler.'),
      'group' => t('Feeds'),
    );
  }

  /**
   * Set up test.
   */
  public function setUp() {
    parent::setUp('feeds', 'feeds_ui', 'ctools');
    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'administer nodes',
        )
      )
    );
    $this->createFeedConfiguration();
    $this->addMappings('syndication',
      array(
        array(
          'source' => 'title',
          'target' => 'title',
          'unique' => FALSE,
        ),
        array(
          'source' => 'description',
          'target' => 'body',
          'unique' => FALSE,
        ),
        array(
          'source' => 'timestamp',
          'target' => 'created',
          'unique' => FALSE,
        ),
        array(
          'source' => 'url',
          'target' => 'url',
          'unique' => TRUE,
        ),
        array(
          'source' => 'guid',
          'target' => 'guid',
          'unique' => TRUE,
        ),
      )
    );
  }

  /**
   * Test scheduling on cron.
   */
  public function testScheduling() {
    // Create 10 feed nodes. Turn off import on create before doing that.
    $edit = array(
      'import_on_create' => FALSE,
    );
    $this->drupalPost('admin/build/feeds/edit/syndication/settings', $edit, 'Save');
    $this->assertText('Do not import on create');

    $nids = $this->createFeedNodes();
    // This implicitly tests the import_on_create node setting being 0.
    $this->assertTrue($nids[0] == 1 && $nids[1] == 2, 'Node ids sequential.');

    // Log out and run cron twice.
    $this->drupalLogout();
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');

    // There should be feeds_schedule_num (= 10) feeds updated now.
    $schedule = array();
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_schedule} WHERE last_executed_time <> 0"));
    $this->assertEqual($count, 10, '10 feeds refreshed on cron.');

    // There should be 100 story nodes in the database.
    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story'"));
    $this->assertEqual($count, 100, 'There are 100 story nodes aggregated.');

    // Hit twice cron again.
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');

    // There should be feeds_schedule_num X 2 (= 20) feeds updated now.
    $schedule = array();
    $result = db_query("SELECT feed_nid, last_executed_time, scheduled FROM {feeds_schedule} WHERE last_executed_time <> 0");
    while ($row = db_fetch_object($result)) {
      $schedule[$row->feed_nid] = $row;
    }
    $this->assertEqual(count($schedule), 20, '20 feeds refreshed on cron.');

    // There should be 200 story nodes in the database.
    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1"));
    $this->assertEqual($count, 200, 'There are 200 story nodes aggregated.');

    // There shouldn't be any items with scheduled = 1 now, if so, this would
    // mean they are stuck.
    $count = db_result(db_query("SELECT COUNT(*) FROM {feeds_schedule} WHERE scheduled = 1"));
    $this->assertEqual($count, 0, 'All items are unscheduled (schedule flag = 0).');

    // Hit cron again twice.
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    // The import_period setting of the feed configuration is 1800, there
    // shouldn't be any change to the database now.
    $equal = TRUE;
    $result = db_query("SELECT feed_nid, last_executed_time, scheduled FROM {feeds_schedule} WHERE last_executed_time <> 0");
    while ($row = db_fetch_object($result)) {
      $equal = $equal && ($row->last_executed_time == $schedule[$row->feed_nid]->last_executed_time);
    }
    $this->assertTrue($equal, 'Schedule did not change.');

    // Log back in and set refreshing to as often as possible.
    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'administer nodes',
        )
      )
    );
    $edit = array(
      'import_period' => 0,
    );
    $this->drupalPost('admin/build/feeds/edit/syndication/settings', $edit, 'Save');
    $this->assertText('Refresh: as often as possible');

    // Hit cron again, 4 times now.
    for ($i = 0; $i < 4; $i++) {
      $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    }

    // Refresh period is set to 'as often as possible'. All scheduled times
    // should have changed now.
    // There should not be more nodes than before.
    $equal = FALSE;
    $output = '';
    $result = db_query("SELECT feed_nid, last_executed_time, scheduled FROM {feeds_schedule} WHERE last_executed_time <> 0");
    while ($row = db_fetch_object($result)) {
      $equal = $equal || ($row->last_executed_time == $schedule[$row->feed_nid]->last_executed_time);
    }
    $this->assertFalse($equal, 'Every feed schedule time changed.');

    // There should be 200 story nodes in the database.
    $count = db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story' AND status = 1"));
    $this->assertEqual($count, 200, 'The total of 200 story nodes has not changed.');

    // @todo Use debug time feature in FeedsScheduler and test behavior in future.
    // @todo How do I call an API function on the test system from the test script?
  }

  /**
   * Test batching on cron.
   */
  function testBatching() {
    // Verify that there are 150 nodes total.
    $nid = $this->createFeedNode('syndication', $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/many_items.rss2');
    $this->assertText('Created 150 Story nodes.');
    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
    $this->assertText('Deleted 150 nodes.');

    // Hit cron 3 times, assert correct number of story nodes.
    for ($i = 0; $i < 3; $i++) {
      $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
      // 50 == FEEDS_NODE_BATCH_SIZE
      $this->assertEqual(50 * ($i + 1), db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story'")));
    }

    // Delete a couple of nodes, then hit cron again. They should not be replaced
    // as the minimum update time is 30 minutes.
    $result = db_query_range("SELECT nid FROM {node} WHERE type = 'story'", 0, 2);
    while ($node = db_fetch_object($result)) {
      $this->drupalPost("node/{$node->nid}/delete", array(), 'Delete');
    }
    $this->assertEqual(148, db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story'")));
    $this->drupalGet($GLOBALS['base_url'] .'/cron.php');
    $this->assertEqual(148, db_result(db_query("SELECT COUNT(*) FROM {node} WHERE type = 'story'")));
  }
}

/**
 * Test single feeds.
 */
class FeedsSyndicationParserTestCase extends FeedsWebTestCase {

  /**
   * Describe this test.
   */
  public function getInfo() {
    return array(
      'name' => t('Syndication parsers'),
      'description' => t('Regression tests for syndication parsers Common syndication and SimplePie. Tests parsers against a set of feeds in the context of Feeds module. <strong>Requires SimplePie parser to be configured correctly.</strong>'),
      'group' => t('Feeds'),
    );
  }

  /**
   * Set up test.
   */
  public function setUp() {
    parent::setUp('feeds', 'feeds_ui', 'ctools');

    $this->drupalLogin(
      $this->drupalCreateUser(
        array(
          'administer feeds', 'administer nodes',
        )
      )
    );
  }

  /**
   * Run tests.
   */
  public function test() {
    $this->createFeedConfiguration('Syndication', 'syndication');

    foreach (array('FeedsSyndicationParser', 'FeedsSimplePieParser') as $parser) {
      $this->setPlugin('syndication', $parser);
      foreach ($this->feedUrls() as $url => $assertions) {
        $this->createFeedNode('syndication', $url);
        $this->assertText('Created '. $assertions['item_count'] .' Story nodes.');
      }
    }
  }

  /**
   * Return an array of test feeds.
   */
  protected function feedUrls() {
    $path = $GLOBALS['base_url'] .'/'. drupal_get_path('module', 'feeds') .'/tests/feeds/';
    return array(
      "{$path}developmentseed.rss2" => array(
        'item_count' => 10,
      ),
      "{$path}feed_without_guid.rss2" => array(
        'item_count' => 10,
      ),
    );
  }
}