You are here

deploy.test in Deploy - Content Staging 7.2

Same filename and directory in other branches
  1. 7.3 deploy.test

Deployment tests.

File

deploy.test
View source
<?php

/**
 * @file
 * Deployment tests.
 */

/**
 * Helper class.
 */
class DeployWebTestCase extends DrupalWebTestCase {

  // Array to keep presaved variables.
  private $servers = array();
  protected $manage_add_to_plan = TRUE;
  protected $origin_modules = array(
    'entity',
    'ctools',
    'uuid',
    'deploy',
    'deploy_ui',
    'deploy_example',
  );
  protected $endpoint_modules = array(
    'entity',
    'ctools',
    'uuid',
    'services',
    'rest_server',
    'uuid_services',
    'uuid_services_example',
  );

  /**
   * Set up all sites.
   *
   * For some tests we don't need the multisite environment, but still want
   * to use common methods in this test case.
   */
  protected function setUp($simple = FALSE) {
    $this->profile = 'standard';
    if ($simple) {
      parent::setUp('entity', 'deploy');
      return;
    }

    // Set up our origin site.
    $this
      ->setUpSite('deploy_origin', $this->origin_modules);

    // Switch back to original site to be able to set up a new site.
    $this
      ->switchSite('deploy_origin', 'simpletest_original_default');

    // Set up one endpoint site.
    $this
      ->setUpSite('deploy_endpoint', $this->endpoint_modules);

    // This is the user that will be used to authenticate the deployment between
    // the site. We add it to $GLOBALS so we can access the user info on the
    // origin site and configure the endpoint object with its username and
    // password.
    $GLOBALS['endpoint_user'] = $this
      ->drupalCreateUser(array(
      'access content',
      'create article content',
      'edit any article content',
      'administer users',
      'administer taxonomy',
    ));

    // Switch back to origin site where we want to start.
    $this
      ->switchSite('deploy_endpoint', 'deploy_origin');

    // Edit an endpoint object to point to our endpoint site.
    $this
      ->editEndpoint('deploy_example_endpoint', 'deploy_endpoint');
  }

  /**
   * Tear down all sites.
   *
   * @todo Make this transparent of how many sites we've set up.
   */
  protected function tearDown() {

    // Tear down current site.
    parent::tearDown();

    // We are making it easy for us (but a bit hacky) by using this method to
    // clean out other environments that we've created.
    simpletest_clean_database();
    simpletest_clean_temporary_directories();
    registry_rebuild();
    cache_clear_all('simpletest', 'cache');
  }

  /**
   * Set up a new site.
   */
  protected function setUpSite($key, $modules) {
    static $original = array();

    // How we can call parent::foo() changed in PHP 5.3.
    if (version_compare(PHP_VERSION, '5.3.0', '>=')) {
      call_user_func_array('parent::setUp', $modules);
    }
    else {
      call_user_func_array(array(
        $this,
        'parent::setUp',
      ), $modules);
    }
    $this
      ->saveState($key);
    variable_set('deploy_site_hash', md5(time()));

    // Save original settings after first setUp(). We need to be able to restore
    // them after subsequent calls to setUp().
    if (empty($original)) {
      $original = array(
        $this->originalLanguage,
        $this->originalLanguageDefault,
        $this->originalFileDirectory,
        $this->originalProfile,
        $this->originalShutdownCallbacks,
      );
    }

    // Restore the original settings.
    list($this->originalLanguage, $this->originalLanguageDefault, $this->originalFileDirectory, $this->originalProfile, $this->originalShutdownCallbacks) = $original;
    $this
      ->setUpSiteSpecifics($key);
  }

  /**
   * Method for site specific setup.
   */
  protected function setUpSiteSpecifics($key) {

    // Empty but available for classes to extend.
  }

  /**
   * Switch to a specific site.
   */
  protected function switchSite($from, $to) {

    // This is used to test the switch.
    $old_site_hash = variable_get('deploy_site_hash', '');

    // Switch database connection. This is where the magic happens.
    Database::renameConnection('default', $from);
    Database::renameConnection($to, 'default');

    // Reset static caches, so sites doesn't share them.
    drupal_static_reset();

    // Since variables ($conf) lives in the global namespace, we need to
    // reinitalize them to not make sites share variables.
    cache_clear_all('*', 'cache_bootstrap');
    $GLOBALS['conf'] = variable_initialize();

    // No need to restore anything if we are switching to the original site.
    if ($to != 'simpletest_original_default') {
      $this
        ->restoreState($to);

      // Test the switch.
      $new_site_hash = variable_get('deploy_site_hash', '');
      $this
        ->assertNotEqual($old_site_hash, $new_site_hash, t('Switch to site %key was successful.', array(
        '%key' => $to,
      )));
    }
  }

  /**
   * Save state.
   */
  protected function saveState($key) {
    if (!isset($this->sites[$key])) {
      $this->sites[$key] = new stdClass();
    }
    $this->sites[$key]->cookieFile = $this->cookieFile;
    $this->sites[$key]->databasePrefix = $this->databasePrefix;
    $this->sites[$key]->curlHandle = $this->curlHandle;
    $this->sites[$key]->cookieFile = $this->cookieFile;
  }

  /**
   * Restore state.
   */
  protected function restoreState($key) {
    $this->cookieFile = $this->sites[$key]->cookieFile;
    $this->databasePrefix = $this->sites[$key]->databasePrefix;
    $this->curlHandle = $this->sites[$key]->curlHandle;
    $this->cookieFile = $this->sites[$key]->cookieFile;
  }

  /**
   * Overridden method adjusted to work with this testing framework.
   */
  protected function cronRun() {

    // The "regular" way of running cron in SimpleTest doesn't work for us,
    // since we have a very complex setup with a several "virtual" SimpleTest
    // sites. This is an easier way of running cron. It doesn't cover as many
    // test cases, but it's good enough.
    drupal_cron_run();
  }

  /**
   * Edit a plan.
   */
  protected function editPlan($plan_name, $params = array()) {
    $plan = deploy_plan_load($plan_name);
    foreach ($params as $key => $value) {
      $plan->{$key} = $value;
    }
    ctools_export_crud_save('deploy_plans', $plan);
  }

  /**
   * Edit an endpoint to make it point to a specific site in this test
   * environment.
   *
   * This is needed in order for deployments to be able to reach sites in this
   * test environment.
   */
  protected function editEndpoint($endpoint_name, $site_key) {
    $endpoint = deploy_endpoint_load($endpoint_name);
    $endpoint->authenticator_config = array(
      'username' => $GLOBALS['endpoint_user']->name,
      'password' => $GLOBALS['endpoint_user']->pass_raw,
    );
    $endpoint->service_config['url'] = url('api', array(
      'absolute' => TRUE,
    ));
    $user_agent = drupal_generate_test_ua($this->sites[$site_key]->databasePrefix);
    $endpoint->service_config['headers'] = array(
      'User-Agent' => $user_agent,
    );
    ctools_export_crud_save('deploy_endpoints', $endpoint);
  }

  /**
   * Deploy the plan.
   *
   * @param string $name
   *   Name of the deployment plan.
   * @return type
   */
  protected function deployPlan($name) {
    if (empty($name)) {
      return;
    }
    $plan = deploy_plan_load($name);
    $plan
      ->deploy();

    // Some processors depends on cron.
    $this
      ->cronRun();
  }

  // @ignore comment_comment_see:comment

  /**
   * Code taken from TaxonomyWebTestCase::createTerm() since we can't extend
   * that test case. Some simplifications are made though.
   *
   * @todo
   *   This will probably not work when the Testing profile is used. Then we
   *   need to create the vocabulary manually.
   *
   * @see TaxonomyWebTestCase::createTerm()
   */
  protected function createTerm() {
    $term = new stdClass();
    $term->name = $this
      ->randomName();
    $term->description = $this
      ->randomName();

    // Use the first available text format.
    $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)
      ->fetchField();

    // For our test cases it's enough to rely on the standard 'tags' vocabulary.
    $term->vid = 1;
    taxonomy_term_save($term);
    return $term;
  }

  /**
   * This method runs a deployment scenario where we have one production site
   * (the endpoint) and a staging site (the origin).
   *
   * Both sites are "out of sync" content wise (as production/stage always are)
   * but deployments of new and updated content are still possible.
   *
   * @todo
   *   Conditionally test references modules too, since they are very likely too
   *   be used on most sites.
   *
   * @todo
   *   Test with translations too.
   */
  protected function runScenario($plan_name) {

    // Switch to our production site.
    $this
      ->switchSite('deploy_origin', 'deploy_endpoint');

    // Intentionally force the sites out of sync by creating some content that
    // only exists in production.
    $user = $this
      ->drupalCreateUser();
    $term = $this
      ->createTerm();
    $this
      ->drupalCreateNode(array(
      'type' => 'article',
      'uid' => $user->uid,
      'field_tags' => array(
        LANGUAGE_NONE => array(
          array(
            'tid' => $term->tid,
          ),
        ),
      ),
    ));

    // Switch to our staging site and push some new content.
    $this
      ->switchSite('deploy_endpoint', 'deploy_origin');
    $user_stage = $this
      ->drupalCreateUser();
    $term_stage = $this
      ->createTerm();
    $node_title_orig = $this
      ->randomString();
    $node_stage = $this
      ->drupalCreateNode(array(
      'type' => 'article',
      'title' => $node_title_orig,
      'uid' => $user_stage->uid,
      'field_tags' => array(
        LANGUAGE_NONE => array(
          array(
            'tid' => $term_stage->tid,
          ),
        ),
      ),
    ));
    list($nid_stage, $vid_stage) = entity_extract_ids('node', $node_stage);

    // If this base class should manage adding to the plan is configurable to
    // allow for modules like deploy_auto_plan.module to use other ways and
    // still be able to extend this class.
    if ($this->manage_add_to_plan) {

      // Now add the node to the plan.
      deploy_manager_add_to_plan($plan_name, 'node', $node_stage);
    }

    // Test that the node was added to the plan with the right identifiers.
    $count = db_query('SELECT COUNT(revision_id) FROM {deploy_manager_entities} WHERE plan_name = :plan_name AND entity_id = :nid AND revision_id = :vid', array(
      ':plan_name' => $plan_name,
      ':nid' => $nid_stage,
      ':vid' => $vid_stage,
    ))
      ->fetchField();
    $this
      ->assertEqual($count, 1, 'The entity was added to the deployment plan');

    // This will deploy the node only. But with dependencies (like the author
    // and the term).
    $this
      ->deployPlan($plan_name);

    // Switch to our production site and make sure the content was pushed.
    $this
      ->switchSite('deploy_origin', 'deploy_endpoint');

    // Load the deployed entities to test. Since we don't know their primary IDs
    // here on the production site we look them up using their UUIDs.
    $users = entity_uuid_load('user', array(
      $user_stage->uuid,
    ), array(), TRUE);
    $terms = entity_uuid_load('taxonomy_term', array(
      $term_stage->uuid,
    ), array(), TRUE);
    $nodes = entity_uuid_load('node', array(
      $node_stage->uuid,
    ), array(), TRUE);
    $user_prod = reset($users);
    $term_prod = reset($terms);
    $node_prod = reset($nodes);

    // Test to see if all entities are locally different, but universally the
    // same. They should be, since we forced the sites out of sync earlier.
    //
    // Test the node author.
    $test = $user_stage->uuid == $user_prod->uuid && $user_stage->uid != $user_prod->uid;
    $this
      ->assertTrue($test, 'New node author was deployed successfully.');

    // Test the term.
    $test = $term_stage->uuid == $term_prod->uuid && $term_stage->tid != $term_prod->tid;
    $this
      ->assertTrue($test, 'New term was deployed successfully.');

    // Test the node itself.
    $test = $node_stage->uuid == $node_prod->uuid && $node_stage->nid != $node_prod->nid;
    $this
      ->assertTrue($test, 'New node was deployed successfully.');

    // Test if the dependencies got attached to the node.
    $this
      ->assertEqual($node_prod->uid, $user_stage->uuid, 'Node author was successfully attached to node.');
    $this
      ->assertEqual($node_prod->field_tags[LANGUAGE_NONE][0]['tid'], $term_stage->uuid, 'Term was successfully attached to node.');

    // Now switch back to staging site and make updates to all entities to see
    // if updates is comming through, when a new deployment is done.
    $this
      ->switchSite('deploy_endpoint', 'deploy_origin');

    // Update the node.
    $node_stage->title = $this
      ->randomString();
    node_save($node_stage);

    // TODO: Update more entities in the dependency chain of the node.
    // Since we have a new revision ID we have to add the node again.
    if ($this->manage_add_to_plan) {
      deploy_manager_add_to_plan($plan_name, 'node', $node_stage);
    }

    // Now deploy the node again.
    $this
      ->deployPlan($plan_name);

    // Switch back to production to assert the changes.
    $this
      ->switchSite('deploy_origin', 'deploy_endpoint');
    $nodes = entity_uuid_load('node', array(
      $node_stage->uuid,
    ), array(), TRUE);
    $node_prod = reset($nodes);
    $test = $node_prod->title == $node_stage->title && $node_prod->title != $node_title_orig;
    $this
      ->assertTrue($test, 'Node was successfully updated after new deployment.');
  }

}

/**
 * Test a full deployment between two sites, based on the provided example
 * feature.
 */
class DeploySimpleDeploymentTestCase extends DeployWebTestCase {

  /**
   * {@inheritdoc}
   */
  public static function getInfo() {
    return array(
      'name' => 'Simple deployment setup',
      'description' => 'Test a full deployment between two sites, based on the provided example feature.',
      'group' => 'Deployment',
    );
  }

  // @ignore comment_comment_see:comment

  /**
   * Simple deployment scenario.
   *
   * @see DeployWebTestCase::runScenario()
   */
  function testDeployment() {
    $this
      ->runScenario('deploy_example_plan');
  }

}

/**
 * Test a full deployment between two sites with the Views aggregator and the
 * Queue processor.
 */
class DeployQueuedDeploymentTestCase extends DeployWebTestCase {

  /**
   * {@inheritdoc}
   */
  public static function getInfo() {
    return array(
      'name' => 'Queued deployment setup',
      'description' => 'Test a full deployment between two sites, based on the provided example feature with the Queue API processor.',
      'group' => 'Deployment',
    );
  }

  /**
   * {@inheritdoc}
   */
  function testDeployment() {
    $this
      ->editPlan('deploy_example_plan', array(
      'processor_plugin' => 'DeployProcessorQueue',
    ));
    $this
      ->runScenario('deploy_example_plan');
  }

}

Classes

Namesort descending Description
DeployQueuedDeploymentTestCase Test a full deployment between two sites with the Views aggregator and the Queue processor.
DeploySimpleDeploymentTestCase Test a full deployment between two sites, based on the provided example feature.
DeployWebTestCase Helper class.