<?php

/**
 * @file
 * Unit tests for the redirect module.
 */

class RedirectTestHelper extends DrupalWebTestCase {
  function setUp(array $modules = array()) {
    array_unshift($modules, 'redirect');
    parent::setUp($modules);
  }

  protected function assertRedirect($redirect) {
    $source_url = url($redirect->source, array('absolute' => TRUE) + $redirect->source_options);
    $redirect_url = url($redirect->redirect, array('absolute' => TRUE) + $redirect->redirect_options);
    $this->drupalGet($source_url);
    $this->assertEqual($this->getUrl(), $redirect_url, t('Page %source was redirected to %redirect.', array('%source' => $source_url, '%redirect' => $redirect_url)));

    // Reload the redirect.
    if (!empty($redirect->rid)) {
      return redirect_load($redirect->rid);
    }
  }

  protected function assertNoRedirect($redirect) {
    $source_url = url($redirect->source, array('absolute' => TRUE) + $redirect->source_options);
    $this->drupalGet($source_url);
    $this->assertEqual($this->getUrl(), $source_url, t('Page %url was not redirected.', array('%url' => $source_url)));
  }

  /**
   * Add an URL redirection
   *
   * @param $source
   *   A source path.
   * @param $redirect
   *   A redirect path.
   */
  protected function addRedirect($source_path, $redirect_path, array $redirect = array()) {
    $source_parsed = redirect_parse_url($source_path);
    $redirect['source'] = $source_parsed['url'];
    if (isset($source_parsed['query'])) {
      $redirect['source_options']['query'] = $source_parsed['query'];
    }

    $redirect_parsed = redirect_parse_url($redirect_path);
    $redirect['redirect'] = $redirect_parsed['url'];
    if (isset($redirect_parsed['query'])) {
      $redirect['redirect_options']['query'] = $redirect_parsed['query'];
    }
    if (isset($redirect_parsed['fragment'])) {
      $redirect['redirect_options']['fragment'] = $redirect_parsed['fragment'];
    }

    $redirect_object = new stdClass();
    redirect_object_prepare($redirect_object, $redirect);
    redirect_save($redirect_object);
    return $redirect_object;
  }

  protected function assertPageCached($url, array $options = array()) {
    $options['absolute'] = TRUE;
    $url = url($url, $options);
    $cache = cache_get($url, 'cache_page');
    $this->assertTrue($cache, t('Page %url was cached.', array('%url' => $url)));
    return $cache;
  }

  protected function assertPageNotCached($url, array $options = array()) {
    $options['absolute'] = TRUE;
    $url = url($url, $options);
    $cache = cache_get($url, 'cache_page');
    $this->assertFalse($cache, t('Page %url was not cached.', array('%url' => $url)));
  }

  protected function assertHeader($name, $expected, $headers = NULL) {
    if (!isset($headers)) {
      $headers = $this->drupalGetHeaders();
      $name = strtolower($name);
    }
    return $this->assertIdentical($headers[$name], $expected);
  }
}

class RedirectUnitTest extends RedirectTestHelper {
  public static function getInfo() {
    return array(
      'name' => 'Redirect unit tests',
      'description' => 'Test basic functions and functionality.',
      'group' => 'Redirect',
    );
  }

  /**
   * Test the redirect_compare_array_recursive() function.
   */
  function testCompareArrayRecursive() {
    $haystack = array('a' => 'aa', 'b' => 'bb', 'c' => array('c1' => 'cc1', 'c2' => 'cc2'));
    $cases = array(
      array('query' => array('a' => 'aa', 'b' => 'invalid'), 'result' => FALSE),
      array('query' => array('b' => 'bb', 'b' => 'bb'), 'result' => TRUE),
      array('query' => array('b' => 'bb', 'c' => 'invalid'), 'result' => FALSE),
      array('query' => array('b' => 'bb', 'c' => array()), 'result' => TRUE),
      array('query' => array('b' => 'bb', 'c' => array('invalid')), 'result' => FALSE),
      array('query' => array('b' => 'bb', 'c' => array('c2' => 'invalid')), 'result' => FALSE),
      array('query' => array('b' => 'bb', 'c' => array('c2' => 'cc2')), 'result' => TRUE),
    );
    foreach ($cases as $index => $case) {
      $this->assertEqual($case['result'], redirect_compare_array_recursive($case['query'], $haystack));
    }
  }

  /**
   * Test redirect_sort_recursive().
   */
  function testSortRecursive() {
    $test_cases = array(
      array(
        'input' => array('b' => 'aa', 'c' => array('c2' => 'aa', 'c1' => 'aa'), 'a' => 'aa'),
        'expected' => array('a' => 'aa', 'b' => 'aa', 'c' => array('c1' => 'aa', 'c2' => 'aa')),
        'callback' => 'ksort',
      ),
    );
    foreach ($test_cases as $index => $test_case) {
      $output = $test_case['input'];
      redirect_sort_recursive($output, $test_case['callback']);
      $this->assertIdentical($output, $test_case['expected']);
    }
  }

  /**
   * Test redirect_parse_url().
   */
  function testParseURL() {
    //$test_cases = array(
    //  array(
    //    'input' => array('b' => 'aa', 'c' => array('c2' => 'aa', 'c1' => 'aa'), 'a' => 'aa'),
    //    'expected' => array('a' => 'aa', 'b' => 'aa', 'c' => array('c1' => 'aa', 'c2' => 'aa')),
    //  ),
    //);
    //foreach ($test_cases as $index => $test_case) {
    //  $output = redirect_parse_url($test_case['input']);
    //  $this->assertIdentical($output, $test_case['expected']);
    //}
  }
}

class RedirectFunctionalTest extends RedirectTestHelper {
  private $admin_user;

  public static function getInfo() {
    return array(
      'name' => 'Redirect functional tests',
      'description' => 'Test interface functionality.',
      'group' => 'Redirect',
    );
  }

  function setUp(array $modules = array()) {
    parent::setUp($modules);

    $this->admin_user = $this->drupalCreateUser(array('administer redirects', 'access site reports', 'access content', 'create article content', 'edit any article content', 'create url aliases'));
    $this->drupalLogin($this->admin_user);
  }

  function test404Interface() {
    // Check that 404 pages do get add redirect links for admin users.
    $this->drupalGet('invalid-path1');
    $this->drupalGet('invalid-path2');
    $this->assertLink('Add URL redirect from this page to another location');

    // Check that 403 pages do not get the add redirect link at all.
    $this->drupalGet('admin/config/system/actions');
    $this->assertNoLink('Add URL redirect from this page to another location');

    $this->drupalGet('admin/reports/page-not-found');
    $this->clickLink('Fix 404 pages with URL redirects');

    // Check that normal users do not see the add redirect link on 404 pages.
    $this->drupalLogout();
    $this->drupalGet('invalid-path3');
    $this->assertNoLink('Add an URL redirect from this page to another location');
  }

  function testPageCache() {
    // Set up cache variables.
    variable_set('cache', 1);
    $edit = array(
      'redirect_page_cache' => TRUE,
      'redirect_purge_inactive' => 604800,
    );
    $this->drupalPost('admin/config/search/redirect/settings', $edit, 'Save configuration');
    $this->assertText('The configuration options have been saved.');
    $this->drupalLogout();

    // Add a new redirect.
    $redirect = $this->addRedirect('redirect', 'node');
    $this->assertEqual($redirect->access, 0);
    $this->assertEqual($redirect->count, 0);
    $this->assertPageNotCached('redirect');

    // Perform the redirect and check that last_used
    $redirect = $this->assertRedirect($redirect);
    $this->assertEqual($redirect->count, 1);
    $this->assertTrue($redirect->access > 0);
    $cache = $this->assertPageCached('redirect');
    $this->assertHeader('Location', url('node', array('absolute' => TRUE)), $cache->data['headers']);
    $this->assertHeader('X-Redirect-ID', $redirect->rid, $cache->data['headers']);

    // Set a redirect to not used in a while and disable running bootstrap
    // hooks during cache page serve. Running cron to remove inactive redirects
    // should not remove since they cannot be tracked.
    $redirect->access = 1;
    redirect_save($redirect);
    variable_set('page_cache_invoke_hooks', FALSE);
    $this->cronRun();
    $this->assertRedirect($redirect);

    $redirect->access = 1;
    redirect_save($redirect);
    variable_set('page_cache_invoke_hooks', TRUE);
    $this->cronRun();
    $this->assertNoRedirect($redirect);
  }

  function testPathChangeRedirects() {
    // Create an initial article node with a path alias.
    $node = $this->drupalCreateNode(array('type' => 'article', 'path' => array('alias' => 'first-alias')));

    // Change the node's alias will create an automatic redirect from 'first-alias' to the node.
    $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'second-alias'), t('Save'));
    $this->drupalGet('first-alias');
    $this->assertText($node->title);

    $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'first-alias'), t('Save'));
    $this->assertResponse(200, "Changing node's alias back to 'first-alias' does not break page load with a circular redirect.");
    $this->assertNoText('Infinite redirect loop prevented.');
    $this->drupalGet('second-alias');
    $this->assertText($node->title);

    $this->drupalPost("node/{$node->nid}/edit", array('path[alias]' => 'second-alias'), t('Save'));
    $this->assertResponse(200, "Changing node's alias back to 'second-alias' does not break page load with a circular redirect.");
    $this->assertNoText('Infinite redirect loop prevented.');
    // Check that first-alias redirect has been re-enabled.
    $this->drupalGet('first-alias');
    $this->assertText($node->title);
  }

  function testPathAddOverwriteRedirects() {
    // Create an initial article node with a path alias.
    $first_node = $this->drupalCreateNode(array('type' => 'article', 'path' => array('alias' => 'first-alias')));
    // Change the node's alias will create an automatic redirect from 'first-alias' to the node.
    $this->drupalPost("node/{$first_node->nid}/edit", array('path[alias]' => 'second-alias'), t('Save'));

    // Now create a second article node with the same alias as the redirect
    // created above.
    $second_node = $this->drupalCreateNode(array('type' => 'article', 'path' => array('alias' => 'first-alias')));

    // Visit the path 'first-alias' which should be an alias for $second_node.
    $this->drupalGet('first-alias');
    $this->assertNoText($first_node->title, 'Adding a new path alias that matches an existing redirect disables the redirect.');
    $this->assertText($second_node->title, 'Adding a new path alias that matches an existing redirect disables the redirect.');
  }

  function testDisableEnableRedirect() {
    // Add a new redirect.
    $redirect = $this->addRedirect('redirect', 'node');
    // Check that it is enabled.
    $this->assertEqual($redirect->status, 1);

    // Disable the redirect.
    $edit = array('status' => FALSE);
    $this->drupalPost("admin/config/search/redirect/edit/{$redirect->rid}", $edit, t('Save'));
    $redirect = redirect_load($redirect->rid);
    // Check that it has been disabled.
    $this->assertEqual($redirect->status, 0);
    $this->drupalGet("admin/config/search/redirect/edit/{$redirect->rid}");
    $this->assertNoFieldChecked('edit-status', 'status is unchecked');
    $this->assertNoRedirect($redirect);

    // Re-enable the redirect.
    $edit = array('status' => 1);
    $this->drupalPost("admin/config/search/redirect/edit/{$redirect->rid}", $edit, t('Save'));
    $this->assertRedirect($redirect);
  }
}