AggregatorTestBase.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. <?php
  2. namespace Drupal\Tests\aggregator\Functional;
  3. use Drupal\Component\Render\FormattableMarkup;
  4. use Drupal\Core\Url;
  5. use Drupal\aggregator\Entity\Feed;
  6. use Drupal\Component\Utility\Html;
  7. use Drupal\node\NodeInterface;
  8. use Drupal\Tests\BrowserTestBase;
  9. use Drupal\aggregator\FeedInterface;
  10. /**
  11. * Defines a base class for testing the Aggregator module.
  12. */
  13. abstract class AggregatorTestBase extends BrowserTestBase {
  14. /**
  15. * A user with permission to administer feeds and create content.
  16. *
  17. * @var \Drupal\user\Entity\User
  18. */
  19. protected $adminUser;
  20. /**
  21. * Modules to install.
  22. *
  23. * @var array
  24. */
  25. public static $modules = [
  26. 'block',
  27. 'node',
  28. 'aggregator',
  29. 'aggregator_test',
  30. 'views',
  31. ];
  32. /**
  33. * {@inheritdoc}
  34. */
  35. protected function setUp() {
  36. parent::setUp();
  37. // Create an Article node type.
  38. if ($this->profile != 'standard') {
  39. $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
  40. }
  41. $this->adminUser = $this->drupalCreateUser([
  42. 'access administration pages',
  43. 'administer news feeds',
  44. 'access news feeds',
  45. 'create article content',
  46. ]);
  47. $this->drupalLogin($this->adminUser);
  48. $this->drupalPlaceBlock('local_tasks_block');
  49. }
  50. /**
  51. * Creates an aggregator feed.
  52. *
  53. * This method simulates the form submission on path aggregator/sources/add.
  54. *
  55. * @param string $feed_url
  56. * (optional) If given, feed will be created with this URL, otherwise
  57. * /rss.xml will be used. Defaults to NULL.
  58. * @param array $edit
  59. * Array with additional form fields.
  60. *
  61. * @return \Drupal\aggregator\FeedInterface
  62. * Full feed object if possible.
  63. *
  64. * @see getFeedEditArray()
  65. */
  66. public function createFeed($feed_url = NULL, array $edit = []) {
  67. $edit = $this->getFeedEditArray($feed_url, $edit);
  68. $this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
  69. $this->assertText(t('The feed @name has been added.', ['@name' => $edit['title[0][value]']]), new FormattableMarkup('The feed @name has been added.', ['@name' => $edit['title[0][value]']]));
  70. // Verify that the creation message contains a link to a feed.
  71. $view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', [':href' => 'aggregator/sources/']);
  72. $this->assert(isset($view_link), 'The message area contains a link to a feed');
  73. $fids = \Drupal::entityQuery('aggregator_feed')->condition('title', $edit['title[0][value]'])->condition('url', $edit['url[0][value]'])->execute();
  74. $this->assertNotEmpty($fids, 'The feed found in database.');
  75. return Feed::load(array_values($fids)[0]);
  76. }
  77. /**
  78. * Deletes an aggregator feed.
  79. *
  80. * @param \Drupal\aggregator\FeedInterface $feed
  81. * Feed object representing the feed.
  82. */
  83. public function deleteFeed(FeedInterface $feed) {
  84. $this->drupalPostForm('aggregator/sources/' . $feed->id() . '/delete', [], t('Delete'));
  85. $this->assertRaw(t('The feed %title has been deleted.', ['%title' => $feed->label()]), 'Feed deleted successfully.');
  86. }
  87. /**
  88. * Returns a randomly generated feed edit array.
  89. *
  90. * @param string $feed_url
  91. * (optional) If given, feed will be created with this URL, otherwise
  92. * /rss.xml will be used. Defaults to NULL.
  93. * @param array $edit
  94. * Array with additional form fields.
  95. *
  96. * @return array
  97. * A feed array.
  98. */
  99. public function getFeedEditArray($feed_url = NULL, array $edit = []) {
  100. $feed_name = $this->randomMachineName(10);
  101. if (!$feed_url) {
  102. $feed_url = Url::fromRoute('view.frontpage.feed_1', [], [
  103. 'query' => ['feed' => $feed_name],
  104. 'absolute' => TRUE,
  105. ])->toString();
  106. }
  107. $edit += [
  108. 'title[0][value]' => $feed_name,
  109. 'url[0][value]' => $feed_url,
  110. 'refresh' => '900',
  111. ];
  112. return $edit;
  113. }
  114. /**
  115. * Returns a randomly generated feed edit object.
  116. *
  117. * @param string $feed_url
  118. * (optional) If given, feed will be created with this URL, otherwise
  119. * /rss.xml will be used. Defaults to NULL.
  120. * @param array $values
  121. * (optional) Default values to initialize object properties with.
  122. *
  123. * @return \Drupal\aggregator\FeedInterface
  124. * A feed object.
  125. */
  126. public function getFeedEditObject($feed_url = NULL, array $values = []) {
  127. $feed_name = $this->randomMachineName(10);
  128. if (!$feed_url) {
  129. $feed_url = Url::fromRoute('view.frontpage.feed_1', [
  130. 'query' => ['feed' => $feed_name],
  131. 'absolute' => TRUE,
  132. ])->toString();
  133. }
  134. $values += [
  135. 'title' => $feed_name,
  136. 'url' => $feed_url,
  137. 'refresh' => '900',
  138. ];
  139. return Feed::create($values);
  140. }
  141. /**
  142. * Returns the count of the randomly created feed array.
  143. *
  144. * @return int
  145. * Number of feed items on default feed created by createFeed().
  146. */
  147. public function getDefaultFeedItemCount() {
  148. // Our tests are based off of rss.xml, so let's find out how many elements
  149. // should be related.
  150. $feed_count = \Drupal::entityQuery('node')
  151. ->condition('promote', NodeInterface::PROMOTED)
  152. ->condition('status', NodeInterface::PUBLISHED)
  153. ->accessCheck(FALSE)
  154. ->range(0, $this->config('system.rss')->get('items.limit'))
  155. ->count()
  156. ->execute();
  157. return min($feed_count, 10);
  158. }
  159. /**
  160. * Updates the feed items.
  161. *
  162. * This method simulates a click to
  163. * admin/config/services/aggregator/update/$fid.
  164. *
  165. * @param \Drupal\aggregator\FeedInterface $feed
  166. * Feed object representing the feed.
  167. * @param int|null $expected_count
  168. * Expected number of feed items. If omitted no check will happen.
  169. */
  170. public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
  171. // First, let's ensure we can get to the rss xml.
  172. $this->drupalGet($feed->getUrl());
  173. $this->assertSession()->statusCodeEquals(200);
  174. // Attempt to access the update link directly without an access token.
  175. $this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());
  176. $this->assertSession()->statusCodeEquals(403);
  177. // Refresh the feed (simulated link click).
  178. $this->drupalGet('admin/config/services/aggregator');
  179. $this->clickLink('Update items');
  180. // Ensure we have the right number of items.
  181. $item_ids = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->execute();
  182. $feed->items = array_values($item_ids);
  183. if ($expected_count !== NULL) {
  184. $feed->item_count = count($feed->items);
  185. $this->assertEqual($expected_count, $feed->item_count, new FormattableMarkup('Total items in feed equal to the total items in database (@val1 != @val2)', ['@val1' => $expected_count, '@val2' => $feed->item_count]));
  186. }
  187. }
  188. /**
  189. * Confirms an item removal from a feed.
  190. *
  191. * @param \Drupal\aggregator\FeedInterface $feed
  192. * Feed object representing the feed.
  193. */
  194. public function deleteFeedItems(FeedInterface $feed) {
  195. $this->drupalPostForm('admin/config/services/aggregator/delete/' . $feed->id(), [], t('Delete items'));
  196. $this->assertRaw(t('The news items from %title have been deleted.', ['%title' => $feed->label()]), 'Feed items deleted.');
  197. }
  198. /**
  199. * Adds and deletes feed items and ensure that the count is zero.
  200. *
  201. * @param \Drupal\aggregator\FeedInterface $feed
  202. * Feed object representing the feed.
  203. * @param int $expected_count
  204. * Expected number of feed items.
  205. */
  206. public function updateAndDelete(FeedInterface $feed, $expected_count) {
  207. $count_query = \Drupal::entityQuery('aggregator_item')->condition('fid', $feed->id())->count();
  208. $this->updateFeedItems($feed, $expected_count);
  209. $count = $count_query->execute();
  210. $this->assertGreaterThan(0, $count);
  211. $this->deleteFeedItems($feed);
  212. $count = $count_query->execute();
  213. $this->assertEquals(0, $count);
  214. }
  215. /**
  216. * Checks whether the feed name and URL are unique.
  217. *
  218. * @param string $feed_name
  219. * String containing the feed name to check.
  220. * @param string $feed_url
  221. * String containing the feed url to check.
  222. *
  223. * @return bool
  224. * TRUE if feed is unique.
  225. */
  226. public function uniqueFeed($feed_name, $feed_url) {
  227. $result = \Drupal::entityQuery('aggregator_feed')->condition('title', $feed_name)->condition('url', $feed_url)->count()->execute();
  228. return (1 == $result);
  229. }
  230. /**
  231. * Creates a valid OPML file from an array of feeds.
  232. *
  233. * @param array $feeds
  234. * An array of feeds.
  235. *
  236. * @return string
  237. * Path to valid OPML file.
  238. */
  239. public function getValidOpml(array $feeds) {
  240. // Properly escape URLs so that XML parsers don't choke on them.
  241. foreach ($feeds as &$feed) {
  242. $feed['url[0][value]'] = Html::escape($feed['url[0][value]']);
  243. }
  244. /**
  245. * Does not have an XML declaration, must pass the parser.
  246. */
  247. $opml = <<<EOF
  248. <opml version="1.0">
  249. <head></head>
  250. <body>
  251. <!-- First feed to be imported. -->
  252. <outline text="{$feeds[0]['title[0][value]']}" xmlurl="{$feeds[0]['url[0][value]']}" />
  253. <!-- Second feed. Test string delimitation and attribute order. -->
  254. <outline xmlurl='{$feeds[1]['url[0][value]']}' text='{$feeds[1]['title[0][value]']}'/>
  255. <!-- Test for duplicate URL and title. -->
  256. <outline xmlurl="{$feeds[0]['url[0][value]']}" text="Duplicate URL"/>
  257. <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title[0][value]']}"/>
  258. <!-- Test that feeds are only added with required attributes. -->
  259. <outline text="{$feeds[2]['title[0][value]']}" />
  260. <outline xmlurl="{$feeds[2]['url[0][value]']}" />
  261. </body>
  262. </opml>
  263. EOF;
  264. $path = 'public://valid-opml.xml';
  265. // Add the UTF-8 byte order mark.
  266. return \Drupal::service('file_system')->saveData(chr(239) . chr(187) . chr(191) . $opml, $path);
  267. }
  268. /**
  269. * Creates an invalid OPML file.
  270. *
  271. * @return string
  272. * Path to invalid OPML file.
  273. */
  274. public function getInvalidOpml() {
  275. $opml = <<<EOF
  276. <opml>
  277. <invalid>
  278. </opml>
  279. EOF;
  280. $path = 'public://invalid-opml.xml';
  281. return \Drupal::service('file_system')->saveData($opml, $path);
  282. }
  283. /**
  284. * Creates a valid but empty OPML file.
  285. *
  286. * @return string
  287. * Path to empty OPML file.
  288. */
  289. public function getEmptyOpml() {
  290. $opml = <<<EOF
  291. <?xml version="1.0" encoding="utf-8"?>
  292. <opml version="1.0">
  293. <head></head>
  294. <body>
  295. <outline text="Sample text" />
  296. <outline text="Sample text" url="Sample URL" />
  297. </body>
  298. </opml>
  299. EOF;
  300. $path = 'public://empty-opml.xml';
  301. return \Drupal::service('file_system')->saveData($opml, $path);
  302. }
  303. /**
  304. * Returns a example RSS091 feed.
  305. *
  306. * @return string
  307. * Path to the feed.
  308. */
  309. public function getRSS091Sample() {
  310. return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_rss091.xml';
  311. }
  312. /**
  313. * Returns a example Atom feed.
  314. *
  315. * @return string
  316. * Path to the feed.
  317. */
  318. public function getAtomSample() {
  319. // The content of this sample ATOM feed is based directly off of the
  320. // example provided in RFC 4287.
  321. return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_atom.xml';
  322. }
  323. /**
  324. * Returns a example feed.
  325. *
  326. * @return string
  327. * Path to the feed.
  328. */
  329. public function getHtmlEntitiesSample() {
  330. return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/modules/aggregator_test/aggregator_test_title_entities.xml';
  331. }
  332. /**
  333. * Creates sample article nodes.
  334. *
  335. * @param int $count
  336. * (optional) The number of nodes to generate. Defaults to five.
  337. */
  338. public function createSampleNodes($count = 5) {
  339. // Post $count article nodes.
  340. for ($i = 0; $i < $count; $i++) {
  341. $edit = [];
  342. $edit['title[0][value]'] = $this->randomMachineName();
  343. $edit['body[0][value]'] = $this->randomMachineName();
  344. $this->drupalPostForm('node/add/article', $edit, t('Save'));
  345. }
  346. }
  347. /**
  348. * Enable the plugins coming with aggregator_test module.
  349. */
  350. public function enableTestPlugins() {
  351. $this->config('aggregator.settings')
  352. ->set('fetcher', 'aggregator_test_fetcher')
  353. ->set('parser', 'aggregator_test_parser')
  354. ->set('processors', [
  355. 'aggregator_test_processor' => 'aggregator_test_processor',
  356. 'aggregator' => 'aggregator',
  357. ])
  358. ->save();
  359. }
  360. }