Newer
Older
Alex Barth
committed
<?php
/**
* @file
* Common functionality for all Feeds tests.
*/
/**
* Test basic Data API functionality.
*/
class FeedsWebTestCase extends DrupalWebTestCase {
Dave Reid
committed
$args = func_get_args();
// Build the list of required modules which can be altered by passing in an
// array of module names to setUp().
if (isset($args[0])) {
if (is_array($args[0])) {
Chris Leppanen
committed
$modules = $args[0];
}
Chris Leppanen
committed
$modules = $args;
}
else {
$modules = array();
}
$modules[] = 'taxonomy';
$modules[] = 'image';
$modules[] = 'file';
$modules[] = 'field';
$modules[] = 'field_ui';
$modules[] = 'feeds';
$modules[] = 'feeds_ui';
$modules[] = 'ctools';
$modules[] = 'job_scheduler';
$modules = array_unique($modules);
parent::setUp($modules);
// Add text formats Directly.
$filtered_html_format = array(
'name' => 'Filtered HTML',
'weight' => 0,
'filters' => array(
// URL filter.
'filter_url' => array(
'weight' => 0,
'status' => 1,
),
// HTML filter.
'filter_html' => array(
'weight' => 1,
'status' => 1,
),
// Line break filter.
'filter_autop' => array(
'weight' => 2,
'status' => 1,
),
// HTML corrector filter.
'filter_htmlcorrector' => array(
'weight' => 10,
'status' => 1,
),
),
);
$filtered_html_format = (object) $filtered_html_format;
filter_format_save($filtered_html_format);
// Build the list of required administration permissions. Additional
// permissions can be passed as an array into setUp()'s second parameter.
if (isset($args[1]) && is_array($args[1])) {
$permissions = $args[1];
}
else {
$permissions = array();
}
$permissions[] = 'access content';
$permissions[] = 'administer site configuration';
$permissions[] = 'administer content types';
$permissions[] = 'administer nodes';
$permissions[] = 'bypass node access';
$permissions[] = 'administer taxonomy';
$permissions[] = 'administer users';
$permissions[] = 'administer filters';
MegaChriz
committed
$permissions[] = 'administer fields';
// Create an admin user and log in.
$this->admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->admin_user);
$types = array(
array(
'type' => 'page',
'name' => 'Basic page',
'node_options[status]' => 1,
'node_options[promote]' => 0,
),
array(
'type' => 'article',
'name' => 'Article',
'node_options[status]' => 1,
'node_options[promote]' => 1,
),
);
foreach ($types as $type) {
$this->drupalPost('admin/structure/types/add', $type, 'Save content type');
$this->assertText("The content type " . $type['name'] . " has been added.");
Alex Barth
committed
}
/**
* Absolute path to Drupal root.
*/
public function absolute() {
Alex Barth
committed
return realpath(getcwd());
}
/**
* Get the absolute directory path of the feeds module.
*/
public function absolutePath() {
return $this->absolute() . '/' . drupal_get_path('module', 'feeds');
}
/**
* Generate an OPML test feed.
*
* The purpose of this function is to create a dynamic OPML feed that points
* to feeds included in this test.
*/
public function generateOPML() {
$path = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/';
$output =
'<?xml version="1.0" encoding="utf-8"?>
<opml version="1.1">
<head>
<title>Feeds test OPML</title>
<dateCreated>Fri, 16 Oct 2009 02:53:17 GMT</dateCreated>
<ownerName></ownerName>
</head>
<body>
<outline text="Feeds test group" >
<outline title="Development Seed - Technological Solutions for Progressive Organizations" text="" xmlUrl="' . $path . 'developmentseed.rss2" type="rss" />
Chris Leppanen
committed
<outline title="Magyar Nemzet Online - H\'rek" text="" xmlUrl="' . $path . 'feed_without_guid.rss2" type="rss" />
<outline title="Drupal planet" text="" type="rss" xmlUrl="' . $path . 'drupalplanet.rss2" />
</body>
</opml>';
// UTF 8 encode output string and write it to disk
$output = utf8_encode($output);
$filename = file_default_scheme() . '://test-opml-' . $this->randomName() . '.opml';
$filename = file_unmanaged_save_data($output, $filename);
return $filename;
}
Alex Barth
committed
/**
* Create an importer configuration.
*
* @param $name
* The natural name of the feed.
* @param $id
* The persistent id of the feed.
* @param $edit
* Optional array that defines the basic settings for the feed in a format
* that can be posted to the feed's basic settings form.
*/
public function createImporterConfiguration($name = 'Syndication', $id = 'syndication') {
Alex Barth
committed
// Create new feed configuration.
$this->drupalGet('admin/structure/feeds');
twistor
committed
$this->clickLink('Add importer');
Alex Barth
committed
$edit = array(
'name' => $name,
'id' => $id,
);
$this->drupalPost('admin/structure/feeds/create', $edit, 'Create');
Alex Barth
committed
// Assert message and presence of default plugins.
$this->assertText('Your configuration has been created with default settings.');
$this->assertPlugins($id, 'FeedsHTTPFetcher', 'FeedsSyndicationParser', 'FeedsNodeProcessor');
// Per default attach to page content type.
$this->setSettings($id, NULL, array('content_type' => 'page'));
// Per default attached to article content type.
$this->setSettings($id, 'FeedsNodeProcessor', array('bundle' => 'article'));
Alex Barth
committed
}
/**
* Choose a plugin for a importer configuration and assert it.
*
* @param $id
* The importer configuration's id.
* @param $plugin_key
* The key string of the plugin to choose (one of the keys defined in
* feeds_feeds_plugins()).
*/
public function setPlugin($id, $plugin_key) {
Alex Barth
committed
if ($type = FeedsPlugin::typeOf($plugin_key)) {
Alex Barth
committed
$edit = array(
'plugin_key' => $plugin_key,
);
twistor
committed
$this->drupalPost("admin/structure/feeds/$id/$type", $edit, 'Save');
Alex Barth
committed
// Assert actual configuration.
$config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField());
$this->assertEqual($config[$type]['plugin_key'], $plugin_key, 'Verified correct ' . $type . ' (' . $plugin_key . ').');
Alex Barth
committed
}
}
Alex Barth
committed
/**
* Set importer or plugin settings.
*
* @param $id
* The importer configuration's id.
* @param $plugin
* The plugin (class) name, or NULL to set importer's settings
* @param $settings
* The settings to set.
*/
public function setSettings($id, $plugin, $settings) {
twistor
committed
$this->drupalPost('admin/structure/feeds/' . $id . '/settings/' . $plugin, $settings, 'Save');
Alex Barth
committed
$this->assertText('Your changes have been saved.');
}
Alex Barth
committed
/**
* Create a test feed node. Test user has to have sufficient permissions:
*
* * create [type] content
* * use feeds
*
* Assumes that page content type has been configured with
* createImporterConfiguration() as a feed content type.
Alex Barth
committed
*
* @return
* The node id of the node created.
*/
public function createFeedNode($id = 'syndication', $feed_url = NULL, $title = '', $content_type = NULL) {
Alex Barth
committed
if (empty($feed_url)) {
$feed_url = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2';
Alex Barth
committed
}
Alex Barth
committed
// If content type not given, retrieve it.
if (!$content_type) {
Alex Barth
committed
$result= db_select('feeds_importer', 'f')
->condition('f.id', $id, '=')
->fields('f', array('config'))
->execute();
$config = unserialize($result->fetchField());
$content_type = $config['content_type'];
$this->assertFalse(empty($content_type), 'Valid content type found: ' . $content_type);
Alex Barth
committed
// Create a feed node.
$edit = array(
'title' => $title,
'feeds[FeedsHTTPFetcher][source]' => $feed_url,
);
$this->drupalPost('node/add/' . str_replace('_', '-', $content_type), $edit, 'Save');
Alex Barth
committed
$this->assertText('has been created.');
// Get the node id from URL.
$nid = $this->getNid($this->getUrl());
Alex Barth
committed
// Check whether feed got recorded in feeds_source table.
Alex Barth
committed
$query = db_select('feeds_source', 's')
->condition('s.id', $id, '=')
->condition('s.feed_nid', $nid, '=');
$query->addExpression("COUNT(*)");
$result = $query->execute()->fetchField();
$this->assertEqual(1, $result);
$source = db_select('feeds_source', 's')
->condition('s.id', $id, '=')
->condition('s.feed_nid', $nid, '=')
->fields('s', array('config'))
->execute()->fetchObject();
Alex Barth
committed
$config = unserialize($source->config);
$this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
return $nid;
}
/**
* Edit the configuration of a feed node to test update behavior.
*
* @param $nid
* The nid to edit.
* @param $feed_url
* The new (absolute) feed URL to use.
* @param $title
* Optional parameter to change title of feed node.
*/
public function editFeedNode($nid, $feed_url, $title = '') {
$edit = array(
'title' => $title,
'feeds[FeedsHTTPFetcher][source]' => $feed_url,
);
// Check that the update was saved.
$this->drupalPost('node/' . $nid . '/edit', $edit, 'Save');
$this->assertText('has been updated.');
// Check that the URL was updated in the feeds_source table.
$source = db_query("SELECT * FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $nid))->fetchObject();
$config = unserialize($source->config);
$this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
}
Alex Barth
committed
/**
* Batch create a variable amount of feed nodes. All will have the
* same URL configured.
*
* @return
* An array of node ids of the nodes created.
*/
public function createFeedNodes($id = 'syndication', $num = 20, $content_type = NULL) {
Alex Barth
committed
$nids = array();
for ($i = 0; $i < $num; $i++) {
$nids[] = $this->createFeedNode($id, NULL, $this->randomName(), $content_type);
Alex Barth
committed
}
return $nids;
}
/**
* Import a URL through the import form. Assumes FeedsHTTPFetcher in place.
*/
public function importURL($id, $feed_url = NULL) {
if (empty($feed_url)) {
$feed_url = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2';
Alex Barth
committed
}
$edit = array(
'feeds[FeedsHTTPFetcher][source]' => $feed_url,
$nid = $this->drupalPost('import/' . $id, $edit, 'Import');
Alex Barth
committed
// Check whether feed got recorded in feeds_source table.
$this->assertEqual(1, db_query("SELECT COUNT(*) FROM {feeds_source} WHERE id = :id AND feed_nid = 0", array(':id' => $id))->fetchField());
$source = db_query("SELECT * FROM {feeds_source} WHERE id = :id AND feed_nid = 0", array(':id' => $id))->fetchObject();
Alex Barth
committed
$config = unserialize($source->config);
$this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
// Check whether feed got properly added to scheduler.
$this->assertEqual(1, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_import' AND last <> 0 AND scheduled = 0", array(':id' => $id))->fetchField());
// Check expire scheduler.
if (feeds_importer($id)->processor->expiryTime() == FEEDS_EXPIRE_NEVER) {
$this->assertEqual(0, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_expire'", array(':id' => $id))->fetchField());
}
else {
$this->assertEqual(1, db_query("SELECT COUNT(*) FROM {job_schedule} WHERE type = :id AND id = 0 AND name = 'feeds_source_expire'", array(':id' => $id))->fetchField());
}
Alex Barth
committed
}
/**
* Import a file through the import form. Assumes FeedsFileFetcher in place.
*/
public function importFile($id, $file) {
$this->assertTrue(file_exists($file), 'Source file exists');
$edit = array(
'files[feeds]' => $file,
);
$this->drupalPost('import/' . $id, $edit, 'Import');
}
Alex Barth
committed
/**
* Assert a feeds configuration's plugins.
*
* @deprecated:
* Use setPlugin() instead.
*
* @todo Refactor users of assertPlugin() and make them use setPugin() instead.
Alex Barth
committed
*/
public function assertPlugins($id, $fetcher, $parser, $processor) {
// Assert actual configuration.
$config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField());
Alex Barth
committed
$this->assertEqual($config['fetcher']['plugin_key'], $fetcher, 'Correct fetcher');
$this->assertEqual($config['parser']['plugin_key'], $parser, 'Correct parser');
$this->assertEqual($config['processor']['plugin_key'], $processor, 'Correct processor');
}
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
/**
* Overrides DrupalWebTestCase::assertFieldByXPath().
*
* The core version has a bug, this is the D8 version.
*
* @todo Remove once https://drupal.org/node/2105617 lands.
*/
protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
$fields = $this->xpath($xpath);
// If value specified then check array for match.
$found = TRUE;
if (isset($value)) {
$found = FALSE;
if ($fields) {
foreach ($fields as $field) {
if (isset($field['value']) && $field['value'] == $value) {
// Input element with correct value.
$found = TRUE;
}
elseif (isset($field->option) || isset($field->optgroup)) {
// Select element found.
$selected = $this->getSelectedItem($field);
if ($selected === FALSE) {
// No item selected so use first item.
$items = $this->getAllOptions($field);
if (!empty($items) && $items[0]['value'] == $value) {
$found = TRUE;
}
}
elseif ($selected == $value) {
$found = TRUE;
}
}
elseif ((string) $field == $value) {
// Text area with correct text.
$found = TRUE;
}
}
}
}
return $this->assertTrue($fields && $found, $message, $group);
}
Chris Leppanen
committed
/**
* Adds mappings to a given configuration.
*
* @param string $id
* ID of the importer.
* @param array $mappings
* An array of mapping arrays. Each mapping array must have a source and
* an target key and can have a unique key.
* @param bool $test_mappings
* (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
*/
public function addMappings($id, array $mappings, $test_mappings = TRUE) {
Chris Leppanen
committed
$path = "admin/structure/feeds/$id/mapping";
// Iterate through all mappings and add the mapping via the form.
Alex Barth
committed
foreach ($mappings as $i => $mapping) {
Chris Leppanen
committed
if ($test_mappings) {
$current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
$this->assertEqual($current_mapping_key, -1, 'Mapping does not exist before addition.');
}
// Get unique flag and unset it. Otherwise, drupalPost will complain that
niklas
committed
// Split up config and mapping.
$config = $mapping;
unset($config['source'], $config['target']);
$mapping = array('source' => $mapping['source'], 'target' => $mapping['target']);
// Add mapping.
Chris Leppanen
committed
$this->drupalPost($path, $mapping, t('Save'));
Alex Barth
committed
niklas
committed
// If there are other configuration options, set them.
if ($config) {
$this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_' . $i);
// Set some settings.
$edit = array();
foreach ($config as $key => $value) {
if (is_array($value)) {
foreach ($value as $subkey => $subvalue) {
$edit["config[$i][settings][$key][$subkey]"] = $subvalue;
}
}
else {
$edit["config[$i][settings][$key]"] = $value;
}
niklas
committed
}
$this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_' . $i);
$this->drupalPost(NULL, array(), t('Save'));
Alex Barth
committed
}
Chris Leppanen
committed
if ($test_mappings) {
$current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
$this->assertTrue($current_mapping_key >= 0, 'Mapping exists after addition.');
}
}
}
/**
* Remove mappings from a given configuration.
*
* @param string $id
* ID of the importer.
Chris Leppanen
committed
* @param array $mappings
* An array of mapping arrays. Each mapping array must have a source and
* a target key and can have a unique key.
* @param bool $test_mappings
* (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
*/
public function removeMappings($id, array $mappings, $test_mappings = TRUE) {
Chris Leppanen
committed
$path = "admin/structure/feeds/$id/mapping";
Chris Leppanen
committed
// Iterate through all mappings and remove via the form.
foreach ($mappings as $i => $mapping) {
if ($test_mappings) {
$current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
$this->assertEqual($current_mapping_key, $i, 'Mapping exists before removal.');
}
Chris Leppanen
committed
}
$this->drupalPost($path, $edit, t('Save'));
$this->assertText('Your changes have been saved.');
Chris Leppanen
committed
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
}
/**
* Gets an array of current mappings from the feeds_importer config.
*
* @param string $id
* ID of the importer.
*
* @return bool|array
* FALSE if the importer has no mappings, or an an array of mappings.
*/
public function getCurrentMappings($id) {
$config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField();
$config = unserialize($config);
// We are very specific here. 'mappings' can either be an array or not
// exist.
if (array_key_exists('mappings', $config['processor']['config'])) {
$this->assertTrue(is_array($config['processor']['config']['mappings']), 'Mappings is an array.');
return $config['processor']['config']['mappings'];
}
return FALSE;
}
/**
* Determines if a mapping exists for a given importer.
*
* @param string $id
* ID of the importer.
* @param integer $i
* The key of the mapping.
* @param string $source
* The source field.
* @param string $target
* The target field.
*
* @return integer
* -1 if the mapping doesn't exist, the key of the mapping otherwise.
*/
public function mappingExists($id, $i, $source, $target) {
$current_mappings = $this->getCurrentMappings($id);
if ($current_mappings) {
foreach ($current_mappings as $key => $mapping) {
if ($mapping['source'] == $source && $mapping['target'] == $target && $key == $i) {
return $key;
}
}
Alex Barth
committed
}
Chris Leppanen
committed
return -1;
Alex Barth
committed
}
/**
* Helper function, retrieves node id from a URL.
*/
public function getNid($url) {
$matches = array();
preg_match('/node\/(\d+?)$/', $url, $matches);
$nid = $matches[1];
// Test for actual integerness.
$this->assertTrue($nid === (string) (int) $nid, 'Node id is an integer.');
return $nid;
}
/**
* Copies a directory.
*/
public function copyDir($source, $dest) {
$result = file_prepare_directory($dest, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
if (is_file("$source/$file")) {
$file = file_unmanaged_copy("$source/$file", "$dest/$file");
}
}
}
/**
* Download and extract SimplePIE.
*
* Sets the 'feeds_simplepie_library_dir' variable to the directory where
* SimplePie is downloaded.
*/
function downloadExtractSimplePie($version) {
$url = "http://simplepie.org/downloads/simplepie_$version.mini.php";
$filename = 'simplepie.mini.php';
// Avoid downloading the file dozens of times
$library_dir = DRUPAL_ROOT . '/' . $this->originalFileDirectory . '/simpletest/feeds';
$simplepie_library_dir = $library_dir . '/simplepie';
if (!file_exists($library_dir)) {
if (!file_exists($simplepie_library_dir)) {
}
// Local file name.
$local_file = $simplepie_library_dir . '/' . $filename;
// Begin single threaded code.
if (function_exists('sem_get')) {
$semaphore = sem_get(ftok(__FILE__, 1));
sem_acquire($semaphore);
}
// Download and extact the archive, but only in one thread.
if (!file_exists($local_file)) {
$local_file = system_retrieve_file($url, $local_file, FALSE, FILE_EXISTS_REPLACE);
}
if (function_exists('sem_get')) {
sem_release($semaphore);
}
// End single threaded code.
// Verify that files were successfully extracted.
$this->assertTrue(file_exists($local_file), t('@file found.', array('@file' => $local_file)));
// Set the simpletest library directory.
variable_set('feeds_library_dir', $library_dir);
}
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
/**
* Provides a wrapper for DrupalUnitTestCase for Feeds unit testing.
*/
class FeedsUnitTestHelper extends DrupalUnitTestCase {
public function setUp() {
parent::setUp();
// Manually include the feeds module.
// @todo Allow an array of modules from the child class.
drupal_load('module', 'feeds');
}
}
class FeedsUnitTestCase extends FeedsUnitTestHelper {
public static function getInfo() {
return array(
'name' => 'Unit tests',
'description' => 'Test basic low-level Feeds module functionality.',
'group' => 'Feeds',
);
}
/**
* Test valid absolute urls.
*
* @see ValidUrlTestCase
*
* @todo Remove when http://drupal.org/node/1191252 is fixed.
*/
function testFeedsValidURL() {
$url_schemes = array('http', 'https', 'ftp', 'feed', 'webcal');
$valid_absolute_urls = array(
'example.com',
'www.example.com',
'ex-ample.com',
'3xampl3.com',
'example.com/paren(the)sis',
'example.com/index.html#pagetop',
'example.com:8080',
'subdomain.example.com',
'example.com/index.php?q=node',
'example.com/index.php?q=node¶m=false',
'user@www.example.com',
'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
'127.0.0.1',
'example.org?',
'john%20doe:secret:foo@example.org/',
'example.org/~,$\'*;',
'caf%C3%A9.example.org',
'[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
Chris Leppanen
committed
'graph.asfdasdfasdf.com/blarg/feed?access_token=133283760145143|tGew8jbxi1ctfVlYh35CPYij1eE',
);
foreach ($url_schemes as $scheme) {
foreach ($valid_absolute_urls as $url) {
$test_url = $scheme . '://' . $url;
$valid_url = feeds_valid_url($test_url, TRUE);
$this->assertTrue($valid_url, t('@url is a valid url.', array('@url' => $test_url)));
}
}
$invalid_ablosule_urls = array(
'',
'ex!ample.com',
'ex%ample.com',
);
foreach ($url_schemes as $scheme) {
foreach ($invalid_ablosule_urls as $url) {
$test_url = $scheme . '://' . $url;
$valid_url = feeds_valid_url($test_url, TRUE);
$this->assertFalse($valid_url, t('@url is NOT a valid url.', array('@url' => $test_url)));
}
}
}
}