<?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(array $redirect) {
    $redirect += array(
      'source_options' => array(),
      'redirect_options' => array(),
    );
    $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(array $redirect) {
    $redirect += array(
      'source_options' => array(),
    );
    $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) $redirect;
    redirect_save($redirect);
    return $redirect;
  }

  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'));
    $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 an 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 an 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);
  }
}
