var logWarns = function(warns) {
   sails.log.blank();
   _.each(warns, function (warn) {
     sails.log.warn(warn);
   });
   STRINGFILE.logMoreInfoLink(STRINGFILE.get('links.docs.config.blueprints'), sails.log.warn);
 };
        Migratable.Waterline.teardown(function(err) {
          // Create a new ORM instance
          var wl = new Waterline();

          // Load the fixtures again
          var fixtures = _.cloneDeep(Migratable.fixtures);
          _.each(fixtures, function(val, key) {
            wl.registerModel(Waterline.Collection.extend(_.merge({}, Migratable.WaterlineOptions.defaults, fixtures[key])));
          });

          // Initialize the ORM again
          wl.initialize(Migratable.WaterlineOptions, function(err, orm) {
            if (err) {
              return done(err);
            }

            // Run migrations
            waterlineUtils.autoMigrations('safe', orm, function(err) {
              if (err) {
                return done(err);
              }

              orm.collections.safe.count().exec(function(err, numOfPirates) {
                if (err) {
                  return done(err);
                }

                assert.strictEqual(numOfPirates, 1);

                return done();
              });
            });
          });
        });
Example #3
0
 httpHelper.testRoute('get', 'partials', function(err, response) {
   if (err) {
     return done(new Error(err));
   }
   assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><BLAP><FOO><BAR>BAZ!</BAR></FOO></BLAP></body></html>');
   filesToWrite = {
     'views/layout.ejs': '<ZAP><%- body %></ZAP>',
     'views/test-partials.ejs': '<APPLE><%- partial(\'./partials/outer.ejs\') %></APPLE>',
     'views/partials/outer.ejs': '<ORANGE><%- partial(\'./nested/inner.ejs\') %></ORANGE>',
     'views/partials/nested/inner.ejs': '<BANANA>TADA!</BANANA>'
   };
   _.each(filesToWrite, function(data, filename) {
     Filesystem.writeSync({
       force: true,
       destination: filename,
       string: data
     }).execSync();
   });
   httpHelper.testRoute('get', 'partials', function(err, response) {
     if (err) {
       return done(new Error(err));
     }
     assert.equal(response.body, '<!DOCTYPE html><html><head><!-- default layout --></head><body><BLAP><FOO><BAR>BAZ!</BAR></FOO></BLAP></body></html>');
     done();
   });
 });
Example #4
0
  _.each(attributes, function(attribute, attributeName) {
    // Build a validation list for the attribute
    validations[attributeName] = {};

    // Process each property in the attribute and look for any validation
    // properties.
    _.each(attribute, function(property, propertyName) {
      // Ignore NULL values
      if (_.isNull(property)) {
        return;
      }

      // If the property is reserved, don't do anything with it
      if (_.indexOf(RESERVED_PROPERTY_NAMES, propertyName) > -1) {
        return;
      }

      // If the property is an `enum` alias it to the anchor IN validation
      if (propertyName.toLowerCase() === 'enum') {
        validations[attributeName].in = property;
        return;
      }

      // Otherwise validate that the property name is a valid anchor validation.
      if (_.indexOf(RESERVED_VALIDATION_NAMES, propertyName) < 0) {
        return;
      }

      // Set the validation
      validations[attributeName][propertyName] = property;
    });
  });
Example #5
0
    define: function define(datastoreName, tableName, definition, cb) {

      // Get a reference to the datastore.
      var datastore = datastores[datastoreName];
      if (!datastore) { return cb(new Error('Unrecognized datastore: `'+datastoreName+'`,  It doesn\'t seem to have been registered with this adapter (sails-disk).')); }

      var db;

      // If memory only, create a new in-memory nedb for the collection.
      if (datastore.config.inMemoryOnly === true) {
        db = new nedb({ inMemoryOnly: true });
      } else {
        db = new nedb({ filename: path.resolve(datastore.config.dir, tableName + '.db') });
      }

      datastore.dbs[tableName] = db;

      // Re-create any unique indexes.
      _.each(definition, function(val, columnName) {
        // If the attribute has `unique` set on it, or it's the primary key, add a unique index.
        if (val.unique || val.primaryKey) {
          db.ensureIndex({
            fieldName: columnName,
            unique: true
          });
        }
      });

      return db.loadDatabase(cb);

    },
    initialize: function (cb) {

      // Provide hook context to closures
      hook = this;

      // Set the _middlewareType of each blueprint action to 'BLUEPRINT: <action>'.
      _.each(BlueprintController, function(fn, key) {
        fn._middlewareType = 'BLUEPRINT: ' + key;
      });

      // Register route syntax for binding blueprints directly.
      // This is deprecated, so onRoute currently just logs a warning.
      sails.on('route:typeUnknown', onRoute);

      // Wait until after user routes have been bound to bind our
      // own "shadow routes" (action routes, RESTful routes,
      // shortcut routes and index routes).
      sails.on('router:after', hook.bindShadowRoutes);

      // If the ORM hook is active, wait for it to load, then create actions
      // for each model.
      if (sails.hooks['orm-offshore']) {
        sails.after('hook:orm-offshore:loaded', function() {
          hook.registerActions(cb);
        });
      }
      // Otherwise we're done!
      else {
        return cb();
      }
    },
Example #7
0
 beforeEach(function(done) {
   // Cache the current working directory.
   curDir = process.cwd();
   // Create a temp directory.
   tmpDir = tmp.dirSync({gracefulCleanup: true, unsafeCleanup: true});
   // Switch to the temp directory.
   process.chdir(tmpDir.name);
   // Write a layout file for each test.
   Filesystem.writeSync({
     force: true,
     destination: 'views/layout.ejs',
     string: '<!DOCTYPE html><html><head><!-- default layout --></head><body><%- body %></body></html>'
   }).execSync();
   // Write out any files specific to this test.
   _.each(filesToWrite, function(data, filename) {
     Filesystem.writeSync({
       force: true,
       destination: filename,
       string: data
     }).execSync();
   });
   // Merge the default config with any config specific to this test.
   var _config = _.merge({
     port: 1342,
     hooks: {grunt: false, blueprints: false, policies: false, pubsub: false},
     log: {level: 'error'},
   }, sailsConfig);
   // Lift Sails for this test.
   (new Sails()).lift(_config, function(err, _sails) {
       sailsApp = _sails;
       return done(err);
     }
   );
 });
Example #8
0
 _.each(records, function(record) {
   _.each(datastore.refCols[query.using], function(colName) {
     // If this looks like NeDB's idea of a serialized Buffer, turn it into a real buffer.
     if (record[colName] && record[colName].type === 'Buffer' && _.isArray(record[colName].data)) {
       record[colName] = new Buffer(record[colName].data);
     }
   });
 });
Example #9
0
 self.composeSchema = function() {
   superComposeSchema();
   const forbiddenFields = [ 'path', 'rank', 'level' ];
   _.each(self.schema, function(field) {
     if (_.contains(forbiddenFields, field.name)) {
       throw new Error('Page type ' + self.name + ': the field name ' + field.name + ' is forbidden');
     }
   });
 };
 it('should add four `/foo` routes to the sails config', function() {
   var fooRoutes = 0;
   _.each(sails.router._privateRouter.stack, function(stack){
     if(stack.route.path === '/foo' && stack.route.methods.get === true){
       fooRoutes += 1;
     }
   });
   assert(fooRoutes === 5);
 });
Example #11
0
Support.registerConnection = function registerConnection(tableNames, cb) {
  var collections = {};

  _.each(tableNames, function processTable(name) {
    var collection = Support.Model(name);
    collections[name] = collection;
  });

  var connection = _.cloneDeep(Support.Config);
  connection.identity = 'test';

  adapter.registerDatastore(connection, collections, cb);
};
Example #12
0
  req.allParams = function () {
    // Combines parameters from the query string, and encoded request body
    // to compose a monolithic object of named parameters, irrespective of source
    var allParams = _.extend({}, req.query, req.body);

    // Mixin route params, as long as they have defined values
    _.each(Object.keys(req.params), function(paramName) {
      if (allParams[paramName] || !_.isUndefined(req.params[paramName])) {
        allParams[paramName] = !_.isUndefined(req.params[paramName]) ? req.params[paramName] : allParams[paramName];
      }
    });
    return allParams;
  };
Example #13
0
            _.each(parentKeys, function buildUnion(parentPk) {
              var unionStatement = _.merge({}, template.statement);

              // Replace the placeholder `?` values with the primary key of the
              // parent record.
              var andClause = _.pullAt(unionStatement.where.and, unionStatement.where.and.length - 1);
              _.each(_.first(andClause), function replaceValue(val, key) {
                _.first(andClause)[key] = parentPk;
              });

              // Add the UNION statement to the array of other statements
              unionStatement.where.and.push(_.first(andClause));
              unionStatements.push(unionStatement);
            });
Example #14
0
 _.each(managers, function(manager, name) {
   var schema = manager.schema;
   if (!schema) {
     return;
   }
   _.each(schema, function(field) {
     if (field.name === 'title') {
       // Was always sortified, migration would be redundant
       return;
     }
     if (!field.sortify) {
       return;
     }
     manager.addSortifyMigration(field.name);
   });
 });
 registerActions: function(cb) {
   // Loop through all of the loaded models and add actions for each.
   // Even though we're adding the same exact actions for each model,
   // (e.g. user/find and pet/find are the same), it's important that
   // each model gets its own set so that they can have different
   // action middleware (e.g. policies) applied to them.
   _.each(_.keys(sails.models), function(modelIdentity) {
     sails.registerAction(BlueprintController.create, modelIdentity + '/create');
     sails.registerAction(BlueprintController.find, modelIdentity + '/find');
     sails.registerAction(BlueprintController.findone, modelIdentity + '/findOne');
     sails.registerAction(BlueprintController.update, modelIdentity + '/update');
     sails.registerAction(BlueprintController.destroy, modelIdentity + '/destroy');
     sails.registerAction(BlueprintController.populate, modelIdentity + '/populate');
     sails.registerAction(BlueprintController.add, modelIdentity + '/add');
     sails.registerAction(BlueprintController.remove, modelIdentity + '/remove');
     sails.registerAction(BlueprintController.replace, modelIdentity + '/replace');
   });
   return cb();
 }
Example #16
0
Support.Setup = function setup(tableName, cb) {
  var collection = Support.Model(tableName);
  var collections = {};
  collections[tableName] = collection;

  var connection = _.cloneDeep(Support.Config);
  connection.identity = 'test';

  // Setup a primaryKey for migrations
  collection.definition = _.cloneDeep(Support.Definition);

  // Build a schema to represent the underlying physical database structure
  var schema = {};
  _.each(collection.definition, function parseAttribute(attributeVal, attributeName) {
    var columnName = attributeVal.columnName || attributeName;

    // If the attribute doesn't have an `autoMigrations` key on it, ignore it.
    if (!_.has(attributeVal, 'autoMigrations')) {
      return;
    }

    schema[columnName] = attributeVal.autoMigrations;
  });

  // Set Primary Key flag on the primary key attribute
  var primaryKeyAttrName = collection.primaryKey;
  var primaryKey = collection.definition[primaryKeyAttrName];
  if (primaryKey) {
    var pkColumnName = primaryKey.columnName || primaryKeyAttrName;
    schema[pkColumnName].primaryKey = true;
  }


  adapter.registerDatastore(connection, collections, function registerCb(err) {
    if (err) {
      return cb(err);
    }

    adapter.define('test', tableName, schema, cb);
  });
};
    _.each(options.sortedResults.parents, function buildAliasCache(parentRecord) {
      var cache = {
        attrName: key,
        parentPkAttr: pkColumnName,
        belongsToPkValue: parentRecord[pkColumnName],
        keyName: keyName || alias,
        type: strategy
      };

      // Grab the join keys used in the query
      var childKey = _.first(popInstructions).childKey;
      var parentKey = _.first(popInstructions).parentKey;

      // Find any records in the children that match up to the join keys
      var records = _.filter(options.sortedResults.children[alias], function findChildren(child) {
        // If this is a VIA_JUNCTOR join, use the foreign key we built up,
        // otherwise check equality between child and parent join keys.
        if (strategy === 3) {
          return child._parent_fk === parentRecord[parentKey];
        }

        return child[childKey] === parentRecord[parentKey];
      });

      // If this is a many-to-many strategy, be sure to clear the foreign
      // key value that was added as part of the join process. The end user
      // doesn't care about that.
      if (strategy === 3) {
        _.each(records, function cleanRecords(record) {
          delete record._parent_fk;
        });
      }

      // Store the child on the cache
      if (records.length) {
        cache.records = records;
      }

      // Store the local cache value in the query cache
      queryCache.set(cache);
    }); // </ buildAliasCache >
Example #18
0
 it('should load all of the valid controller actions', function() {
   var expectedActions = [
     'toplevellegacy/fnaction',
     'toplevellegacy/machineaction',
     'toplevellegacy/underscore_action',
     'toplevellegacy/action-with-dashes',
     'top-level-standalone-fn',
     'top-level-standalone-machine',
     'somefolder/someotherfolder/nestedlegacy/fnaction',
     'somefolder/someotherfolder/nestedlegacy/machineaction',
     'some/folder/some/other/folder/nestedlegacy/fnaction',
     'some/folder/some/other/folder/nestedlegacy/machineaction',
     'somefolder/someotherfolder/nested-standalone-machine'
   ];
   var unexpectedActions = _.difference(_.keys(sailsApp._actions), expectedActions);
   assert(!unexpectedActions.length, 'Loaded unexpected actions:\n' + util.inspect(unexpectedActions));
   _.each(expectedActions, function(expectedAction) {
     assert(sailsApp._actions[expectedAction], 'Did not load expected action `' + expectedAction + '`');
     assert(_.isFunction(sailsApp._actions[expectedAction]), 'Expected action `' + expectedAction + '` loaded, but instead of a function it\'s a ' + typeof(sailsApp._actions[expectedAction]));
   });
 });
Example #19
0
      sails.modules.loadServices(function(err, modules) {
        if (err) {
          sails.log.error('Error occurred loading modules ::');
          sails.log.error(err);
          return cb(err);
        }

        // Expose services on `sails.services` to provide access even when globals are disabled.
        _.extend(sails.services, modules);

        // Expose globals (if enabled)
        if (sails.config.globals.services) {
          _.each(sails.services, function(service, identity) {
            var globalId = service.globalId || service.identity || identity;
            global[globalId] = service;
          });
        }

        // Relevant modules have finished loading.
        return cb();
      });
      queryOptions.newRecord = (function getNewRecord(){

        // Use all of the request params as values for the new record.
        var values = req.allParams();

        // Attempt to JSON parse any collection attributes into arrays.  This is to allow
        // setting collections using the shortcut routes.
        _.each(Model.attributes, function(attrDef, attrName) {
          if (attrDef.collection && (!req.body || !req.body[attrName]) && (req.query && _.isString(req.query[attrName]))) {
            try {
              values[attrName] = JSON.parse(req.query[attrName]);
              // If it is not valid JSON (e.g. because it's just a normal string),
              // then fall back to interpreting it as-is
            } catch(unusedErr) {}

          }
        });

        return values;

      })();
Example #21
0
module.exports = function generateSecret() {

  // Combine random and case-specific factors into a base string
  var factors = {
    creationDate: (new Date()).getTime(),
    random: Math.random() * (Math.random() * 1000),
    nodeVersion: process.version
  };
  var basestring = '';
  _.each(factors, function(val) {
    basestring += val;
  });

  // Build hash
  var hash =
  crypto.createHash('md5')
  .update(basestring)
  .digest('hex');

  return hash;
};
Example #22
0
    mongoDeferred.toArray(function findCb(err, nativeResult) {
      if (err) { return exits.error(err); }

      //  ╔═╗╦═╗╔═╗╔═╗╔═╗╔═╗╔═╗  ┌┐┌┌─┐┌┬┐┬┬  ┬┌─┐  ┬─┐┌─┐┌─┐┌─┐┬─┐┌┬┐┌─┌─┐─┐
      //  ╠═╝╠╦╝║ ║║  ║╣ ╚═╗╚═╗  │││├─┤ │ │└┐┌┘├┤   ├┬┘├┤ │  │ │├┬┘ │││ └─┐ │
      //  ╩  ╩╚═╚═╝╚═╝╚═╝╚═╝╚═╝  ┘└┘┴ ┴ ┴ ┴ └┘ └─┘  ┴└─└─┘└─┘└─┘┴└──┴┘└─└─┘─┘
      // Process records (mutate in-place) to wash away adapter-specific eccentricities.
      var phRecords = nativeResult;
      try {
        _.each(phRecords, function (phRecord){
          processNativeRecord(phRecord, WLModel, s3q.meta);
        });
      } catch (e) { return exits.error(e); }


      // if (s3q.meta && s3q.meta.logMongoS3Qs) {
      //   console.log('found %d records',phRecords.length, require('util').inspect(phRecords,{depth:10}),'\n');
      // }
      return exits.success(phRecords);

    }); // </ mongoDeferred.toArray() >
Example #23
0
module.exports = function sortData(data, sortCriteria) {

  function dynamicSort(property) {
    var sortOrder = 1;
    if (property[0] === '-') {
      sortOrder = -1;
      property = property.substr(1);
    }

    return function(a, b) {
      var result = (a[property] < b[property]) ? -1 : (a[property] > b[property]) ? 1 : 0;
      return result * sortOrder;
    };
  }

  function dynamicSortMultiple() {
    var props = arguments;
    return function(obj1, obj2) {
      var i = 0;
      var result = 0;
      var numberOfProperties = props.length;

      while (result === 0 && i < numberOfProperties) {
        result = dynamicSort(props[i])(obj1, obj2);
        i++;
      }
      return result;
    };
  }

  // build sort criteria in the format ['firstName', '-lastName']
  var sortArray = [];
  _.each(_.keys(sortCriteria), function(key) {
    if (sortCriteria[key] === -1) sortArray.push('-' + key);
    else sortArray.push(key);
  });

  data.sort(dynamicSortMultiple.apply(null, sortArray));
  return data;
};
Example #24
0
        fs.readdir(sails.config.paths.adapters, function(err, contents) {
          if (err) {
            if (err.code === 'ENOENT') {
              return cb(undefined, classicStyleAdapters);
            }
            return cb(err);
          }

          var folderStyleAdapters = {};
          try {
            _.each(contents, function(filename) {

              var absPath = path.join(sails.config.paths.adapters, filename);

              // Exclude things that aren't directories, and directories that start with dots.
              if (_.startsWith(filename, '.')) {
                return;
              }
              var stats = fs.statSync(absPath);
              if (!stats.isDirectory()) {
                return;
              }

              // But otherwise, if we see a directory in here, try to require it.
              // (this follows the rules of the package.json file if there is one--
              //  or otherwise uses index.js by convention)
              var adapterDef = require(absPath);

              // Use the name of the folder as the identity.
              folderStyleAdapters[filename] = adapterDef;

            }); //</_.each()>
          } catch (e) {
            return cb(e);
          }

          // Finally, send back the merged-together set of adapters.
          return cb(undefined, _.extend(classicStyleAdapters, folderStyleAdapters));

        }); //</fs.readdir>
Example #25
0
 findQuery.exec(function(err, records) {
   if (err) {return cb(err);}
   // Does this model contain any attributes with type `ref`?
   if (datastore.refCols[query.using].length > 0) {
     // If so, loop through the records and transform refs to Buffers where possible.
     _.each(records, function(record) {
       _.each(datastore.refCols[query.using], function(colName) {
         // If this looks like NeDB's idea of a serialized Buffer, turn it into a real buffer.
         if (record[colName] && record[colName].type === 'Buffer' && _.isArray(record[colName].data)) {
           record[colName] = new Buffer(record[colName].data);
         }
       });
     });
   }
   // If the primary key column is `_id`, and we had a projection with just `_id`, transform the records
   // to only contain that column.  This is a workaround for an issue in NeDB where doing a projection
   // with just _id returns all the columns.
   if (primaryKeyCol === '_id' && _.keys(projection).length === 1 && projection._id === 1) {
     records = _.map(records, function(record) {return _.pick(record, '_id');});
   }
   return cb(undefined, records);
 });
Example #26
0
    teardown: function (identity, cb) {

      var datastoreIdentities = [];

      // If no specific identity was sent, teardown all the datastores
      if (!identity || identity === null) {
        datastoreIdentities = datastoreIdentities.concat(_.keys(datastores));
      } else {
        datastoreIdentities.push(identity);
      }

      // Teardown each datastore
      _.each(datastoreIdentities, function teardownDatastore(datastoreIdentity) {

        // Remove the datastore entry.
        delete datastores[datastoreIdentity];

      });

      return cb();

    },
Example #27
0
    async.each(_.keys(datastoreMap), function(item, nextItem) {
      var datastore = datastoreMap[item];
      var usedSchemas = {};

      // Check if the datastore's adapter has a `registerConnection` method
      if (!_.has(datastore.adapter, 'registerConnection')) {
        return setImmediate(function() {
          nextItem();
        });
      }

      // Add the datastore name as an identity property on the config
      datastore.config.identity = item;

      // Get all the collections using the datastore and build up a normalized
      // map that can be passed down to the adapter.
      var usedSchemas = {};

      _.each(_.uniq(datastore.collections), function(modelName) {
        var collection = modelMap[modelName];
        var identity = modelName;

        // Normalize the identity to use as the tableName for use in the adapter
        if (_.has(Object.getPrototypeOf(collection), 'tableName')) {
          identity = Object.getPrototypeOf(collection).tableName;
        }

        usedSchemas[identity] = {
          primaryKey: collection.primaryKey,
          definition: collection.schema,
          tableName: collection.tableName || identity
        };
      });


      // Call the `registerConnection` method on the datastore
      datastore.adapter.registerConnection(datastore.config, usedSchemas, nextItem);
    }, function(err) {
  waterline.initialize(wlOptions, function(err, orm) {
    if (err) {
      return done(err);
    }

    // Save a reference to the ORM
    ORM = orm;

    // Globalize collections for normalization
    _.each(ORM.collections, function(collection, identity) {
      var globalName = identity.charAt(0).toUpperCase() + identity.slice(1);
      global.Migratable[globalName] = collection;
    });

    // Run migrations
    waterlineUtils.autoMigrations('alter', orm, function(err) {
      if (err) {
        return done(err);
      }

      return done();
    });
  });
module.exports = function runBenchmarks(name, testFns, done) {
  var suite = new Benchmark.Suite({
    name: name
  });

  _.each(testFns, function buildTest(testFn) {
    suite = suite.add(testFn.name, {
      defer: true,
      async: true,
      fn: function(deferred) {
        testFn(function _afterRunningTestFn() {
          deferred.resolve();
        });
      }
    });
  });

  suite.on('cycle', function(event) {
    console.log(' •', String(event.target));
  })
  .on('complete', function() {
    // Time is measured in microseconds so 1000 = 1ms
    var fastestMean = _.first(this.filter('fastest')).stats.mean * 1000;
    var slowestMean = _.first(this.filter('slowest')).stats.mean * 1000;

    var mean = {
      fastest: Benchmark.formatNumber(fastestMean < 1 ? fastestMean.toFixed(2) : Math.round(fastestMean)),
      slowest: Benchmark.formatNumber(slowestMean < 1 ? slowestMean.toFixed(2) : Math.round(slowestMean))
    };

    console.log('Fastest is ' + this.filter('fastest').map('name') + ' with an average of: ' + mean.fastest + 'ms');
    console.log('Slowest is ' + this.filter('slowest').map('name') + ' with an average of: ' + mean.slowest + 'ms');

    return done(undefined, this);
  })
  .run();
};
Example #30
0
    it('should return a shallow clone of the actions dictionary when `sails.getActions` is called', function() {
      var actions = sailsApp.getActions();
      assert(actions !== sailsApp._actions, 'sails.getActions is supposed to return a shallow clone, but got an exact reference!');
      var expectedActions = [
        'toplevellegacy/fnaction',
        'toplevellegacy/machineaction',
        'toplevellegacy/underscore_action',
        'toplevellegacy/action-with-dashes',
        'top-level-standalone-fn',
        'top-level-standalone-machine',
        'somefolder/someotherfolder/nestedlegacy/fnaction',
        'somefolder/someotherfolder/nestedlegacy/machineaction',
        'some/folder/some/other/folder/nestedlegacy/fnaction',
        'some/folder/some/other/folder/nestedlegacy/machineaction',
        'somefolder/someotherfolder/nested-standalone-machine'
      ];
      var unexpectedActions = _.difference(_.keys(actions), expectedActions);
      assert(!unexpectedActions.length, 'Loaded unexpected actions:\n' + util.inspect(unexpectedActions));
      _.each(expectedActions, function(expectedAction) {
        assert(actions[expectedAction], 'Did not load expected action `' + expectedAction + '`');
        assert(_.isFunction(actions[expectedAction]), 'Expected action `' + expectedAction + '` loaded, but instead of a function it\'s a ' + typeof(actions[expectedAction]));
      });

    });