Projet

Général

Profil

Paste
Télécharger (27,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / feeds / tests / feeds.test @ ed9a13f1

1
<?php
2

    
3
/**
4
 * @file
5
 * Common functionality for all Feeds tests.
6
 */
7

    
8
/**
9
 * Test basic Data API functionality.
10
 */
11
class FeedsWebTestCase extends DrupalWebTestCase {
12
  protected $profile = 'testing';
13

    
14
  /**
15
   * {@inheritdoc}
16
   */
17
  public function setUp() {
18
    $args = func_get_args();
19

    
20
    // Build the list of required modules which can be altered by passing in an
21
    // array of module names to setUp().
22
    if (isset($args[0])) {
23
      if (is_array($args[0])) {
24
        $modules = $args[0];
25
      }
26
      else {
27
        $modules = $args;
28
      }
29
    }
30
    else {
31
      $modules = array();
32
    }
33

    
34
    $modules[] = 'taxonomy';
35
    $modules[] = 'image';
36
    $modules[] = 'file';
37
    $modules[] = 'field';
38
    $modules[] = 'field_ui';
39
    $modules[] = 'feeds';
40
    $modules[] = 'feeds_ui';
41
    $modules[] = 'feeds_tests';
42
    $modules[] = 'ctools';
43
    $modules[] = 'job_scheduler';
44
    $modules = array_unique($modules);
45
    parent::setUp($modules);
46

    
47
    // Add text formats Directly.
48
    $filtered_html_format = array(
49
      'format' => 'filtered_html',
50
      'name' => 'Filtered HTML',
51
      'weight' => 0,
52
      'filters' => array(
53
        // URL filter.
54
        'filter_url' => array(
55
          'weight' => 0,
56
          'status' => 1,
57
        ),
58
        // HTML filter.
59
        'filter_html' => array(
60
          'weight' => 1,
61
          'status' => 1,
62
        ),
63
        // Line break filter.
64
        'filter_autop' => array(
65
          'weight' => 2,
66
          'status' => 1,
67
        ),
68
        // HTML corrector filter.
69
        'filter_htmlcorrector' => array(
70
          'weight' => 10,
71
          'status' => 1,
72
        ),
73
      ),
74
    );
75
    $filtered_html_format = (object) $filtered_html_format;
76
    filter_format_save($filtered_html_format);
77

    
78
    // Build the list of required administration permissions. Additional
79
    // permissions can be passed as an array into setUp()'s second parameter.
80
    if (isset($args[1]) && is_array($args[1])) {
81
      $permissions = $args[1];
82
    }
83
    else {
84
      $permissions = array();
85
    }
86

    
87
    $permissions[] = 'access content';
88
    $permissions[] = 'administer site configuration';
89
    $permissions[] = 'administer content types';
90
    $permissions[] = 'administer nodes';
91
    $permissions[] = 'bypass node access';
92
    $permissions[] = 'administer taxonomy';
93
    $permissions[] = 'administer users';
94
    $permissions[] = 'administer feeds';
95
    $permissions[] = 'administer filters';
96
    $permissions[] = 'administer fields';
97

    
98
    // Create an admin user and log in.
99
    $this->admin_user = $this->drupalCreateUser($permissions);
100
    $this->drupalLogin($this->admin_user);
101

    
102
    // Create two content types if they don't exist yet.
103
    $existing_node_types = node_type_get_names();
104
    $types = array(
105
      array(
106
        'type' => 'page',
107
        'name' => 'Basic page',
108
        'node_options[status]' => 1,
109
        'node_options[promote]' => 0,
110
      ),
111
      array(
112
        'type' => 'article',
113
        'name' => 'Article',
114
        'node_options[status]' => 1,
115
        'node_options[promote]' => 1,
116
      ),
117
    );
118
    foreach ($types as $type) {
119
      // Check if the content type to add already exists. Other tests which
120
      // inherit from FeedsWebTestCase might already have added one of the
121
      // content types in one of the modules they have enabled in their startup.
122
      if (isset($existing_node_types[$type['type']])) {
123
        // Content type already exists. Continue to the next one.
124
        continue;
125
      }
126

    
127
      $this->drupalPost('admin/structure/types/add', $type, 'Save content type');
128
      $this->assertText("The content type " . $type['name'] . " has been added.");
129
    }
130
  }
131

    
132
  /**
133
   * Absolute path to Drupal root.
134
   */
135
  public function absolute() {
136
    return realpath(getcwd());
137
  }
138

    
139
  /**
140
   * Get the absolute directory path of the feeds module.
141
   */
142
  public function absolutePath() {
143
    return $this->absolute() . '/' . drupal_get_path('module', 'feeds');
144
  }
145

    
146
  /**
147
   * Generate an OPML test feed.
148
   *
149
   * The purpose of this function is to create a dynamic OPML feed that points
150
   * to feeds included in this test.
151
   */
152
  public function generateOPML() {
153
    $path = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/';
154

    
155
    $output =
156
    '<?xml version="1.0" encoding="utf-8"?>
157
<opml version="1.1">
158
<head>
159
    <title>Feeds test OPML</title>
160
    <dateCreated>Fri, 16 Oct 2009 02:53:17 GMT</dateCreated>
161
    <ownerName></ownerName>
162
</head>
163
<body>
164
  <outline text="Feeds test group" >
165
    <outline title="Development Seed - Technological Solutions for Progressive Organizations" text="" xmlUrl="' . $path . 'developmentseed.rss2" type="rss" />
166
    <outline title="Magyar Nemzet Online - H\'rek" text="" xmlUrl="' . $path . 'feed_without_guid.rss2" type="rss" />
167
    <outline title="Drupal planet" text="" type="rss" xmlUrl="' . $path . 'drupalplanet.rss2" />
168
  </outline>
169
</body>
170
</opml>';
171

    
172
    // UTF 8 encode output string and write it to disk.
173
    $output = utf8_encode($output);
174
    $filename = file_default_scheme() . '://test-opml-' . $this->randomName() . '.opml';
175

    
176
    $filename = file_unmanaged_save_data($output, $filename);
177
    return $filename;
178
  }
179

    
180
  /**
181
   * Create an importer configuration.
182
   *
183
   * @param $name
184
   *   The natural name of the feed.
185
   * @param $id
186
   *   The persistent id of the feed.
187
   * @param $edit
188
   *   Optional array that defines the basic settings for the feed in a format
189
   *   that can be posted to the feed's basic settings form.
190
   */
191
  public function createImporterConfiguration($name = 'Syndication', $id = 'syndication') {
192
    // Create new feed configuration.
193
    $this->drupalGet('admin/structure/feeds');
194
    $this->clickLink('Add importer');
195
    $edit = array(
196
      'name' => $name,
197
      'id' => $id,
198
    );
199
    $this->drupalPost('admin/structure/feeds/create', $edit, 'Create');
200

    
201
    // Assert message and presence of default plugins.
202
    $this->assertText('Your configuration has been created with default settings.');
203
    $this->assertPlugins($id, 'FeedsHTTPFetcher', 'FeedsSyndicationParser', 'FeedsNodeProcessor');
204
    // Per default attach to page content type.
205
    $this->setSettings($id, NULL, array('content_type' => 'page'));
206
    // Per default attached to article content type.
207
    $this->setSettings($id, 'FeedsNodeProcessor', array('bundle' => 'article'));
208
  }
209

    
210
  /**
211
   * Choose a plugin for a importer configuration and assert it.
212
   *
213
   * @param $id
214
   *   The importer configuration's id.
215
   * @param $plugin_key
216
   *   The key string of the plugin to choose (one of the keys defined in
217
   *   feeds_feeds_plugins()).
218
   */
219
  public function setPlugin($id, $plugin_key) {
220
    if ($type = FeedsPlugin::typeOf($plugin_key)) {
221
      $edit = array(
222
        'plugin_key' => $plugin_key,
223
      );
224
      $this->drupalPost("admin/structure/feeds/$id/$type", $edit, 'Save');
225

    
226
      // Assert actual configuration.
227
      $config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField());
228
      $this->assertEqual($config[$type]['plugin_key'], $plugin_key, 'Verified correct ' . $type . ' (' . $plugin_key . ').');
229
    }
230
  }
231

    
232
  /**
233
   * Set importer or plugin settings.
234
   *
235
   * @param $id
236
   *   The importer configuration's id.
237
   * @param $plugin
238
   *   The plugin (class) name, or NULL to set importer's settings
239
   * @param $settings
240
   *   The settings to set.
241
   */
242
  public function setSettings($id, $plugin, $settings) {
243
    $this->drupalPost('admin/structure/feeds/' . $id . '/settings/' . $plugin, $settings, 'Save');
244
    $this->assertText('Your changes have been saved.');
245
  }
246

    
247
  /**
248
   * Create a test feed node. Test user has to have sufficient permissions:.
249
   *
250
   * * create [type] content
251
   * * use feeds.
252
   *
253
   * Assumes that page content type has been configured with
254
   * createImporterConfiguration() as a feed content type.
255
   *
256
   * @return
257
   *   The node id of the node created.
258
   */
259
  public function createFeedNode($id = 'syndication', $feed_url = NULL, $title = '', $content_type = NULL) {
260
    if (empty($feed_url)) {
261
      $feed_url = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2';
262
    }
263

    
264
    // If content type not given, retrieve it.
265
    if (!$content_type) {
266
      $result = db_select('feeds_importer', 'f')
267
        ->condition('f.id', $id, '=')
268
        ->fields('f', array('config'))
269
        ->execute();
270
      $config = unserialize($result->fetchField());
271
      $content_type = $config['content_type'];
272
      $this->assertFalse(empty($content_type), 'Valid content type found: ' . $content_type);
273
    }
274

    
275
    // Create a feed node.
276
    $edit = array(
277
      'title' => $title,
278
      'feeds[FeedsHTTPFetcher][source]' => $feed_url,
279
    );
280
    $this->drupalPost('node/add/' . str_replace('_', '-', $content_type), $edit, 'Save');
281
    $this->assertText('has been created.');
282

    
283
    // Get the node id from URL.
284
    $nid = $this->getNid($this->getUrl());
285

    
286
    // Check whether feed got recorded in feeds_source table.
287
    $query = db_select('feeds_source', 's')
288
      ->condition('s.id', $id, '=')
289
      ->condition('s.feed_nid', $nid, '=');
290
    $query->addExpression("COUNT(*)");
291
    $result = $query->execute()->fetchField();
292
    $this->assertEqual(1, $result);
293

    
294
    $source = db_select('feeds_source', 's')
295
      ->condition('s.id', $id, '=')
296
      ->condition('s.feed_nid', $nid, '=')
297
      ->fields('s', array('config'))
298
      ->execute()->fetchObject();
299
    $config = unserialize($source->config);
300
    $this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
301
    return $nid;
302
  }
303

    
304
  /**
305
   * Edit the configuration of a feed node to test update behavior.
306
   *
307
   * @param $nid
308
   *   The nid to edit.
309
   * @param $feed_url
310
   *   The new (absolute) feed URL to use.
311
   * @param $title
312
   *   Optional parameter to change title of feed node.
313
   */
314
  public function editFeedNode($nid, $feed_url, $title = '') {
315
    $edit = array(
316
      'title' => $title,
317
      'feeds[FeedsHTTPFetcher][source]' => $feed_url,
318
    );
319
    // Check that the update was saved.
320
    $this->drupalPost('node/' . $nid . '/edit', $edit, 'Save');
321
    $this->assertText('has been updated.');
322

    
323
    // Check that the URL was updated in the feeds_source table.
324
    $source = db_query("SELECT * FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $nid))->fetchObject();
325
    $config = unserialize($source->config);
326
    $this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
327
  }
328

    
329
  /**
330
   * Batch create a variable amount of feed nodes. All will have the
331
   * same URL configured.
332
   *
333
   * @return
334
   *   An array of node ids of the nodes created.
335
   */
336
  public function createFeedNodes($id = 'syndication', $num = 20, $content_type = NULL) {
337
    $nids = array();
338
    for ($i = 0; $i < $num; $i++) {
339
      $nids[] = $this->createFeedNode($id, NULL, $this->randomName(), $content_type);
340
    }
341
    return $nids;
342
  }
343

    
344
  /**
345
   * Import a URL through the import form. Assumes FeedsHTTPFetcher in place.
346
   */
347
  public function importURL($id, $feed_url = NULL) {
348
    if (empty($feed_url)) {
349
      $feed_url = $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'feeds') . '/tests/feeds/developmentseed.rss2';
350
    }
351
    $edit = array(
352
      'feeds[FeedsHTTPFetcher][source]' => $feed_url,
353
    );
354
    $nid = $this->drupalPost('import/' . $id, $edit, 'Import');
355

    
356
    // Check whether feed got recorded in feeds_source table.
357
    $this->assertEqual(1, db_query("SELECT COUNT(*) FROM {feeds_source} WHERE id = :id AND feed_nid = 0", array(':id' => $id))->fetchField());
358
    $source = db_query("SELECT * FROM {feeds_source} WHERE id = :id AND feed_nid = 0", array(':id' => $id))->fetchObject();
359
    $config = unserialize($source->config);
360
    $this->assertEqual($config['FeedsHTTPFetcher']['source'], $feed_url, t('URL in DB correct.'));
361

    
362
    // Check whether feed got properly added to scheduler.
363
    $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());
364
    // Check expire scheduler.
365
    if (feeds_importer($id)->processor->expiryTime() == FEEDS_EXPIRE_NEVER) {
366
      $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());
367
    }
368
    else {
369
      $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());
370
    }
371
  }
372

    
373
  /**
374
   * Import a file through the import form. Assumes FeedsFileFetcher in place.
375
   *
376
   * @param string $id
377
   *   The ID of the importer.
378
   * @param string $file
379
   *   The absolute path to the file.
380
   * @param string $submit
381
   *   (optional) The button to press.
382
   *   Defaults to the button "Import".
383
   */
384
  public function importFile($id, $file, $submit = NULL) {
385
    $this->assertTrue(file_exists($file), 'Source file exists');
386
    $edit = array(
387
      'files[feeds]' => $file,
388
    );
389
    if (empty($submit)) {
390
      $submit = 'Import';
391
    }
392
    $this->drupalPost('import/' . $id, $edit, $submit);
393
  }
394

    
395
  /**
396
   * Asserts that the given number of nodes exist.
397
   *
398
   * @param int $expected_node_count
399
   *   The expected number of nodes in the node table.
400
   * @param string $message
401
   *   (optional) The message to assert.
402
   */
403
  protected function assertNodeCount($expected_node_count, $message = '') {
404
    if (!$message) {
405
      $message = '@expected nodes have been created (actual: @count).';
406
    }
407

    
408
    $node_count = db_select('node')
409
      ->fields('node', array())
410
      ->countQuery()
411
      ->execute()
412
      ->fetchField();
413
    $this->assertEqual($expected_node_count, $node_count, format_string($message, array(
414
      '@expected' => $expected_node_count,
415
      '@count' => $node_count,
416
    )));
417
  }
418

    
419
  /**
420
   * Assert a feeds configuration's plugins.
421
   *
422
   * @deprecated:
423
   *   Use setPlugin() instead.
424
   *
425
   * @todo Refactor users of assertPlugin() and make them use setPugin() instead.
426
   */
427
  public function assertPlugins($id, $fetcher, $parser, $processor) {
428
    // Assert actual configuration.
429
    $config = unserialize(db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField());
430

    
431
    $this->assertEqual($config['fetcher']['plugin_key'], $fetcher, 'Correct fetcher');
432
    $this->assertEqual($config['parser']['plugin_key'], $parser, 'Correct parser');
433
    $this->assertEqual($config['processor']['plugin_key'], $processor, 'Correct processor');
434
  }
435

    
436
  /**
437
   * Overrides DrupalWebTestCase::assertFieldByXPath().
438
   *
439
   * The core version has a bug, this is the D8 version.
440
   *
441
   * @todo Remove once https://drupal.org/node/2105617 lands.
442
   */
443
  protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
444
    $fields = $this->xpath($xpath);
445

    
446
    // If value specified then check array for match.
447
    $found = TRUE;
448
    if (isset($value)) {
449
      $found = FALSE;
450
      if ($fields) {
451
        foreach ($fields as $field) {
452
          if (isset($field['value']) && $field['value'] == $value) {
453
            // Input element with correct value.
454
            $found = TRUE;
455
          }
456
          elseif (isset($field->option) || isset($field->optgroup)) {
457
            // Select element found.
458
            $selected = $this->getSelectedItem($field);
459
            if ($selected === FALSE) {
460
              // No item selected so use first item.
461
              $items = $this->getAllOptions($field);
462
              if (!empty($items) && $items[0]['value'] == $value) {
463
                $found = TRUE;
464
              }
465
            }
466
            elseif ($selected == $value) {
467
              $found = TRUE;
468
            }
469
          }
470
          elseif ((string) $field == $value) {
471
            // Text area with correct text.
472
            $found = TRUE;
473
          }
474
        }
475
      }
476
    }
477
    return $this->assertTrue($fields && $found, $message, $group);
478
  }
479

    
480
  /**
481
   * Asserts that a field in the current page is enabled.
482
   *
483
   * @param string $name
484
   *   Name of field to assert.
485
   * @param string $message
486
   *   (optional) A message to display with the assertion.
487
   *
488
   * @return bool
489
   *   TRUE on pass, FALSE on fail.
490
   */
491
  protected function assertFieldEnabled($name, $message = '') {
492
    $elements = $this->xpath($this->constructFieldXpath('name', $name));
493
    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['disabled']), $message ? $message : t('Field %name is enabled.', array('%name' => $name)), t('Browser'));
494
  }
495

    
496
  /**
497
   * Asserts that a field in the current page is disabled.
498
   *
499
   * @param string $name
500
   *   Name of field to assert.
501
   * @param string $message
502
   *   (optional) A message to display with the assertion.
503
   *
504
   * @return bool
505
   *   TRUE on pass, FALSE on fail.
506
   */
507
  protected function assertFieldDisabled($name, $message = '') {
508
    $elements = $this->xpath($this->constructFieldXpath('name', $name));
509
    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['disabled']), $message ? $message : t('Field %name is disabled.', array('%name' => $name)), t('Browser'));
510
  }
511

    
512
  /**
513
   * Adds mappings to a given configuration.
514
   *
515
   * @param string $id
516
   *   ID of the importer.
517
   * @param array $mappings
518
   *   An array of mapping arrays. Each mapping array must have a source and
519
   *   an target key and can have a unique key.
520
   * @param bool $test_mappings
521
   *   (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
522
   */
523
  public function addMappings($id, array $mappings, $test_mappings = TRUE) {
524

    
525
    $path = "admin/structure/feeds/$id/mapping";
526

    
527
    // Iterate through all mappings and add the mapping via the form.
528
    foreach ($mappings as $i => $mapping) {
529

    
530
      if ($test_mappings) {
531
        $current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
532
        $this->assertEqual($current_mapping_key, -1, 'Mapping does not exist before addition.');
533
      }
534

    
535
      // Get unique flag and unset it. Otherwise, drupalPost will complain that
536
      // Split up config and mapping.
537
      $config = $mapping;
538
      unset($config['source'], $config['target']);
539
      $mapping = array('source' => $mapping['source'], 'target' => $mapping['target']);
540

    
541
      // Add mapping.
542
      $this->drupalPost($path, $mapping, t('Save'));
543

    
544
      // If there are other configuration options, set them.
545
      if ($config) {
546
        $this->drupalPostAJAX(NULL, array(), 'mapping_settings_edit_' . $i);
547

    
548
        // Set some settings.
549
        $edit = array();
550
        foreach ($config as $key => $value) {
551
          if (is_array($value)) {
552
            foreach ($value as $subkey => $subvalue) {
553
              $edit["config[$i][settings][$key][$subkey]"] = $subvalue;
554
            }
555
          }
556
          else {
557
            $edit["config[$i][settings][$key]"] = $value;
558
          }
559
        }
560
        $this->drupalPostAJAX(NULL, $edit, 'mapping_settings_update_' . $i);
561
        $this->drupalPost(NULL, array(), t('Save'));
562
      }
563

    
564
      if ($test_mappings) {
565
        $current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
566
        $this->assertTrue($current_mapping_key >= 0, 'Mapping exists after addition.');
567
      }
568
    }
569
  }
570

    
571
  /**
572
   * Remove mappings from a given configuration.
573
   *
574
   * @param string $id
575
   *   ID of the importer.
576
   * @param array $mappings
577
   *   An array of mapping arrays. Each mapping array must have a source and
578
   *   a target key and can have a unique key.
579
   * @param bool $test_mappings
580
   *   (optional) TRUE to automatically test mapping configs. Defaults to TRUE.
581
   */
582
  public function removeMappings($id, array $mappings, $test_mappings = TRUE) {
583
    $path = "admin/structure/feeds/$id/mapping";
584

    
585
    $edit = array();
586

    
587
    // Iterate through all mappings and remove via the form.
588
    foreach ($mappings as $i => $mapping) {
589

    
590
      if ($test_mappings) {
591
        $current_mapping_key = $this->mappingExists($id, $i, $mapping['source'], $mapping['target']);
592
        $this->assertEqual($current_mapping_key, $i, 'Mapping exists before removal.');
593
      }
594

    
595
      $edit["remove_flags[$i]"] = 1;
596
    }
597

    
598
    $this->drupalPost($path, $edit, t('Save'));
599
    $this->assertText('Your changes have been saved.');
600
  }
601

    
602
  /**
603
   * Gets an array of current mappings from the feeds_importer config.
604
   *
605
   * @param string $id
606
   *   ID of the importer.
607
   *
608
   * @return bool|array
609
   *   FALSE if the importer has no mappings, or an an array of mappings.
610
   */
611
  public function getCurrentMappings($id) {
612
    $config = db_query("SELECT config FROM {feeds_importer} WHERE id = :id", array(':id' => $id))->fetchField();
613

    
614
    $config = unserialize($config);
615

    
616
    // We are very specific here. 'mappings' can either be an array or not
617
    // exist.
618
    if (array_key_exists('mappings', $config['processor']['config'])) {
619
      $this->assertTrue(is_array($config['processor']['config']['mappings']), 'Mappings is an array.');
620

    
621
      return $config['processor']['config']['mappings'];
622
    }
623

    
624
    return FALSE;
625
  }
626

    
627
  /**
628
   * Determines if a mapping exists for a given importer.
629
   *
630
   * @param string $id
631
   *   ID of the importer.
632
   * @param int $i
633
   *   The key of the mapping.
634
   * @param string $source
635
   *   The source field.
636
   * @param string $target
637
   *   The target field.
638
   *
639
   * @return int
640
   *   -1 if the mapping doesn't exist, the key of the mapping otherwise.
641
   */
642
  public function mappingExists($id, $i, $source, $target) {
643

    
644
    $current_mappings = $this->getCurrentMappings($id);
645

    
646
    if ($current_mappings) {
647
      foreach ($current_mappings as $key => $mapping) {
648
        if ($mapping['source'] == $source && $mapping['target'] == $target && $key == $i) {
649
          return $key;
650
        }
651
      }
652
    }
653

    
654
    return -1;
655
  }
656

    
657
  /**
658
   * Helper function, retrieves node id from a URL.
659
   */
660
  public function getNid($url) {
661
    $matches = array();
662
    preg_match('/node\/(\d+?)$/', $url, $matches);
663
    $nid = $matches[1];
664

    
665
    // Test for actual integerness.
666
    $this->assertTrue($nid === (string) (int) $nid, 'Node id is an integer.');
667

    
668
    return $nid;
669
  }
670

    
671
  /**
672
   * Changes the author of a node and asserts the change in the UI.
673
   *
674
   * @param int $nid
675
   *   The ID of the node to change author.
676
   * @param object $account
677
   *   The new author.
678
   */
679
  protected function changeNodeAuthor($nid, $account) {
680
    $node = node_load($nid);
681
    $node->uid = $account->uid;
682
    node_save($node);
683
    // Assert that author was in fact changed.
684
    $this->drupalGet('node/' . $nid);
685
    $this->assertText($account->name);
686
  }
687

    
688
  /**
689
   * Copies a directory.
690
   *
691
   * @param string $source
692
   *   The path of the source directory, from which files should be copied.
693
   * @param string $dest
694
   *   The path of the destination directory, where files should be copied to.
695
   * @param array $mapping
696
   *   (optional) The files to rename in the process.
697
   *   To skip files from being copied, map filename to FALSE.
698
   */
699
  public function copyDir($source, $dest, array $mapping = array()) {
700
    $result = file_prepare_directory($dest, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
701
    foreach (@scandir($source) as $file) {
702
      if (is_file("$source/$file")) {
703
        $new_name = isset($mapping[$file]) ? $mapping[$file] : $file;
704
        if ($new_name !== FALSE) {
705
          $file = file_unmanaged_copy("$source/$file", "$dest/$new_name");
706
        }
707
      }
708
    }
709
  }
710

    
711
  /**
712
   * Download and extract SimplePIE.
713
   *
714
   * Sets the 'feeds_simplepie_library_dir' variable to the directory where
715
   * SimplePie is downloaded.
716
   */
717
  public function downloadExtractSimplePie($version) {
718
    $url = "https://codeload.github.com/simplepie/simplepie/zip/$version";
719

    
720
    // Avoid downloading the file dozens of times.
721
    $library_dir = DRUPAL_ROOT . '/' . $this->originalFileDirectory . '/simpletest/feeds';
722
    $simplepie_library_dir = $library_dir . '/simplepie';
723

    
724
    if (!file_exists($library_dir)) {
725
      drupal_mkdir($library_dir);
726
    }
727

    
728
    // Local file name.
729
    $local_file = $library_dir . '/simplepie/library/SimplePie.php';
730
    $zip_file = $library_dir . '/simplepie.zip';
731

    
732
    // Begin single threaded code.
733
    if (function_exists('sem_get')) {
734
      $semaphore = sem_get(ftok(__FILE__, 1));
735
      sem_acquire($semaphore);
736
    }
737

    
738
    // Download the archive, but only in one thread.
739
    if (!file_exists($local_file)) {
740
      if (!file_exists($zip_file)) {
741
        $zip_file = system_retrieve_file($url, $zip_file, FALSE, FILE_EXISTS_REPLACE);
742
      }
743

    
744
      // Extract the archive.
745
      $zip = new ZipArchive();
746
      if ($zip->open($zip_file) === TRUE) {
747
        $zip->extractTo($library_dir);
748
        $zip->close();
749
      }
750

    
751
      // Rename directory.
752
      rename($library_dir . '/simplepie-' . $version, $simplepie_library_dir);
753
    }
754

    
755
    if (function_exists('sem_get')) {
756
      sem_release($semaphore);
757
    }
758
    // End single threaded code.
759
    // Verify that files were successfully extracted.
760
    $this->assertTrue(file_exists($local_file), t('@file found.', array('@file' => $local_file)));
761

    
762
    // Set the simpletest library directory.
763
    variable_set('feeds_library_dir', $library_dir);
764
  }
765

    
766
}
767

    
768
/**
769
 * Provides a wrapper for DrupalUnitTestCase for Feeds unit testing.
770
 */
771
class FeedsUnitTestHelper extends DrupalUnitTestCase {
772

    
773
  /**
774
   * {@inheritdoc}
775
   */
776
  public function setUp() {
777
    parent::setUp();
778

    
779
    // Manually include the feeds module.
780
    // @todo Allow an array of modules from the child class.
781
    drupal_load('module', 'feeds');
782
  }
783

    
784
}
785
/**
786
 *
787
 */
788
class FeedsUnitTestCase extends FeedsUnitTestHelper {
789

    
790
  /**
791
   * {@inheritdoc}
792
   */
793
  public static function getInfo() {
794
    return array(
795
      'name' => 'Unit tests',
796
      'description' => 'Test basic low-level Feeds module functionality.',
797
      'group' => 'Feeds',
798
    );
799
  }
800

    
801
  /**
802
   * Test valid absolute urls.
803
   *
804
   * @see ValidUrlTestCase
805
   *
806
   * @todo Remove when http://drupal.org/node/1191252 is fixed.
807
   */
808
  public function testFeedsValidURL() {
809
    $url_schemes = array('http', 'https', 'ftp', 'feed', 'webcal');
810
    $valid_absolute_urls = array(
811
      'example.com',
812
      'www.example.com',
813
      'ex-ample.com',
814
      '3xampl3.com',
815
      'example.com/paren(the)sis',
816
      'example.com/index.html#pagetop',
817
      'example.com:8080',
818
      'subdomain.example.com',
819
      'example.com/index.php?q=node',
820
      'example.com/index.php?q=node&param=false',
821
      'user@www.example.com',
822
      'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
823
      '127.0.0.1',
824
      'example.org?',
825
      'john%20doe:secret:foo@example.org/',
826
      'example.org/~,$\'*;',
827
      'caf%C3%A9.example.org',
828
      '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
829
      'graph.asfdasdfasdf.com/blarg/feed?access_token=133283760145143|tGew8jbxi1ctfVlYh35CPYij1eE',
830
    );
831

    
832
    foreach ($url_schemes as $scheme) {
833
      foreach ($valid_absolute_urls as $url) {
834
        $test_url = $scheme . '://' . $url;
835
        $valid_url = feeds_valid_url($test_url, TRUE);
836
        $this->assertTrue($valid_url, t('@url is a valid url.', array('@url' => $test_url)));
837
      }
838
    }
839

    
840
    $invalid_ablosule_urls = array(
841
      '',
842
      'ex!ample.com',
843
      'ex%ample.com',
844
    );
845

    
846
    foreach ($url_schemes as $scheme) {
847
      foreach ($invalid_ablosule_urls as $url) {
848
        $test_url = $scheme . '://' . $url;
849
        $valid_url = feeds_valid_url($test_url, TRUE);
850
        $this->assertFalse($valid_url, t('@url is NOT a valid url.', array('@url' => $test_url)));
851
      }
852
    }
853
  }
854

    
855
}