Repository: phase2/grunt-drupal-tasks (js)

Issue:
Create a facility to support multiple environments
During the primary build of a project there are often multiple domains basepaths. Development, QA, Demo, Production, Feature-branch instances, and so on. Any of these might need meaningful behat and other sorts of testing processes.

We need a method to make the basepath adapative or swappable via configuration. Making basepath a default configuration with overrides from command-line might be the most natural, for example: ```grunt --env=grayside.example.com``` or ```GRUNT_BASEPATH=grayside.example.com```. The latter leverages environment parameters, which may allow for automating how this happens without explicit developer action.

Pull request:
Environment-injected Base URL
This PR adds a new `domain` key to facilitate shipping a default environment parameter. When used alongside siteUrls with the appropriate configuration templating, this allows absolute URLs to each site to be adaptive. Domain can be specified in the `Gruntfile.json` for your project, but an environment variable `GRUNT_DRUPAL_DOMAIN` will take precedence.

A new utility module is created `lib/gdt.js` which can be loaded with `var GDT = require('lib/gdt');`. This allows test-oriented tasks to submit a list of paths and get a set of absolute URLs for a particular baseUrl. `GDT.expandUrls(grunt.config('config.domain'), ['/', 'admin'])`

This change does not modify documentation, I thought to leave that for the first follow-up leveraging the system.

This PR fixes #32.

Patch:
diff --git a/CONFIG.md b/CONFIG.md
index 96f8108..206039a 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -160,6 +160,26 @@ analysis and validation tools.
 **buildPaths.temp**: The directory that should be used for temporary build
 artifacts. This can be a subdirectory of buildPaths.html.
 
+**domain**: The domain of the site without protocol. It may also be a Base URL
+(with a path prefix) so long as the protocol is excluded. This domain is used
+to construct complete URLs for testing purposes. This may be overridden by the
+`GDT_DOMAIN` environment variable, and if neither is set will fall back to the
+environment's hostname. If a domain is specified, we recommend it is lined up
+with any standard set for local development, as development processes should
+not default to hitting a production or staging environment.
+
+**siteUrls**: (Optional) A map of Drupal subsite names to the URLs by which
+each can be accessed for end-to-end testing by tools such as Behat. For clarity
+please align the site names (keys) with any Drush Site Aliases. If no siteUrls
+are specified it will default to `http://<%= config.domain %>`.
+
+```
+  "siteUrls": {
+    "default": "http://dev-site.local",
+    "subsite": "http://sub.dev-site.local"
+  },
+```
+
 ### Analyze Settings
 
 This is an example of the settings for analyze tasks:
@@ -187,10 +207,6 @@ This is an example of the settings for Behat tasks:
 
 ```
 {
-  "siteUrls": {
-    "default": "http://dev-site.local",
-    "subsite": "http://sub.dev-site.local"
-  },
   "behat": {
     "flags": "--tags '~@javascript'",
     "subsite": {
@@ -201,9 +217,6 @@ This is an example of the settings for Behat tasks:
 }
 ```
 
-**siteUrls**: A map of Drupal subsite names to the URLs by which each can be
-accessed for testing by Behat.
-
 **behat.\<siteurl\>**: A map of Drupal subsite names to a configuration object,
 which will extend the defaults passed to
 [grunt-parallel-behat](https://github.com/linusnorton/grunt-parallel-behat)
diff --git a/README.md b/README.md
index e4fd61a..506c4ed 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,10 @@ These flags are not yet documented as part of the `grunt help` command.
 ### Environment Options
 
 These environment variables will override other options.
-* `GRUNT_DRUPAL_QUIET`: If evaluated truthy, will suppress all desktop notifications.
+* `GRUNT_DRUPAL_QUIET`: If evaluated truthy, will suppress all desktop
+notifications.
+* `GDT_DOMAIN`: Specify the base URL for live system testing. Falls back to a
+setting in the project's Gruntconfig.json, then hostname if not set.
 
 ## Setting Up and Extending
 
diff --git a/bootstrap.js b/bootstrap.js
index a5584cf..e99e2ae 100644
--- a/bootstrap.js
+++ b/bootstrap.js
@@ -1,5 +1,3 @@
-var _ = require('lodash');
-
 module.exports = function(grunt) {
   // Initialize global configuration variables.
   var config = grunt.file.readJSON('Gruntconfig.json');
@@ -7,16 +5,9 @@ module.exports = function(grunt) {
     config: config
   });
 
-  // Set implicit global configuration.
-  var buildPaths = grunt.config('config.buildPaths');
-  buildPaths = _.extend({
-    build: 'build',
-    html: 'build/html',
-    package: 'build/packages',
-    reports: 'build/reports',
-    temp: 'build/temp'
-  }, buildPaths);
-  grunt.config('config.buildPaths', buildPaths);
+  // Set up default global configuration.
+  var GDT = require('./lib/init')(grunt);
+  GDT.init();
 
   // Wrap Grunt's loadNpmTasks() function to allow loading Grunt task modules
   // that are dependencies of Grunt Drupal Tasks.
diff --git a/example/Gruntconfig.json b/example/Gruntconfig.json
index 3e53bfe..36ebf71 100644
--- a/example/Gruntconfig.json
+++ b/example/Gruntconfig.json
@@ -3,9 +3,7 @@
     "make": "src/project.make",
     "drupal": "src"
   },
-  "siteUrls": {
-    "default": "http://project.local"
-  },
+  "domain": "project.vm",
   "packages": {
     "srcFiles": ["!sites/*/files/**", "!xmlrpc.php", "!modules/php/*"],
     "projFiles": ["README*", "bin/**"]
diff --git a/lib/init.js b/lib/init.js
new file mode 100644
index 0000000..4f7d35e
--- /dev/null
+++ b/lib/init.js
@@ -0,0 +1,39 @@
+var _ = require('lodash');
+
+module.exports = function(grunt) {
+  var module = {};
+
+  module.domain = function() {
+    return process.env.GDT_DOMAIN || grunt.config('config.domain') || require('os').hostname();
+  };
+
+  // Ensure a default URL for the project.
+  module.siteUrls = function() {
+    return grunt.config('config.siteUrls.default') || "http://<%= config.domain %>";
+  }
+
+  module.buildPaths = function() {
+    // Set implicit global configuration.
+    var buildPaths = grunt.config('config.buildPaths');
+    buildPaths = _.extend({
+      build: 'build',
+      html: 'build/html',
+      package: 'build/packages',
+      reports: 'build/reports',
+      temp: 'build/temp'
+    }, buildPaths);
+
+    return buildPaths;
+  }
+
+  module.init = function() {
+    if (grunt.config('config.siteUrls') === undefined)
+      grunt.config('config.siteUrls', {});
+
+    grunt.config('config.domain', module.domain());
+    grunt.config('config.siteUrls.default', module.siteUrls());
+    grunt.config('config.buildPaths', module.buildPaths());
+  };
+
+  return module;
+};
diff --git a/lib/util.js b/lib/util.js
index 2b1585f..478617a 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -1,3 +1,11 @@
 module.exports = {
-  concurrency: Math.max((require('os').cpus().length || 1) * 2, 2)
+  // Dynamically determine the ideal concurrency for parallel operations.
+  // Algorithm courtesy of grunt-concurrent.
+  concurrency: Math.max((require('os').cpus().length || 1) * 2, 2),
+  // Assemble an array of absolute URLs
+  expandUrls: function(baseUrl, paths) {
+    return paths.map(function(value, index, arr) {
+      return baseUrl + value;
+    });
+  }
 };
diff --git a/tasks/behat.js b/tasks/behat.js
index a14070f..62ee06a 100644
--- a/tasks/behat.js
+++ b/tasks/behat.js
@@ -6,6 +6,8 @@ module.exports = function(grunt) {
    * Dynamically adds Behat testing tasks based on configuration sets in the
    * Gruntconfig.json file.
    *
+   * For a complete understanding of the behat task, please review CONFIG.md.
+   *
    * Example:
    *   "config": {
    *     "buildPaths": {


Test patch:
diff --git a/test/build.js b/test/build.js
index c71eabb..e78e49e 100644
--- a/test/build.js
+++ b/test/build.js
@@ -71,10 +71,3 @@ describe('grunt', function() {
 
   });
 });
-
-describe('Utility Functions', function() {
-  it('should have "concurrency" to recommend a safe limit greater than 2', function() {
-    var limit = require('../lib/util').concurrency;
-    assert(limit >= 2, 'concurrency limit not >= 2');
-  });
-});
diff --git a/test/library.js b/test/library.js
new file mode 100644
index 0000000..f1c9cef
--- /dev/null
+++ b/test/library.js
@@ -0,0 +1,27 @@
+var assert = require('assert');
+var util = require('../lib/util');
+
+describe('Utility Functions', function() {
+  describe('provides a capability to determine effective concurrency', function() {
+    it('should have "concurrency" to recommend a safe limit greater than 2', function() {
+      var limit = util.concurrency;
+      assert(limit >= 2, 'concurrency limit not >= 2');
+    });
+  });
+  describe('provides a capability to construct absolute URLs', function() {
+    it ('allows expansion of a root path', function() {
+      var items = util.expandUrls('http://example.com', ['/']);
+      assert.equal('http://example.com/', items[0]);
+    });
+    it ('allows expansion of multiple paths', function() {
+      var items = util.expandUrls('http://example.com', ['/a', '/b']);
+      assert.equal('http://example.com/a', items[0]);
+      assert.equal('http://example.com/b', items[1]);
+    });
+    it ('allows expansion of a base URL with a base path', function() {
+      var items = util.expandUrls('http://example.com/subsite', ['/a', '/b']);
+      assert.equal('http://example.com/subsite/a', items[0]);
+      assert.equal('http://example.com/subsite/b', items[1]);
+    });
+  });
+});