'Newsletter generation test', 'description' => 'Testing generation of newsletters', 'group' => 'Simplenews Scheduler', ); } /** * Declares the module dependencies for the test. */ function setUp() { parent::setUp(); $this->privileged_user = $this->drupalCreateUser(array( 'access content', 'administer nodes', 'create simplenews content', 'edit own simplenews content', 'send newsletter', 'send scheduled newsletters', 'overview scheduled newsletters', )); $this->drupalLogin($this->privileged_user); // Subscribe a user to simplenews. $categories = simplenews_categories_load_multiple(); $this->mail = 'test@example.org'; simplenews_subscribe_user($this->mail, key($categories), FALSE, 'test'); } /** * Basic simplenews newsletter generation test * create a simplenews node, */ function testNewsletterGeneration() { $edit = array(); $title ="newsletter " . $this->randomName(8); $edit = array(); $edit['title'] = $title; $edit["body[und][0][value]"] = $this->randomName(16); $this->drupalPost('node/add/simplenews', $edit, t('Save')); $this->assertText($title); preg_match('|node/(\d+)$|', $this->getUrl(), $matches); $node = node_load($matches[1]); // Make sure that the editions tab is not visible as long as it's not a // scheduled newsletter. $this->drupalGet("node/{$node->nid}/editions"); $this->assertResponse(403, t('Editions tab not accessible')); // Now create the simplenews schedule configuration. $this->drupalGet("node/{$node->nid}/simplenews"); $this->assertText(t("Send newsletter according to schedule")); $edit = array(); $edit["simplenews[send]"] = '4'; $edit["simplenews[scheduler][interval]"] = "hour"; // Specify a start time 30 minutes in the past to be able to have a known // edition creation time that can be checked. $date = new DateTime(); $date->sub(new DateInterval('PT30M')); $edit["simplenews[scheduler][start_date][year]"] = $date->format('Y'); $edit["simplenews[scheduler][start_date][month]"] = $date->format('n'); $edit["simplenews[scheduler][start_date][day]"] = $date->format('j'); $edit["simplenews[scheduler][start_date][hour]"] = $date->format('G'); $edit["simplenews[scheduler][start_date][minute]"] = $date->format('i'); $edit["simplenews[scheduler][title]"] = "Custom title [site:name]"; $this->drupalPost("node/{$node->nid}/simplenews", $edit, t('Submit')); // Make sure it knows no editions created yet. $this->drupalGet("node/{$node->nid}/editions"); $this->assertText(t("No scheduled newsletter editions have been sent.")); // Execute cron. drupal_cron_run(); // See if it was created. $this->drupalGet("node/{$node->nid}/editions"); $this->assertText("Custom title"); $this->assertNoText(t("No scheduled newsletter editions have been sent.")); $this->assertText($title); // original real node title // Go to node and verify creation time and token for custom title // @todo: make this real token integration $this->clickLink("Custom title ". variable_get('site_name', 'Drupal')); $this->assertText(t('This is a newsletter edititon. View the the master template of this newsletter here')); $this->assertText(t('Submitted by @name on @date', array('@name' => format_username($this->privileged_user), '@date' => format_date($date->getTimestamp())))); // Check sent mails. $mails = $this->drupalGetMails(); $this->assertEqual(1, count($mails), t('Newsletter mail has been sent.')); $this->clickLink(t('Newsletter')); $this->assertText(t('This node is part of a scheduled newsletter configuration.')); $this->clickLink(t('here')); $this->assertEqual(url('node/' . $node->nid, array('absolute' => TRUE)), $this->getUrl()); // Test the tab on a sent newsletter, schedule details should not be shown. $title ="newsletter " . $this->randomName(8); $edit = array(); $edit['title'] = $title; $edit["body[und][0][value]"] = $this->randomName(16); $this->drupalPost('node/add/simplenews', $edit, t('Save')); $this->assertText($title); preg_match('|node/(\d+)$|', $this->getUrl(), $matches); $node = node_load($matches[1]); $edit = array(); $edit["simplenews[send]"] = SIMPLENEWS_COMMAND_SEND_NOW; $this->drupalPost("node/{$node->nid}/simplenews", $edit, t('Submit')); $this->assertNoText(t('Schedule details')); // Check sent mails. $mails = $this->drupalGetMails(); $this->assertEqual(1, count($mails), t('Newsletter mail has been sent.')); } } /** * Unit testing for monthly newsletter next run times. */ class SimpleNewsSchedulerNextRunTimeTest extends SimpleNewsSchedulerWebTestCase { /** * Provides information about this test. */ public static function getInfo() { return array( 'name' => 'Next run time: monthly', 'description' => 'Testing edition times for newsletters due every month and every 2 months.', 'group' => 'Simplenews Scheduler', ); } /** * Test a frequency of 1 month. */ function testNextRunTimeOneMonth() { // The start date of the edition. $this->edition_day = '05'; $start_date = new DateTime("2012-01-{$this->edition_day} 12:00:00"); // Fake newsletter parent data: sets the interval, start date, and frequency. $newsletter_parent_data = (object) array( 'nid' => 1, 'last_run' => 0, 'activated' => '1', 'send_interval' => 'month', 'interval_frequency' => '1', 'start_date' => $start_date->getTimestamp(), 'next_run' => $start_date->getTimestamp(), // Needs to be set manually when creating new records programmatically. 'stop_type' => '0', 'stop_date' => '0', 'stop_edition' => '0', 'php_eval' => '', 'title' => '[node:title] for [current-date:long]', ); // Number of days to run for. $days = 370; // Index of the days we've done so far. $added_days = 0; // Iterate over days. $last_run_time = $start_date->getTimestamp(); while ($added_days <= $days) { // Create today's date at noon and get the timestamp. $date = clone($start_date); $date->add(new DateInterval("P{$added_days}D")); $timestamp_noon = $date->getTimestamp(); // Get the next run time from the API function we're testing. $next_run_time = simplenews_scheduler_calculate_next_run_time($newsletter_parent_data, $timestamp_noon); //debug($edition_time); if ($next_run_time != $last_run_time) { $offset = _simplenews_scheduler_make_time_offset($newsletter_parent_data->send_interval, $newsletter_parent_data->interval_frequency); $next_run_date = date_add(date_create(date('Y-m-d H:i:s', $last_run_time)), date_interval_create_from_date_string($offset)); $this->assertEqual($next_run_date->getTimestamp(), $next_run_time, t('New next run timestamp has advanced by the expected offset of !offset.', array( '!offset' => $offset, ))); $last_run_time = $next_run_time; } $this->assertTrue($timestamp_noon < $next_run_time, t('Next run time of !next-run is in the future relative to current time of !now.', array( '!next-run' => date("Y-n-d H:i:s", $next_run_time), '!now' => date("Y-n-d H:i:s", $timestamp_noon), ))); $interval = $newsletter_parent_data->interval_frequency * 31 * 24 * 60 * 60; //$this->assertTrue($next_run_time - $timestamp_noon <= $interval, t('Next run timestamp is less than or exactly one month in the future.')); // Create a date object from the timestamp. The '@' makes the constructor // consider the string as a timestamp. $next_run_date = new DateTime(date('Y-m-d H:i:s', $last_run_time)); $d = date_format($next_run_date, 'd'); $this->assertEqual($next_run_date->format('d'), $this->edition_day, t('Next run timestamp is on same day of the month as the start date.')); $this->assertEqual($next_run_date->format('H:i:s'), '12:00:00', t('Next run timestamp is at the same time.')); $added_days++; } // while days } /** * Test a frequency of 2 months. */ function testNextRunTimeTwoMonths() { // The start date of the edition. $this->edition_day = '05'; $start_date = new DateTime("2012-01-{$this->edition_day} 12:00:00"); // Fake newsletter parent data: sets the interval, start date, and frequency. $newsletter_parent_data = (object) array( 'nid' => 1, 'last_run' => 0, 'activated' => '1', 'send_interval' => 'month', 'interval_frequency' => '2', 'start_date' => $start_date->getTimestamp(), 'next_run' => $start_date->getTimestamp(), // Needs to be set manually when creating new records programmatically. 'stop_type' => '0', 'stop_date' => '0', 'stop_edition' => '0', 'php_eval' => '', 'title' => '[node:title] for [current-date:long]', ); // Number of days to run for. $days = 370; // Index of the days we've done so far. $added_days = 0; // Iterate over days. while ($added_days <= $days) { // Create today's date at noon and get the timestamp. $date = clone($start_date); $date->add(new DateInterval("P{$added_days}D")); $timestamp_noon = $date->getTimestamp(); // Get the next run time from the API function we're testing. $next_run_time = simplenews_scheduler_calculate_next_run_time($newsletter_parent_data, $timestamp_noon); //debug($edition_time); $this->assertTrue($timestamp_noon < $next_run_time, t('Next run time of !next-run is in the future relative to current time of !now.', array( '!next-run' => date("Y-n-d H:i:s", $next_run_time), '!now' => date("Y-n-d H:i:s", $timestamp_noon), ))); $interval = $newsletter_parent_data->interval_frequency * 31 * 24 * 60 * 60; $this->assertTrue($next_run_time - $timestamp_noon <= $interval, t('Next run timestamp is less than or exactly two months in the future.')); // Create a date object from the timestamp. The '@' makes the constructor // consider the string as a timestamp. $next_run_date = new DateTime("@$next_run_time"); $d = date_format($next_run_date, 'd'); $this->assertEqual($next_run_date->format('d'), $this->edition_day, t('Next run timestamp is on same day of the month as the start date.')); $added_days++; } // while days } } /** * Unit testing for monthly newsletter edition times. */ class SimpleNewsSchedulerEditionTimeTest extends SimpleNewsSchedulerWebTestCase { /** * Provides information about this test. */ public static function getInfo() { return array( 'name' => 'Edition time: monthly', 'description' => 'Testing edition times for newsletters due every month and every 2 months.', 'group' => 'Simplenews Scheduler', ); } /** * Test a frequency of 1 month. */ function testEditionTimeOneMonth() { // The start date of the edition. $this->edition_day = '01'; $start_date = new DateTime("2012-01-{$this->edition_day} 12:00:00"); // Fake newsletter parent data: sets the interval, start date, and frequency. $newsletter_parent_data = (object) array( 'nid' => 1, 'last_run' => 0, 'activated' => '1', 'send_interval' => 'month', 'interval_frequency' => '1', 'start_date' => $start_date->getTimestamp(), 'stop_type' => '0', 'stop_date' => '0', 'stop_edition' => '0', 'php_eval' => '', 'title' => '[node:title] for [current-date:long]', ); // Number of days to run for. Go just over one year. $days = 370; // Index of the days we've done so far. $added_days = 0; // Iterate over days. while ($added_days <= $days) { // Create today's date at noon and get the timestamp. $date = clone($start_date); $date->add(new DateInterval("P{$added_days}D")); $timestamp_noon = $date->getTimestamp(); $edition_time = simplenews_scheduler_calculate_edition_time($newsletter_parent_data, $timestamp_noon); //debug($edition_time); // Expected edition time is always the {$this->edition_day}th of the month // at noon. $edition_time_formatted = date("Y-m-d H:i:s", $edition_time); $this_month = $date->format('Y-m'); $expected_time_formatted = "{$this_month}-{$this->edition_day} 12:00:00"; $this->assertEqual($edition_time_formatted, $expected_time_formatted, t("Edition time of !edition-time matches expected time of !edition-time-expected at time !now.", array( '!edition-time' => $edition_time_formatted, '!edition-time-expected' => $expected_time_formatted, '!now' => $date->format("Y-m-d H:i:s"), ))); $added_days++; } // while days } /** * Test a frequency of 2 months. */ function testEditionTimeTwoMonths() { // The start date of the edition. $this->edition_day = '01'; $start_date = new DateTime("2012-01-{$this->edition_day} 12:00:00"); // Fake newsletter parent data: sets the interval, start date, and frequency. $newsletter_parent_data = (object) array( 'nid' => 1, 'last_run' => 0, 'activated' => '1', 'send_interval' => 'month', 'interval_frequency' => '2', 'start_date' => $start_date->getTimestamp(), 'stop_type' => '0', 'stop_date' => '0', 'stop_edition' => '0', 'php_eval' => '', 'title' => '[node:title] for [current-date:long]', ); // Number of days to run for. Go just over one year. $days = 370; // Index of the days we've done so far. $added_days = 0; // Iterate over days. while ($added_days <= $days) { // Create today's date at noon and get the timestamp. $date = clone($start_date); $date->add(new DateInterval("P{$added_days}D")); $timestamp_noon = $date->getTimestamp(); $edition_time = simplenews_scheduler_calculate_edition_time($newsletter_parent_data, $timestamp_noon); //debug($edition_time); // Expected edition time is always the {$this->edition_day}th of the month // at noon. // Note here we use 'n' for the month to avoid having to pad. $edition_time_formatted = date("Y-n-d H:i:s", $edition_time); $this_year = $date->format('Y'); $this_month = $date->format('n'); // We start in January and run 2-monthly. // We want the number of elapsed months, module 2 (the frequency), to know // the remainder to subtract. $elapsed_mod = ($this_month - 1) % 2; $expected_month = $this_month - $elapsed_mod; $expected_time_formatted = "{$this_year}-{$expected_month}-{$this->edition_day} 12:00:00"; $this->assertEqual($edition_time_formatted, $expected_time_formatted, t("Edition time of !edition-time matches expected time of !edition-time-expected at time !now.", array( '!edition-time' => $edition_time_formatted, '!edition-time-expected' => $expected_time_formatted, '!now' => $date->format("Y-m-d H:i:s"), ))); $added_days++; } // while days } } /** * Unit testing for simplenews_scheduler_get_newsletters_due(). */ class SimpleNewsSchedulerEditionDueTest extends SimpleNewsSchedulerWebTestCase { protected $privileged_user; /** * Provides information about this test. */ public static function getInfo() { return array( 'name' => 'Edition due test', 'description' => 'Functional tests for simplenews_scheduler_get_newsletters_due().', 'group' => 'Simplenews Scheduler', ); } /** * Declares the module dependencies and create data for the test. */ function setUp() { parent::setUp(); $this->privileged_user = $this->drupalCreateUser(array( 'access content', 'administer nodes', 'create simplenews content', 'edit own simplenews content', 'send newsletter', 'send scheduled newsletters', 'overview scheduled newsletters', )); $this->drupalLogin($this->privileged_user); // The start date of the edition. This is on 5 January so that we get some // days in either month, and at at noon to keep things simple. $this->edition_day = '05'; $start_date = new DateTime("2012-01-{$this->edition_day} 12:00:00"); // Create a parent newsletter node. $node = (object) NULL; $node->type = 'simplenews'; $node->title = 'Parent'; $node->uid = 1; $node->status = 1; $node->language = 'und'; // Safe to assume there is only one taxonomy term and it's the newsletter. $node->field_simplenews_term['und'][0]['tid'] = 1; // Workaround for http://drupal.org/node/1480258 $node->nid = NULL; node_save($node); // Grumble grumble there's no node saving API in our module! // @see http://drupal.org/node/1480328 to clean this up. $node->simplenews_scheduler = (object) array( 'nid' => $node->nid, 'last_run' => 0, 'activated' => '1', 'send_interval' => 'month', 'interval_frequency' => '1', 'start_date' => $start_date->getTimestamp(), 'next_run' => $start_date->getTimestamp(), // Needs to be set manually when creating new records programmatically. 'stop_type' => '0', 'stop_date' => '0', 'stop_edition' => '0', 'php_eval' => '', 'title' => '[node:title] for [current-date:long]', ); $record = (array) $node->simplenews_scheduler; $query = db_merge('simplenews_scheduler'); $query->key(array( 'nid' => $record['nid'], )) ->fields($record) ->execute(); // Store the node ID for the test to use. $this->parent_nid = $node->nid; } /** * Test simplenews_scheduler_get_newsletters_due(). */ function testEditionsDue() { // Get the node id of the parent newsletter node. $parent_nid = $this->parent_nid ; // But just check it exists for sanity. $this->drupalGet("node/$parent_nid"); // Simulate cron running daily at half past 12 so that an edition due at // 12 noon should be picked up. $start_date = new DateTime("2012-01-01 12:00:00"); $time_offsets = array( 'before' => "-1 hour", 'after' => "+1 hour", ); // Number of days to run cron for. $days = 150; // Index of the days we've done so far. $added_days = 0; // Iterate over days. while ($added_days <= $days) { // Create today's date at noon and get the timestamp. $date = clone($start_date); $date->add(new DateInterval("P{$added_days}D")); $timestamp_noon = $date->getTimestamp(); // We simulate running cron one hour before and one hour after noon. foreach ($time_offsets as $offset_key => $offset) { // Create a timestamp based on noon + the offset. // This gives us either 11:00 or 13:00 on the current day. $timestamp = strtotime($offset, $timestamp_noon); // debug("base: $timestamp_noon, off: $offset, result: $timestamp"); // Get the list of newsletters due. $due = simplenews_scheduler_get_newsletters_due($timestamp); // An edition is due if it's 13:00 on the edition day. $formatted = date(DATE_RFC850, $timestamp); if ($offset_key == 'after' && date('d', $timestamp) == $this->edition_day) { $this->assertTrue(isset($due[$parent_nid]), "Edition due at day $added_days, $formatted, $timestamp"); } else { $this->assertFalse(isset($due[$parent_nid]), "Edition not due at day $added_days, $formatted, $timestamp"); } // Get some debug output to figure out what is going on in // simplenews_scheduler_get_newsletters_due(). $intervals['hour'] = 3600; $intervals['day'] = 86400; $intervals['week'] = $intervals['day'] * 7; $intervals['month'] = $intervals['day'] * date_days_in_month(date('Y', $timestamp), date('m', $timestamp)); if (isset($due[$parent_nid])) { // Output what we got back from the function. // debug($due); $newsletter_parent_data = $due[$parent_nid]; $edition_time = simplenews_scheduler_calculate_edition_time($newsletter_parent_data, $timestamp); $eid = _simplenews_scheduler_new_edition($newsletter_parent_data->nid, $timestamp); // Output the last_run as a sanity check. $result = db_query("SELECT last_run FROM {simplenews_scheduler} WHERE nid = :nid", array(':nid' => $parent_nid)); $last_run = $result->fetchField(); $formatted = date(DATE_RFC850, $last_run); // debug("Last run: $formatted, $last_run"); // Output the calculated edition time. $formatted = date(DATE_RFC850, $edition_time); // debug("Edition time: $formatted, $edition_time"); // Check the edition time is 12:00. $this->assertEqual(date('H:i', $edition_time), '12:00', t('Edition time is at 12:00.')); // Fake sending it: update the 'last_run' for subsequent iterations. db_update('simplenews_scheduler') ->fields(array('last_run' => $timestamp)) ->condition('nid', $parent_nid) ->execute(); // Update the edition record. simplenews_scheduler_scheduler_update($newsletter_parent_data, $timestamp); // Check the node exists. $this->drupalGet("node/$eid"); } // handling the request for a new edition } // foreach offset timestamp // Increment our counter. $added_days++; } // foreach day } } /** * Test edition time around DST changes. * * Test that simplenews_scheduler_calculate_edition_time() returns an edition * timestamp whose time in the local timezone remains the same after the * timezone changes over to Daylight Saving Time. */ class SimpleNewsSchedulerDaylightSavingSwitchTest extends SimpleNewsSchedulerWebTestCase { protected $privileged_user; /** * Provides information about this test. */ public static function getInfo() { return array( 'name' => 'Daylight Saving Time', 'description' => 'Functional tests for DST changes.', 'group' => 'Simplenews Scheduler', ); } /** * Test edition time after DST changes for a monthly newsletter. * * @todo: generalize this for other intervals. */ function testDSTMonthly() { $timezone_name = date_default_timezone(); //debug($timezone_name); // Create a last run time before DST begins, and a now time after. // Use date_create() rather than strtotime so that we create a date at the // given time *in the current timezone* rather than UTC. $last_run_date = new DateTime("01-Mar-12 12:00:00"); $now_date = date_create("05-Apr-12 12:00:00"); //debug('last run date TZ: ' . $last_run_date->getTimezone()->getName()); //debug('now date TZ: ' . $now_date->getTimezone()->getName()); // Fake up newsletter data. $newsletter_parent_data = (object) array( 'last_run' => $last_run_date->getTimestamp(), 'send_interval' => 'month', 'interval_frequency' => 1, ); // Get the edition time. $edition_time = simplenews_scheduler_calculate_edition_time($newsletter_parent_data, $now_date->getTimestamp()); $edition_date = date_create('@' . $edition_time); //debug($edition_date->format(DATE_ATOM)); // Format the edition time. $edition_time_formatted = format_date($edition_time, 'custom', DATE_ATOM); $edition_hour_formatted = format_date($edition_time, 'custom', 'H:i'); $this->assertEqual($edition_hour_formatted, '12:00', t('Edition time is at 12:00 in the local timezone; full edition time is %time.', array( '%time' => $edition_time_formatted, ))); } }