<commit_before>/*
 * Copyright 2012-2013 Nuance Communications Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * dynamic_module_loader_test
 *
 * @author <NAME>
 * @created 2012/08/26
 */
// We always want long stack traces here.
require('longjohn');

var expect = require('chai').expect;
var assert = require('chai').assert;
var fs = require('fs');
var util = require('util');
var wrench = require('wrench');
var nock = require('nock');
var zlib = require('zlib');
var tar = require('tar');
var fstream = require('fstream');
var path = require('path');
var exec = require('child_process').exec;
var extend = require('xtend');

var DynamicModuleLoader = require('../index').DynamicModuleLoader;
var dmlConfig = require('../index').config;
var _ = require('underscore');
_.str = require('underscore.string');

var LockManager = require('hurt-locker').LockManager;

describe('DynamicModuleLoaderTest', function ()
{
    var rootDir = path.join(__dirname, "/../../");
    var tmpDir = path.join(rootDir, 'target/DynamicModuleLoaderTest-tmp');

    var dynamicModuleName = 'test-dynamic-module';
    var resourceDir = path.join(__dirname, '/resources');
    var dynamicModuleResourceDir = path.join(resourceDir, dynamicModuleName);
    var dynamicModuleFilePath = path.join(tmpDir, '/' + dynamicModuleName);

    var dynamicModuleTarFilePath = dynamicModuleFilePath + '.tar';
    var dynamicModuleTarGzipFilePath = dynamicModuleTarFilePath + ".gz";

    var dynamicModuleZipFilePath = dynamicModuleFilePath + ".zip";
    var dynamicModuleZipFileNoRootDirPath = dynamicModuleFilePath + "-no-root-dir.zip";

    var lockManager;
    var dynamicModuleLoader;

    beforeEach(function (done)
               {
                   // Get rid of the temp directory before we start the test.
                   if (fs.existsSync(tmpDir))
                   {
                       wrench.rmdirSyncRecursive(tmpDir, true);
                   }

                   lockManager = new LockManager({lockDir: path.join(tmpDir, "/locks")});

                   var config = {
                       lockManager: lockManager,
                       downloadDir: path.join(tmpDir + "/downloads"),
                       moduleInstallationDir: path.join(tmpDir + "/installed-modules"),
                       modulePackageServerUrl: "http://gattacus"
                   };

                   if (process.env.NPM_PATH)
                   {
                       config.npmExecutablePath = process.env.NPM_PATH
                   }

                   // Create the module loader to be tested.
                   dynamicModuleLoader = new DynamicModuleLoader(config);

                   wrench.mkdirSyncRecursive(tmpDir);

                   // In preparation for our test, we tar and compress up the test dynamic module.
                   fstream.Reader({path: dynamicModuleResourceDir, type: "Directory"})
                       .pipe(tar.Pack())
                       .pipe(zlib.createGzip())
                       .pipe(fstream.Writer(dynamicModuleTarGzipFilePath).on('close', function (err)
                       {
                           expect(err).to.equal(undefined);
                           zipTestModule();
                       }));

                   // We also zip the test module.
                   // NOTE 2012/09/24 KTD: We have to spawn the zip program because the zip support in Node is
                   // atrocious.  Please note that this will very likely *not* work on Windows, at least not without
                   // Cygwin.
                   function zipTestModule()
                   {
                       execZip(util.format("-rq %s %s", dynamicModuleZipFilePath, dynamicModuleName), resourceDir, zipTestModuleWithNoRootDir);
                   }

                   function zipTestModuleWithNoRootDir()
                   {
                       execZip(util.format("-rv %s *", dynamicModuleZipFileNoRootDirPath), dynamicModuleResourceDir, done);
                   }

                   function execZip(params, resourceDir, next)
                   {
                       // Using this form of child process execution because spawn doesn't work for wildcards when
                       // calling zip.  The zip process exits with code "12", saying that it has nothing to do.
                       exec("zip " + params, {cwd: resourceDir}, function (error, stdout, stderr)
                       {
                           if (error)
                           {
                               console.log(stdout);
                               console.log(stderr);
                           }

                           expect(error).to.equal(null);

                           next();
                       });
                   }
               });

    describe('initialization', function ()
    {
        it('should initialize with default settings when no settings supplied', function (done)
        {
            var lm = new DynamicModuleLoader();

            assert.isDefined(lm.settings.lockManager, "lock manager setting");

            // The DML adds the lock manager to its settings, so we add it to the default values we create before
            // asserting equality.
            var expectedSettings = extend(dmlConfig.createDefaultConfig(), {lockManager: lm.settings.lockManager});
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });

        it('should initialize with settings supplied, overriding default ones and adding new ones', function (done)
        {
            var settings = {wibble: 'drumsticks', downloadDir: 'giblets', lockManager: new LockManager()};
            var lm = new DynamicModuleLoader(settings);
            var expectedSettings = extend(lm.settings, settings);
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });


        it('should initialize with settings supplied, overriding default ones and adding new ones', function (done)
        {
            var settings = {wibble: 'drumsticks', downloadDir: 'giblets'};
            var lm = new DynamicModuleLoader(function ()
                                             {
                                                 return settings;
                                             });
            var expectedSettings = extend(lm.settings, settings);
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });
    });


    describe('__downloadFile', function ()
    {
        it('should not find file', function (done)
        {
            var targetFile = tmpDir + "/file.tar.gz";
            var sourceUrl = "http://localhost/not-found.tar.gz";
            var scope = nock("http://localhost").get("/not-found.tar.gz").reply(404);
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.not.equal(undefined);
                            expect(filePath, 'file path').to.equal(undefined);

                            expect(err.statusCode, 'error status code').to.equal(404);
                            expect(err.message, 'error message').to.equal("Unable to download from " + sourceUrl + " to " +
                                                                          targetFile + ".  Status code 404.");
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(false);

                            scope.done();
                            done();
                        });
        });

        it('should get an error because of unknown host', function (done)
        {
            var targetFile = tmpDir + "/file.tar.gz";
            var sourceUrl = "http://unknown-host/not-found.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.not.equal(undefined);
                            expect(filePath, 'file path').to.equal(undefined);

                            expect(err.statusCode, 'error status code').to.equal(undefined);
                            expect(_.str.contains(err.message, "getaddrinfo ")).to.be.true;
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(false);

                            done();
                        });
        });

        it('should download file to temp directory', function (done)
        {
            var host = "http://gattacus";
            var path = "/test_dynamic_module.tar.gz";
            var sourceUrl = host + path;
            var scope = nock(host).get(path).replyWithFile(200, dynamicModuleTarGzipFilePath);
            var targetFile = tmpDir + "/downloaded.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.equal(undefined);
                            expect(filePath, 'file path').to.equal(targetFile);
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(true);

                            scope.done();
                            done();
                        });
        });

        it('should download file from https', function (done)
        {
            var host = "https://gattacus";
            var path = "/test_dynamic_module.tar.gz";
            var sourceUrl = host + path;
            var scope = nock(host).get(path).replyWithFile(200, dynamicModuleTarGzipFilePath);
            var targetFile = tmpDir + "/downloaded.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.equal(undefined);
                            expect(filePath, 'file path').to.equal(targetFile);
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(true);

                            scope.done();
                            done();
                        });
        });
    });


    describe('__decompressTarFile', function ()
    {
        it('should decompress the tar file', function (done)
        {
            var targetFilePath = tmpDir + "/uncompressed.tar";
            var result = dynamicModuleLoader.__decompressTarFile(dynamicModuleTarGzipFilePath, targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.equal(undefined);
                            expect(decompressedFilePath).to.equal(targetFilePath);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(true);

                            done();
                        });
        });

        it('should fail to find the source tar file', function (done)
        {
            var targetFilePath = tmpDir + "/uncompressed.tar";
            var result = dynamicModuleLoader.__decompressTarFile("does-not-exist.tar.gz", targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });

        it('should fail to write the target file', function (done)
        {
            var targetFilePath = tmpDir + "/some-non-existent-dir/should-not-work.tar";
            var result = dynamicModuleLoader.__decompressTarFile(dynamicModuleTarGzipFilePath, targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });
    });


    describe('__decompressZipFile', function ()
    {
        it('should decompress the zip file', function (done)
        {
            var targetFilePath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, dynamicModuleName);
            var result = dynamicModuleLoader.__decompressZipFile(dynamicModuleZipFilePath,
                                                                 dynamicModuleLoader.settings.moduleInstallationDir);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.equal(undefined);
                            expect(decompressedFilePath).to.equal(dynamicModuleLoader.settings.moduleInstallationDir);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(true);

                            done();
                        });
        });

        it('should fail to find the source zip file', function (done)
        {
            var targetFilePath = path.join(tmpDir, "unzipped");
            var result = dynamicModuleLoader.__decompressZipFile("does-not-exist.zip",
                                                                 dynamicModuleLoader.settings.moduleInstallationDir);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });
    });


    describe('__findPackageJSONFile', function ()
    {
        it('should find package.json in root dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(dynamicModuleResourceDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should find package.json in sub dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(resourceDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should find package.json in sub sub dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(__dirname)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should return the first package.json file found', function (done)
        {
            var targetDir = path.join(tmpDir, 'resources');
            wrench.copyDirSyncRecursive(resourceDir, targetDir);
            fs.writeFileSync(path.join(targetDir, 'test-dynamic-module/lib/package.json'), 'wibble', 'UTF-8');

            // Make sure we also have two package.json files with path names that are the same length, just to test
            // that it doesn't break anything.
            var bilDir = path.join(targetDir, 'test-dynamic-module/bil');
            wrench.mkdirSyncRecursive(bilDir);
            fs.writeFileSync(path.join(bilDir, 'package.json'), 'elbbiw', 'UTF-8');

            dynamicModuleLoader.__findPackageJSONFile(targetDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, path.join(targetDir, 'test-dynamic-module/package.json'), done);
                      });
        });

        it('should not find anything and return an error', function (done)
        {
            var targetDir = path.join(tmpDir, 'resources');
            wrench.copyDirSyncRecursive(resourceDir, targetDir);
            fs.unlinkSync(path.join(targetDir, 'test-dynamic-module/package.json'));

            dynamicModuleLoader.__findPackageJSONFile(targetDir)
                .when(function (err, packageJSONFilePath)
                      {
                          expect(err, 'error').to.not.equal(undefined);
                          expect(packageJSONFilePath, 'package json file path').to.equal(undefined);
                          expect(err.message, 'error message').to.equal('Unable to find package.json file in directory ' + targetDir + ' or any of its sub directories.');

                          done();
                      });
        });

        function checkCorrectFileFound(err, actualPackageJSONFilePath, expectedPackageJSONFilePath, done)
        {
            if (expectedPackageJSONFilePath instanceof Function)
            {
                done = expectedPackageJSONFilePath;
                expectedPackageJSONFilePath = path.join(dynamicModuleResourceDir, 'package.json');
            }

            expect(err, 'error').to.equal(undefined);
            expect(actualPackageJSONFilePath, 'package json file path').to.not.equal(undefined);
            expect(path.resolve(actualPackageJSONFilePath), 'package json file path').to.equal(expectedPackageJSONFilePath);

            done();
        }
    });

    var NOT_OVERRIDING_FILE_EXTENSION = undefined;
    var NOT_EXPECTING_ERROR_MESSAGE = undefined;
    var REGISTER_LISTENERS = true;
    var DO_NOT_REGISTER_LISTENERS = false;

    describe('load', function ()
    {
        it('should download, uncompress and return a valid module from a .tar.gz source with a download path provided', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath, 
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done, "http://MyPath")
        });

        it('should download, uncompress and return a valid module from a .tar.gz source', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done)
        });

        it('should download, uncompress and return a valid module from a zip source with embedded root dir', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.zip', dynamicModuleZipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a zip source with no embedded root dir', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module-no-root-dir.zip', dynamicModuleZipFileNoRootDirPath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a .tar.gz source when an overriding extension is specified', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                ".tar.gz", REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a .zip source when an overriding extension is specified', function (done)
        {
            // Override the default extension to be .tar.gz.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .zip module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.zip', dynamicModuleZipFilePath, 
                '.zip', REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download module when no listeners registered', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should skip npm installation', function (done)
        {
            dynamicModuleLoader.settings.npmSkipInstall = true;
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, ensureNodeModulesDirectoryNotPresent);

            function ensureNodeModulesDirectoryNotPresent()
            {
                // We make sure that there is no "node_modules" directory in the expanded package.  If there is one it
                // means that the NPM program was invoked, which is exactly what we want to avoid.
                expect(fs.existsSync(path.join(dynamicModuleFilePath, '/node_modules'), 'node_modules directory exists')).to.equal(false);
                done();
            }
        });

        function listenerHaltWithError(eventName)
        {
            return function (moduleName, downloadedFile, proceed)
            {
                for (var i = arguments.length - 1; i >= 0; i--)
                {
                    if (_.isFunction(arguments[i]))
                    {
                        // We've found our callback function.  Signify that there's an error.
                        arguments[i](new Error(eventName + ' halt!'));
                        return;
                    }
                }
            }
        }

        it('should stop download with error when moduleDownloaded event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleDownloaded, listenerHaltWithError('moduleDownloaded'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleExtracted, listenerHaltWithError('moduleExtracted'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleDownloaded halt!', doNotExpectModuleInstallationDirToBePresent, done)
        });

        it('should stop download with error when moduleExtracted event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleExtracted, listenerHaltWithError('moduleExtracted'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleExtracted halt!', expectModuleInstallationDirRename, done)
        });

        it('should stop download with error when moduleInstalled event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleInstalled halt!', expectModuleInstallationDirRename, done)
        });

        it('should download stop with error when moduleLoaded event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleLoaded halt!', expectModuleInstallationDirRename, done)
        });

        it('should not rename in-error downloaded module directory if the ERROR package already exists', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, function (moduleName, proceed)
            {
                delete dynamicModuleLoader.downloadedModuleMainFileCache[moduleName];
                proceed(new Error('moduleLoaded halt!'));
            });

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleLoaded halt!',
                expectModuleInstallationDirRenameAndRecordTimestamp, errorAgainAndCheck);

            var originalRenamedModuleDirTimestamp;
            var moduleErrorDirPath;
            var moduleDirPath;
            function expectModuleInstallationDirRenameAndRecordTimestamp(targetModuleName)
            {
                // Makes sure the module dir was renamed.
                expectModuleInstallationDirRename(targetModuleName);

                moduleDirPath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, targetModuleName);
                moduleErrorDirPath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, targetModuleName + "-ERROR");

                // Make sure module dir path does not exist.
                expect(fs.existsSync(moduleDirPath, "module directory path")).to.equal(false);

                // Make sure module error dir path does exist.
                expect(fs.existsSync(moduleErrorDirPath, "module error directory path")).to.equal(true);

                var stat = fs.statSync(moduleErrorDirPath);
                originalRenamedModuleDirTimestamp = stat.ctime;

                // Make sure module dir path does not exist.
                expect(fs.existsSync(moduleDirPath, "module directory path")).to.equal(false);
            }

            function errorAgainAndCheck()
            {
                runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                    NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, 'moduleLoaded halt!',
                    expectOriginalErrorDirectoryStillInPlaceAndDownloadedPackageToBeDeleted, done);
            }<commit_msg>Removed duplicate check.<commit_after>/*
 * Copyright 2012-2013 Nuance Communications Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * dynamic_module_loader_test
 *
 * @author <NAME>
 * @created 2012/08/26
 */
// We always want long stack traces here.
require('longjohn');

var expect = require('chai').expect;
var assert = require('chai').assert;
var fs = require('fs');
var util = require('util');
var wrench = require('wrench');
var nock = require('nock');
var zlib = require('zlib');
var tar = require('tar');
var fstream = require('fstream');
var path = require('path');
var exec = require('child_process').exec;
var extend = require('xtend');

var DynamicModuleLoader = require('../index').DynamicModuleLoader;
var dmlConfig = require('../index').config;
var _ = require('underscore');
_.str = require('underscore.string');

var LockManager = require('hurt-locker').LockManager;

describe('DynamicModuleLoaderTest', function ()
{
    var rootDir = path.join(__dirname, "/../../");
    var tmpDir = path.join(rootDir, 'target/DynamicModuleLoaderTest-tmp');

    var dynamicModuleName = 'test-dynamic-module';
    var resourceDir = path.join(__dirname, '/resources');
    var dynamicModuleResourceDir = path.join(resourceDir, dynamicModuleName);
    var dynamicModuleFilePath = path.join(tmpDir, '/' + dynamicModuleName);

    var dynamicModuleTarFilePath = dynamicModuleFilePath + '.tar';
    var dynamicModuleTarGzipFilePath = dynamicModuleTarFilePath + ".gz";

    var dynamicModuleZipFilePath = dynamicModuleFilePath + ".zip";
    var dynamicModuleZipFileNoRootDirPath = dynamicModuleFilePath + "-no-root-dir.zip";

    var lockManager;
    var dynamicModuleLoader;

    beforeEach(function (done)
               {
                   // Get rid of the temp directory before we start the test.
                   if (fs.existsSync(tmpDir))
                   {
                       wrench.rmdirSyncRecursive(tmpDir, true);
                   }

                   lockManager = new LockManager({lockDir: path.join(tmpDir, "/locks")});

                   var config = {
                       lockManager: lockManager,
                       downloadDir: path.join(tmpDir + "/downloads"),
                       moduleInstallationDir: path.join(tmpDir + "/installed-modules"),
                       modulePackageServerUrl: "http://gattacus"
                   };

                   if (process.env.NPM_PATH)
                   {
                       config.npmExecutablePath = process.env.NPM_PATH
                   }

                   // Create the module loader to be tested.
                   dynamicModuleLoader = new DynamicModuleLoader(config);

                   wrench.mkdirSyncRecursive(tmpDir);

                   // In preparation for our test, we tar and compress up the test dynamic module.
                   fstream.Reader({path: dynamicModuleResourceDir, type: "Directory"})
                       .pipe(tar.Pack())
                       .pipe(zlib.createGzip())
                       .pipe(fstream.Writer(dynamicModuleTarGzipFilePath).on('close', function (err)
                       {
                           expect(err).to.equal(undefined);
                           zipTestModule();
                       }));

                   // We also zip the test module.
                   // NOTE 2012/09/24 KTD: We have to spawn the zip program because the zip support in Node is
                   // atrocious.  Please note that this will very likely *not* work on Windows, at least not without
                   // Cygwin.
                   function zipTestModule()
                   {
                       execZip(util.format("-rq %s %s", dynamicModuleZipFilePath, dynamicModuleName), resourceDir, zipTestModuleWithNoRootDir);
                   }

                   function zipTestModuleWithNoRootDir()
                   {
                       execZip(util.format("-rv %s *", dynamicModuleZipFileNoRootDirPath), dynamicModuleResourceDir, done);
                   }

                   function execZip(params, resourceDir, next)
                   {
                       // Using this form of child process execution because spawn doesn't work for wildcards when
                       // calling zip.  The zip process exits with code "12", saying that it has nothing to do.
                       exec("zip " + params, {cwd: resourceDir}, function (error, stdout, stderr)
                       {
                           if (error)
                           {
                               console.log(stdout);
                               console.log(stderr);
                           }

                           expect(error).to.equal(null);

                           next();
                       });
                   }
               });

    describe('initialization', function ()
    {
        it('should initialize with default settings when no settings supplied', function (done)
        {
            var lm = new DynamicModuleLoader();

            assert.isDefined(lm.settings.lockManager, "lock manager setting");

            // The DML adds the lock manager to its settings, so we add it to the default values we create before
            // asserting equality.
            var expectedSettings = extend(dmlConfig.createDefaultConfig(), {lockManager: lm.settings.lockManager});
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });

        it('should initialize with settings supplied, overriding default ones and adding new ones', function (done)
        {
            var settings = {wibble: 'drumsticks', downloadDir: 'giblets', lockManager: new LockManager()};
            var lm = new DynamicModuleLoader(settings);
            var expectedSettings = extend(lm.settings, settings);
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });


        it('should initialize with settings supplied, overriding default ones and adding new ones', function (done)
        {
            var settings = {wibble: 'drumsticks', downloadDir: 'giblets'};
            var lm = new DynamicModuleLoader(function ()
                                             {
                                                 return settings;
                                             });
            var expectedSettings = extend(lm.settings, settings);
            assert.equal(JSON.stringify(lm.settings), JSON.stringify(expectedSettings), "settings");
            done();
        });
    });


    describe('__downloadFile', function ()
    {
        it('should not find file', function (done)
        {
            var targetFile = tmpDir + "/file.tar.gz";
            var sourceUrl = "http://localhost/not-found.tar.gz";
            var scope = nock("http://localhost").get("/not-found.tar.gz").reply(404);
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.not.equal(undefined);
                            expect(filePath, 'file path').to.equal(undefined);

                            expect(err.statusCode, 'error status code').to.equal(404);
                            expect(err.message, 'error message').to.equal("Unable to download from " + sourceUrl + " to " +
                                                                          targetFile + ".  Status code 404.");
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(false);

                            scope.done();
                            done();
                        });
        });

        it('should get an error because of unknown host', function (done)
        {
            var targetFile = tmpDir + "/file.tar.gz";
            var sourceUrl = "http://unknown-host/not-found.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.not.equal(undefined);
                            expect(filePath, 'file path').to.equal(undefined);

                            expect(err.statusCode, 'error status code').to.equal(undefined);
                            expect(_.str.contains(err.message, "getaddrinfo ")).to.be.true;
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(false);

                            done();
                        });
        });

        it('should download file to temp directory', function (done)
        {
            var host = "http://gattacus";
            var path = "/test_dynamic_module.tar.gz";
            var sourceUrl = host + path;
            var scope = nock(host).get(path).replyWithFile(200, dynamicModuleTarGzipFilePath);
            var targetFile = tmpDir + "/downloaded.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.equal(undefined);
                            expect(filePath, 'file path').to.equal(targetFile);
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(true);

                            scope.done();
                            done();
                        });
        });

        it('should download file from https', function (done)
        {
            var host = "https://gattacus";
            var path = "/test_dynamic_module.tar.gz";
            var sourceUrl = host + path;
            var scope = nock(host).get(path).replyWithFile(200, dynamicModuleTarGzipFilePath);
            var targetFile = tmpDir + "/downloaded.tar.gz";
            var result = dynamicModuleLoader.__downloadFile(sourceUrl, targetFile);
            result.when(function (err, filePath)
                        {
                            expect(err, 'error object').to.equal(undefined);
                            expect(filePath, 'file path').to.equal(targetFile);
                            expect(fs.existsSync(targetFile), 'target file existence').to.equal(true);

                            scope.done();
                            done();
                        });
        });
    });


    describe('__decompressTarFile', function ()
    {
        it('should decompress the tar file', function (done)
        {
            var targetFilePath = tmpDir + "/uncompressed.tar";
            var result = dynamicModuleLoader.__decompressTarFile(dynamicModuleTarGzipFilePath, targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.equal(undefined);
                            expect(decompressedFilePath).to.equal(targetFilePath);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(true);

                            done();
                        });
        });

        it('should fail to find the source tar file', function (done)
        {
            var targetFilePath = tmpDir + "/uncompressed.tar";
            var result = dynamicModuleLoader.__decompressTarFile("does-not-exist.tar.gz", targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });

        it('should fail to write the target file', function (done)
        {
            var targetFilePath = tmpDir + "/some-non-existent-dir/should-not-work.tar";
            var result = dynamicModuleLoader.__decompressTarFile(dynamicModuleTarGzipFilePath, targetFilePath);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });
    });


    describe('__decompressZipFile', function ()
    {
        it('should decompress the zip file', function (done)
        {
            var targetFilePath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, dynamicModuleName);
            var result = dynamicModuleLoader.__decompressZipFile(dynamicModuleZipFilePath,
                                                                 dynamicModuleLoader.settings.moduleInstallationDir);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.equal(undefined);
                            expect(decompressedFilePath).to.equal(dynamicModuleLoader.settings.moduleInstallationDir);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(true);

                            done();
                        });
        });

        it('should fail to find the source zip file', function (done)
        {
            var targetFilePath = path.join(tmpDir, "unzipped");
            var result = dynamicModuleLoader.__decompressZipFile("does-not-exist.zip",
                                                                 dynamicModuleLoader.settings.moduleInstallationDir);
            result.when(function (err, decompressedFilePath)
                        {
                            expect(err).to.not.equal(undefined);
                            expect(decompressedFilePath).to.equal(undefined);
                            expect(fs.existsSync(targetFilePath), 'target file existence').to.equal(false);

                            done();
                        });
        });
    });


    describe('__findPackageJSONFile', function ()
    {
        it('should find package.json in root dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(dynamicModuleResourceDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should find package.json in sub dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(resourceDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should find package.json in sub sub dir', function (done)
        {
            dynamicModuleLoader.__findPackageJSONFile(__dirname)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, done);
                      });
        });

        it('should return the first package.json file found', function (done)
        {
            var targetDir = path.join(tmpDir, 'resources');
            wrench.copyDirSyncRecursive(resourceDir, targetDir);
            fs.writeFileSync(path.join(targetDir, 'test-dynamic-module/lib/package.json'), 'wibble', 'UTF-8');

            // Make sure we also have two package.json files with path names that are the same length, just to test
            // that it doesn't break anything.
            var bilDir = path.join(targetDir, 'test-dynamic-module/bil');
            wrench.mkdirSyncRecursive(bilDir);
            fs.writeFileSync(path.join(bilDir, 'package.json'), 'elbbiw', 'UTF-8');

            dynamicModuleLoader.__findPackageJSONFile(targetDir)
                .when(function (err, packageJSONFilePath)
                      {
                          checkCorrectFileFound(err, packageJSONFilePath, path.join(targetDir, 'test-dynamic-module/package.json'), done);
                      });
        });

        it('should not find anything and return an error', function (done)
        {
            var targetDir = path.join(tmpDir, 'resources');
            wrench.copyDirSyncRecursive(resourceDir, targetDir);
            fs.unlinkSync(path.join(targetDir, 'test-dynamic-module/package.json'));

            dynamicModuleLoader.__findPackageJSONFile(targetDir)
                .when(function (err, packageJSONFilePath)
                      {
                          expect(err, 'error').to.not.equal(undefined);
                          expect(packageJSONFilePath, 'package json file path').to.equal(undefined);
                          expect(err.message, 'error message').to.equal('Unable to find package.json file in directory ' + targetDir + ' or any of its sub directories.');

                          done();
                      });
        });

        function checkCorrectFileFound(err, actualPackageJSONFilePath, expectedPackageJSONFilePath, done)
        {
            if (expectedPackageJSONFilePath instanceof Function)
            {
                done = expectedPackageJSONFilePath;
                expectedPackageJSONFilePath = path.join(dynamicModuleResourceDir, 'package.json');
            }

            expect(err, 'error').to.equal(undefined);
            expect(actualPackageJSONFilePath, 'package json file path').to.not.equal(undefined);
            expect(path.resolve(actualPackageJSONFilePath), 'package json file path').to.equal(expectedPackageJSONFilePath);

            done();
        }
    });

    var NOT_OVERRIDING_FILE_EXTENSION = undefined;
    var NOT_EXPECTING_ERROR_MESSAGE = undefined;
    var REGISTER_LISTENERS = true;
    var DO_NOT_REGISTER_LISTENERS = false;

    describe('load', function ()
    {
        it('should download, uncompress and return a valid module from a .tar.gz source with a download path provided', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath, 
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done, "http://MyPath")
        });

        it('should download, uncompress and return a valid module from a .tar.gz source', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done)
        });

        it('should download, uncompress and return a valid module from a zip source with embedded root dir', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.zip', dynamicModuleZipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a zip source with no embedded root dir', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module-no-root-dir.zip', dynamicModuleZipFileNoRootDirPath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a .tar.gz source when an overriding extension is specified', function (done)
        {
            // Override the default extension to be .zip.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .tar.gz module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                ".tar.gz", REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download, uncompress and return a valid module from a .zip source when an overriding extension is specified', function (done)
        {
            // Override the default extension to be .tar.gz.
            dynamicModuleLoader.settings.defaultRemoteServerPackageFileExtension = '.zip';

            // Now request a .zip module specifying the extension explicitly in the load call.
            runTest(expectDownloadRequest, '/test-dynamic-module.zip', dynamicModuleZipFilePath, 
                '.zip', REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should download module when no listeners registered', function (done)
        {
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, done);
        });

        it('should skip npm installation', function (done)
        {
            dynamicModuleLoader.settings.npmSkipInstall = true;
            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, NOT_EXPECTING_ERROR_MESSAGE, doNotExpectModuleInstallationDirRename, ensureNodeModulesDirectoryNotPresent);

            function ensureNodeModulesDirectoryNotPresent()
            {
                // We make sure that there is no "node_modules" directory in the expanded package.  If there is one it
                // means that the NPM program was invoked, which is exactly what we want to avoid.
                expect(fs.existsSync(path.join(dynamicModuleFilePath, '/node_modules'), 'node_modules directory exists')).to.equal(false);
                done();
            }
        });

        function listenerHaltWithError(eventName)
        {
            return function (moduleName, downloadedFile, proceed)
            {
                for (var i = arguments.length - 1; i >= 0; i--)
                {
                    if (_.isFunction(arguments[i]))
                    {
                        // We've found our callback function.  Signify that there's an error.
                        arguments[i](new Error(eventName + ' halt!'));
                        return;
                    }
                }
            }
        }

        it('should stop download with error when moduleDownloaded event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleDownloaded, listenerHaltWithError('moduleDownloaded'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleExtracted, listenerHaltWithError('moduleExtracted'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleDownloaded halt!', doNotExpectModuleInstallationDirToBePresent, done)
        });

        it('should stop download with error when moduleExtracted event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleExtracted, listenerHaltWithError('moduleExtracted'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleExtracted halt!', expectModuleInstallationDirRename, done)
        });

        it('should stop download with error when moduleInstalled event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleInstalled, listenerHaltWithError('moduleInstalled'));

            // These event handlers are called later in the workflow, so they should have no effect as processing should
            // have halted as a result of the above event.  We register them to make sure that the correct handler is
            // halting things.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleInstalled halt!', expectModuleInstallationDirRename, done)
        });

        it('should download stop with error when moduleLoaded event listener reports an error', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, listenerHaltWithError('moduleLoaded'));

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleLoaded halt!', expectModuleInstallationDirRename, done)
        });

        it('should not rename in-error downloaded module directory if the ERROR package already exists', function (done)
        {
            // Register the event listener that will halt proceedings.
            dynamicModuleLoader.on(dynamicModuleLoader.events.moduleLoaded, function (moduleName, proceed)
            {
                delete dynamicModuleLoader.downloadedModuleMainFileCache[moduleName];
                proceed(new Error('moduleLoaded halt!'));
            });

            runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                NOT_OVERRIDING_FILE_EXTENSION, REGISTER_LISTENERS, 'moduleLoaded halt!',
                expectModuleInstallationDirRenameAndRecordTimestamp, errorAgainAndCheck);

            var originalRenamedModuleDirTimestamp;
            var moduleErrorDirPath;
            var moduleDirPath;
            function expectModuleInstallationDirRenameAndRecordTimestamp(targetModuleName)
            {
                // Makes sure the module dir was renamed.
                expectModuleInstallationDirRename(targetModuleName);

                moduleDirPath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, targetModuleName);
                moduleErrorDirPath = path.join(dynamicModuleLoader.settings.moduleInstallationDir, targetModuleName + "-ERROR");

                // Make sure module dir path does not exist.
                expect(fs.existsSync(moduleDirPath, "module directory path")).to.equal(false);

                // Make sure module error dir path does exist.
                expect(fs.existsSync(moduleErrorDirPath, "module error directory path")).to.equal(true);

                var stat = fs.statSync(moduleErrorDirPath);
                originalRenamedModuleDirTimestamp = stat.ctime;
            }

            function errorAgainAndCheck()
            {
                runTest(expectDownloadRequest, '/test-dynamic-module.tar.gz', dynamicModuleTarGzipFilePath,
                    NOT_OVERRIDING_FILE_EXTENSION, DO_NOT_REGISTER_LISTENERS, 'moduleLoaded halt!',
                    expectOriginalErrorDirectoryStillInPlaceAndDownloadedPackageToBeDeleted, done);
            }
{'fraction_non_alphanumeric': 0.09071652545199813, 'fraction_numerical': 0.0016345319901260925, 'mean_word_length': 2.0014517420905085, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 2, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 122, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6627310', 'n_tokens_mistral': 14462, 'n_tokens_neox': 13819, 'n_words': 3474}