<commit_before>'use strict';
var React = require('react');
var moment = require('moment');
var globals = require('./globals');
var curator = require('./curator');

var truncateString = globals.truncateString;

var GdmCollection = module.exports.GdmCollection = React.createClass({
    getInitialState: function() {
        return {
            sortCol: 'gdm',
            reversed: false
        };
    },

    // Handle clicks in the table header for sorting
    sortDir: function(colName) {
        var reversed = colName === this.state.sortCol ? !this.state.reversed : false;
        var sortCol = colName;
        this.setState({sortCol: sortCol, reversed: reversed});
    },

    // Call-back for the JS sorting function. Expects GDMs to compare in a and b. Depending on the column currently selected
    // for sorting, this function sorts on the relevant parts of the GDM.
    sortCol: function(a, b) {
        var diff;

        switch (this.state.sortCol) {
            case 'gdm':
                diff = a.gene.symbol > b.gene.symbol ? 1 : -1;
                break;
            case 'last':
                var aAnnotation = curator.findLatestAnnotation(a);
                var bAnnotation = curator.findLatestAnnotation(b);
                diff = aAnnotation && bAnnotation ? Date.parse(aAnnotation.date_created) - Date.parse(bAnnotation.date_created) : (aAnnotation ? -1 : 1);
                break;
            case 'creator':
                var aLower = a.submitted_by.last_name.toLowerCase();
                var bLower = b.submitted_by.last_name.toLowerCase();
                diff = aLower > bLower ? 1 : (aLower === bLower ? 0 : -1);
                break;
            case 'created':
                diff = Date.parse(a.date_created) - Date.parse(b.date_created);
                break;
            default:
                diff = 0;
                break;
        }
        return this.state.reversed ? -diff : diff;
    },

    render: function () {
        var context = this.props.context;
        var gdms = context['@graph'];
        var sortIconClass = {gdm: 'tcell-sort', last: 'tcell-sort', creator: 'tcell-sort', created: 'tcell-sort'};
        sortIconClass[this.state.sortCol] = this.state.reversed ? 'tcell-desc' : 'tcell-asc';

        return (

Gene — Disease

Mode

                                Participants

                                Last Edited

                                Creator

                                Created

                        {gdms.sort(this.sortCol).map(function(gdm) {
                            var annotationOwners = curator.getAnnotationOwners(gdm);
                            var latestAnnotation = curator.findLatestAnnotation(gdm);
                            var mode = gdm.modeInheritance.match(/^(.*?)(?: \(HP:[0-9]*?\)){0,1}$/)[1];
                            var term = truncateString(gdm.disease.term, 30);
                            var createdTime = moment(gdm.date_created);
                            var latestTime = latestAnnotation ? moment(latestAnnotation.date_created) : '';
                            var participants = annotationOwners.map(function(owner) { return owner.title; }).join(', ');
                            var statusString = gdm.status.toLowerCase().replace(/\W/g, '-'); // Convert status string to have dashes for CSS
                            var iconClass = 'icon gdm-status-icon-' + statusString;

                            return (

{gdm.gene.symbol} – {gdm.disease.term}

{mode}

                                        {participants}

                                        {latestTime ?

{latestTime.format("YYYY MMM DD")}

{latestTime.format("h:mm a")}

                                        : null}

{gdm.submitted_by.title}

{createdTime.format("YYYY MMM DD")}

{createdTime.format("h:mm a")}

                            );
                        })}

        );
    }
});

globals.content_views.register(GdmCollection, 'gdm_collection');
<commit_msg>Functional (but ugly) GDM search<commit_after>'use strict';
var React = require('react');
var moment = require('moment');
var form = require('../libs/bootstrap/form');
var globals = require('./globals');
var curator = require('./curator');

var Form = form.Form;
var FormMixin = form.FormMixin;
var Input = form.Input;
var truncateString = globals.truncateString;

var GdmCollection = module.exports.GdmCollection = React.createClass({
    mixins: [FormMixin],

    getInitialState: function() {
        return {
            sortCol: 'gdm',
            reversed: false,
            searchTerm: ''
        };
    },

    // Handle clicks in the table header for sorting
    sortDir: function(colName) {
        var reversed = colName === this.state.sortCol ? !this.state.reversed : false;
        var sortCol = colName;
        this.setState({sortCol: sortCol, reversed: reversed});
    },

    // Call-back for the JS sorting function. Expects GDMs to compare in a and b. Depending on the column currently selected
    // for sorting, this function sorts on the relevant parts of the GDM.
    sortCol: function(a, b) {
        var diff;

        switch (this.state.sortCol) {
            case 'gdm':
                diff = a.gene.symbol > b.gene.symbol ? 1 : -1;
                break;
            case 'last':
                var aAnnotation = curator.findLatestAnnotation(a);
                var bAnnotation = curator.findLatestAnnotation(b);
                diff = aAnnotation && bAnnotation ? Date.parse(aAnnotation.date_created) - Date.parse(bAnnotation.date_created) : (aAnnotation ? -1 : 1);
                break;
            case 'creator':
                var aLower = a.submitted_by.last_name.toLowerCase();
                var bLower = b.submitted_by.last_name.toLowerCase();
                diff = aLower > bLower ? 1 : (aLower === bLower ? 0 : -1);
                break;
            case 'created':
                diff = Date.parse(a.date_created) - Date.parse(b.date_created);
                break;
            default:
                diff = 0;
                break;
        }
        return this.state.reversed ? -diff : diff;
    },

    searchChange: function(ref, e) {
        var searchVal = this.refs[ref].getValue().toLowerCase();
        this.setState({searchTerm: searchVal});
    },

    render: function () {
        var context = this.props.context;
        var gdms = context['@graph'];
        var searchTerm = this.state.searchTerm;
        var filteredGdms;
        var sortIconClass = {gdm: 'tcell-sort', last: 'tcell-sort', creator: 'tcell-sort', created: 'tcell-sort'};
        sortIconClass[this.state.sortCol] = this.state.reversed ? 'tcell-desc' : 'tcell-asc';

        // Filter GDMs
        if (searchTerm) {
            filteredGdms = gdms.filter(function(gdm) {
                return gdm.gene.symbol.toLowerCase().indexOf(searchTerm) !== -1 || gdm.disease.term.toLowerCase().indexOf(searchTerm) !== -1;
            });
        } else {
            filteredGdms = gdms;
        }

        return (

                <Form formClassName="form-std">

                        <Input type="text" ref="q" label="Search" handleChange={this.searchChange}
                            labelClassName="control-label" groupClassName="form-group" />

                </Form>

Gene — Disease

Mode

                                Participants

                                Last Edited

                                Creator

                                Created

                        {filteredGdms.sort(this.sortCol).map(function(gdm) {
                            var annotationOwners = curator.getAnnotationOwners(gdm);
                            var latestAnnotation = curator.findLatestAnnotation(gdm);
                            var mode = gdm.modeInheritance.match(/^(.*?)(?: \(HP:[0-9]*?\)){0,1}$/)[1];
                            var term = truncateString(gdm.disease.term, 30);
                            var createdTime = moment(gdm.date_created);
                            var latestTime = latestAnnotation ? moment(latestAnnotation.date_created) : '';
                            var participants = annotationOwners.map(function(owner) { return owner.title; }).join(', ');
                            var statusString = gdm.status.toLowerCase().replace(/\W/g, '-'); // Convert status string to have dashes for CSS
                            var iconClass = 'icon gdm-status-icon-' + statusString;

                            return (

{gdm.gene.symbol} – {gdm.disease.term}

{mode}

                                        {participants}

                                        {latestTime ?

{latestTime.format("YYYY MMM DD")}

{latestTime.format("h:mm a")}

                                        : null}

{gdm.submitted_by.title}

{createdTime.format("YYYY MMM DD")}

{createdTime.format("h:mm a")}

                            );
                        })}

        );
    }
});

globals.content_views.register(GdmCollection, 'gdm_collection');
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 The Lovefield Project Authors. 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.
 */
var fsMod =
    /**
     * @type {{
     *   ensureDirSync: function(string),
     *   removeSync: function(string)
     * }}
     */ (require('fs-extra'));
var glob = /** @type {{sync: !Function}} */ (require('glob')).sync;
var pathMod = /** @type {{sep: string}} */ (require('path'));
var temp = /** @type {{Dir: !Function}} */ (require('temporary'));

/**
 * @type {{
 *   CLOSURE_LIBRARY_PATH: string,
 *   TEST_SCHEMAS: !Array.<{file: string, namespace: string}>
 *   }
 * }}
 */
var config = /** @type {!Function} */ (require(
    pathMod.resolve(__dirname + '/config.js')))();
var genDeps = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).genDeps);
var genModuleDeps = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).genModuleDeps);
var extractRequires = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).extractRequires);
var generateTestSchemas = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'builder.js')).generateTestSchemas);

// Make linter happy.
var log = console['log'];

/** @const {!Array<string>} */
var SYMLINKS = ['lib', 'perf', 'testing', 'tests', 'dist'];

// Firebase tests need special treatment.
var FIREBASE_TESTS = [
  'tests/backstore/firebase_raw_back_store_test.html',
  'tests/backstore/firebase_test.html'
];

/**
 * @constructor
 * A helper class for creating a temporary directory that is capable of
 * executing tests.
 * @param {string} testsDir The folder that contains all the tests.
 */
var TestEnv = function(testsDir) {
  /** @type {string} */
  this.testsDir = testsDir;

  /** @type {string} */
  this.tempPath = pathMod.resolve(new temp.Dir().path);

  /** @type {string} */
  this.origPath = process.cwd();

  /** @type {string} */
  this.genDir = pathMod.join(this.tempPath, 'gen');

  /** @type {string} */
  this.htmlDir = pathMod.join(this.tempPath, 'html');
};

/**
 * @return {!IThenable<string>} A promise holding the path of the temporary
 *     directory.
 */
TestEnv.prototype.create = function() {
  process.chdir(this.tempPath);

  // Generating gen and html folders.
  fsMod.mkdirSync('html');
  this.createSymLinks(config.CLOSURE_LIBRARY_PATH);

  var allHtmlFiles = this.setupTestsWithoutHtml();
  allHtmlFiles.push.apply(
      allHtmlFiles,
      this.setupTestsWithHtml());
  this.setupTestResources();
  allHtmlFiles.sort();
  generateIndexHtml(allHtmlFiles);

  return generateTestSchemas(this.genDir).then(
      function() {
        this.setupTestDeps();
        return this.tempPath;
      }.bind(this),
      function(e) {
        process.chdir(this.origPath);
        cleanUp(this.tempPath);
        throw e;
      }.bind(this));
};

/**
 * Performs necessary setup for tests that do not provide their own HTML file.
 * @return {!Array<string>} A list of all HTML files in the temp directory.
 */
TestEnv.prototype.setupTestsWithoutHtml = function() {
  // Generating HTML files for tests that don't already have one.
  var testFilesWithoutHtml = this.queryTestFiles(false);
  log('Generating', testFilesWithoutHtml.length, 'HTML test files ... ');
  return testFilesWithoutHtml.map(function(name) {
    return generateHtmlFile(name);
  });
};

/**
 * Performs necessary setup for tests that provide their own HTML file.
 * @return {!Array<string>} A list of all HTML files in the temp directory.
 */
TestEnv.prototype.setupTestsWithHtml = function() {
  // Creating symlinks for existing HTML files.
  var testFilesWithHtml = this.queryTestFiles(true).filter(function(test) {
    return FIREBASE_TESTS.indexOf(test) == -1;
  });
  log('Found', testFilesWithHtml.length, 'test files with existing HTML... ');
  return testFilesWithHtml.map(function(testFile) {
    var htmlFile = testFile.substr(0, testFile.length - 3) + '.html';
    // Replacing tests/ with html/ in the name.
    var link = htmlFile.replace('tests/', 'html/');

    // Creating a symlink for the directory.
    // For Windows to work, one must comply
    // 1. use absolute path
    // 2. create junctions (ignored on platforms other than Windows)
    // 3. directories only
    // 4. both directories on the same volume
    var src = pathMod.dirname(pathMod.resolve(this.origPath, htmlFile));
    var dst = pathMod.dirname(pathMod.join(this.tempPath, link));

    // Ensure that the parent dir of link target is constructed.
    fsMod.ensureDirSync(pathMod.dirname(dst));
    if (!fsMod.existsSync(dst)) {
      fsMod.symlinkSync(src, dst, 'junction');
    }

    return link;
  }, this);
};

/**
 * Creates symlinks for any json files such that the HTML files can refer to
 * them (used when running perf tests).
 */
TestEnv.prototype.setupTestResources = function() {
  var jsonFiles = glob(this.testsDir + '/**/*.json');
  jsonFiles.forEach(function(jsonFile) {
    var link = pathMod.join('html', pathMod.basename(jsonFile));
    fsMod.symlinkSync(
        pathMod.resolve(jsonFile),
        pathMod.join(this.tempPath, link), 'junction');
  }, this);
};

/**
 * Sets up the deps.js file needed for the tests to run.
 */
TestEnv.prototype.setupTestDeps = function() {
  var directories = SYMLINKS.map(
      function(dir) {
        return pathMod.join(this.tempPath, dir);
      }, this).concat([this.htmlDir, this.genDir]);
  var deps = genDeps(this.tempPath, directories);
  fsMod.writeFileSync('deps.js', deps);
};

/**
 * @param {boolean} withHtml Whether to look for files that already have an
 *     associating HTML file.
 * @return {!Array<string>}
 */
TestEnv.prototype.queryTestFiles = function(withHtml) {
  var testFiles = glob(this.testsDir + '/**/*_test.js');
  var htmlFiles = glob(this.testsDir + '/**/*_test.html');

  var existingHtmlFilesSet = new Set();
  htmlFiles.forEach(function(file) {
    if (FIREBASE_TESTS.indexOf(file) == -1) {
      // Stripping away the '.html' suffix and adding to the set.
      existingHtmlFilesSet.add(file.substr(0, file.length - '.html'.length));
    }
  });
  return testFiles.filter(function(jsFileName) {
    // Stripping away the '.js'.
    var testName = jsFileName.substr(0, jsFileName.length - '.js'.length);
    return existingHtmlFilesSet.has(testName) == withHtml;
  });
};

/**
 * Creates symbolic links to Closure and Lovefield.
 * @param {string} libraryPath Closure library path.
 */
TestEnv.prototype.createSymLinks = function(libraryPath) {
  fsMod.symlinkSync(
      pathMod.resolve(pathMod.join(libraryPath, 'closure')),
      pathMod.join(this.tempPath, 'closure'),
      'junction');
  SYMLINKS.forEach(function(link) {
    fsMod.symlinkSync(
        pathMod.resolve(pathMod.join(__dirname, '../' + link)),
        pathMod.join(this.tempPath, link),
        'junction');
  }, this);
};

/** Removes previously created symbolic links */
function removeSymLinks() {
  fsMod.unlinkSync('closure');
  SYMLINKS.forEach(function(link) {
    fsMod.unlinkSync(link);
  });
}

/**
 * Creates stub HTML file with links to all individual tests.
 * @param {!Array<string>} testFiles
 */
function generateIndexHtml(testFiles) {
  var links = testFiles.map(function(file) {
    return '    ' + file.slice('html/'.length) +
        '
';
  });
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>Lovefield tests</title>\r\n' +
      '  \r\n' +
      '  \r\n' +
      '
Lovefield tests
\r\n' +
      links.join('\r\n') +
      '\r\n  \r\n' +
      '\r\n';
  fsMod.writeFileSync('index.html', contents);
  log('\nTest files generated. Starting server @' + process.cwd() + ' ...\n');
}

/**
 * Generates test HTML for a given test script. Depending on how the test is
 * written (i.e. goog.module or not), the HTML generated will be different since
 * different code injection is needed.
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @return {string} Generated file path.
 */
function generateHtmlFile(script) {
  var scriptPath = pathMod.resolve(pathMod.join(__dirname, '../', script));
  var contents = fsMod.readFileSync(scriptPath, {'encoding': 'utf-8'});
  var LITERAL = 'goog.module';
  var pos = contents.indexOf(LITERAL);
  if (pos != -1) {
    var pos2 = contents.indexOf(';', pos);
    var moduleName = contents.substring(pos + LITERAL.length + 2, pos2 - 2);
    return createTestModule(script, moduleName);
  } else {
    return createTestFile(script);
  }
}

/**
 * @param {string} script
 * @return {string}
 */
function getFirebaseSpecial(script) {
  var firebaseSpecial = '';
  if (script.indexOf('firebase') != -1 &&
      process.env['FIREBASE_URL'] &&
      process.env['FIREBASE_TOKEN']) {
    firebaseSpecial = [
      ''
    ].join('\r\n') + '\r\n';
    firebaseSpecial =
        '\r\n' + firebaseSpecial;
  }
  return firebaseSpecial;
}

/**
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @param {string} moduleName Test module name.
 * @return {string} Generated file path.
 */
function createTestModule(script, moduleName) {
  var sliceIndex = script.indexOf('/') + 1;
  var target = 'html/' + script.slice(sliceIndex, -2) + 'html';
  var level = target.match(/\//g).length;
  var prefix = new Array(level).join('../') + '../';

  var firebaseSpecial = getFirebaseSpecial(script);
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>' + pathMod.basename(target).slice(0, -5) + '</title>\r\n' +
      '    \r\n' +
      '    \r\n' +
      firebaseSpecial +
      '    \r\n' +
      '    \r\n' +
      '  \r\n' +
      '\r\n';

  fsMod.ensureDirSync(pathMod.dirname(target));
  fsMod.writeFileSync(target, contents);

  return target;
}

/**
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @return {string} Generated file path.
 */
function createTestFile(script) {
  var sliceIndex = script.indexOf('/') + 1;
  var target = 'html/' + script.slice(sliceIndex, -2) + 'html';
  var level = target.match(/\//g).length;
  var prefix = new Array(level).join('../') + '../';
  var fakeName = script.replace('/', '$').replace('.', '_');
  var scriptPath = pathMod.resolve(pathMod.join(__dirname, '../' + script));
  var firebaseSpecial = getFirebaseSpecial(script);
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>' + pathMod.basename(target).slice(0, -5) + '</title>\r\n' +
      '    \r\n' +
      '    \r\n' +
      '  \r\n' +
      '  \r\n' +
      firebaseSpecial +
      '    \r\n' +
      '  \r\n' +
      '\r\n';
  fsMod.ensureDirSync(pathMod.dirname(target));
  fsMod.writeFileSync(target, contents);
  return target;
}

/**
 * Removes temp folder.
 * @param {string} tempPath
 */
function cleanUp(tempPath) {
  var origPath = process.cwd();
  removeSymLinks();
  process.chdir(origPath);
  fsMod.removeSync(tempPath);
}

/**
 * @param {string} testsDir
 * @return {!IThenable<string>}
 */
function createTestEnv(testsDir) {
  var testEnv = new TestEnv(testsDir);
  return testEnv.create();
}

/** @type {!Function} */
exports.createTestEnv = createTestEnv;

/** @type {!Function} */
exports.cleanUp = cleanUp;
<commit_msg>Remove goog.testing.AsyncTestCase dependency in test set up. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=121298345<commit_after>/**
 * @license
 * Copyright 2014 The Lovefield Project Authors. 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.
 */
var fsMod =
    /**
     * @type {{
     *   ensureDirSync: function(string),
     *   removeSync: function(string)
     * }}
     */ (require('fs-extra'));
var glob = /** @type {{sync: !Function}} */ (require('glob')).sync;
var pathMod = /** @type {{sep: string}} */ (require('path'));
var temp = /** @type {{Dir: !Function}} */ (require('temporary'));

/**
 * @type {{
 *   CLOSURE_LIBRARY_PATH: string,
 *   TEST_SCHEMAS: !Array.<{file: string, namespace: string}>
 *   }
 * }}
 */
var config = /** @type {!Function} */ (require(
    pathMod.resolve(__dirname + '/config.js')))();
var genDeps = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).genDeps);
var genModuleDeps = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).genModuleDeps);
var extractRequires = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'scan_deps.js')).extractRequires);
var generateTestSchemas = /** @type {!Function} */ (require(
    pathMod.join(__dirname, 'builder.js')).generateTestSchemas);

// Make linter happy.
var log = console['log'];

/** @const {!Array<string>} */
var SYMLINKS = ['lib', 'perf', 'testing', 'tests', 'dist'];

// Firebase tests need special treatment.
var FIREBASE_TESTS = [
  'tests/backstore/firebase_raw_back_store_test.html',
  'tests/backstore/firebase_test.html'
];

/**
 * @constructor
 * A helper class for creating a temporary directory that is capable of
 * executing tests.
 * @param {string} testsDir The folder that contains all the tests.
 */
var TestEnv = function(testsDir) {
  /** @type {string} */
  this.testsDir = testsDir;

  /** @type {string} */
  this.tempPath = pathMod.resolve(new temp.Dir().path);

  /** @type {string} */
  this.origPath = process.cwd();

  /** @type {string} */
  this.genDir = pathMod.join(this.tempPath, 'gen');

  /** @type {string} */
  this.htmlDir = pathMod.join(this.tempPath, 'html');
};

/**
 * @return {!IThenable<string>} A promise holding the path of the temporary
 *     directory.
 */
TestEnv.prototype.create = function() {
  process.chdir(this.tempPath);

  // Generating gen and html folders.
  fsMod.mkdirSync('html');
  this.createSymLinks(config.CLOSURE_LIBRARY_PATH);

  var allHtmlFiles = this.setupTestsWithoutHtml();
  allHtmlFiles.push.apply(
      allHtmlFiles,
      this.setupTestsWithHtml());
  this.setupTestResources();
  allHtmlFiles.sort();
  generateIndexHtml(allHtmlFiles);

  return generateTestSchemas(this.genDir).then(
      function() {
        this.setupTestDeps();
        return this.tempPath;
      }.bind(this),
      function(e) {
        process.chdir(this.origPath);
        cleanUp(this.tempPath);
        throw e;
      }.bind(this));
};

/**
 * Performs necessary setup for tests that do not provide their own HTML file.
 * @return {!Array<string>} A list of all HTML files in the temp directory.
 */
TestEnv.prototype.setupTestsWithoutHtml = function() {
  // Generating HTML files for tests that don't already have one.
  var testFilesWithoutHtml = this.queryTestFiles(false);
  log('Generating', testFilesWithoutHtml.length, 'HTML test files ... ');
  return testFilesWithoutHtml.map(function(name) {
    return generateHtmlFile(name);
  });
};

/**
 * Performs necessary setup for tests that provide their own HTML file.
 * @return {!Array<string>} A list of all HTML files in the temp directory.
 */
TestEnv.prototype.setupTestsWithHtml = function() {
  // Creating symlinks for existing HTML files.
  var testFilesWithHtml = this.queryTestFiles(true).filter(function(test) {
    return FIREBASE_TESTS.indexOf(test) == -1;
  });
  log('Found', testFilesWithHtml.length, 'test files with existing HTML... ');
  return testFilesWithHtml.map(function(testFile) {
    var htmlFile = testFile.substr(0, testFile.length - 3) + '.html';
    // Replacing tests/ with html/ in the name.
    var link = htmlFile.replace('tests/', 'html/');

    // Creating a symlink for the directory.
    // For Windows to work, one must comply
    // 1. use absolute path
    // 2. create junctions (ignored on platforms other than Windows)
    // 3. directories only
    // 4. both directories on the same volume
    var src = pathMod.dirname(pathMod.resolve(this.origPath, htmlFile));
    var dst = pathMod.dirname(pathMod.join(this.tempPath, link));

    // Ensure that the parent dir of link target is constructed.
    fsMod.ensureDirSync(pathMod.dirname(dst));
    if (!fsMod.existsSync(dst)) {
      fsMod.symlinkSync(src, dst, 'junction');
    }

    return link;
  }, this);
};

/**
 * Creates symlinks for any json files such that the HTML files can refer to
 * them (used when running perf tests).
 */
TestEnv.prototype.setupTestResources = function() {
  var jsonFiles = glob(this.testsDir + '/**/*.json');
  jsonFiles.forEach(function(jsonFile) {
    var link = pathMod.join('html', pathMod.basename(jsonFile));
    fsMod.symlinkSync(
        pathMod.resolve(jsonFile),
        pathMod.join(this.tempPath, link), 'junction');
  }, this);
};

/**
 * Sets up the deps.js file needed for the tests to run.
 */
TestEnv.prototype.setupTestDeps = function() {
  var directories = SYMLINKS.map(
      function(dir) {
        return pathMod.join(this.tempPath, dir);
      }, this).concat([this.htmlDir, this.genDir]);
  var deps = genDeps(this.tempPath, directories);
  fsMod.writeFileSync('deps.js', deps);
};

/**
 * @param {boolean} withHtml Whether to look for files that already have an
 *     associating HTML file.
 * @return {!Array<string>}
 */
TestEnv.prototype.queryTestFiles = function(withHtml) {
  var testFiles = glob(this.testsDir + '/**/*_test.js');
  var htmlFiles = glob(this.testsDir + '/**/*_test.html');

  var existingHtmlFilesSet = new Set();
  htmlFiles.forEach(function(file) {
    if (FIREBASE_TESTS.indexOf(file) == -1) {
      // Stripping away the '.html' suffix and adding to the set.
      existingHtmlFilesSet.add(file.substr(0, file.length - '.html'.length));
    }
  });
  return testFiles.filter(function(jsFileName) {
    // Stripping away the '.js'.
    var testName = jsFileName.substr(0, jsFileName.length - '.js'.length);
    return existingHtmlFilesSet.has(testName) == withHtml;
  });
};

/**
 * Creates symbolic links to Closure and Lovefield.
 * @param {string} libraryPath Closure library path.
 */
TestEnv.prototype.createSymLinks = function(libraryPath) {
  fsMod.symlinkSync(
      pathMod.resolve(pathMod.join(libraryPath, 'closure')),
      pathMod.join(this.tempPath, 'closure'),
      'junction');
  SYMLINKS.forEach(function(link) {
    fsMod.symlinkSync(
        pathMod.resolve(pathMod.join(__dirname, '../' + link)),
        pathMod.join(this.tempPath, link),
        'junction');
  }, this);
};

/** Removes previously created symbolic links */
function removeSymLinks() {
  fsMod.unlinkSync('closure');
  SYMLINKS.forEach(function(link) {
    fsMod.unlinkSync(link);
  });
}

/**
 * Creates stub HTML file with links to all individual tests.
 * @param {!Array<string>} testFiles
 */
function generateIndexHtml(testFiles) {
  var links = testFiles.map(function(file) {
    return '    ' + file.slice('html/'.length) +
        '
';
  });
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>Lovefield tests</title>\r\n' +
      '  \r\n' +
      '  \r\n' +
      '
Lovefield tests
\r\n' +
      links.join('\r\n') +
      '\r\n  \r\n' +
      '\r\n';
  fsMod.writeFileSync('index.html', contents);
  log('\nTest files generated. Starting server @' + process.cwd() + ' ...\n');
}

/**
 * Generates test HTML for a given test script. Depending on how the test is
 * written (i.e. goog.module or not), the HTML generated will be different since
 * different code injection is needed.
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @return {string} Generated file path.
 */
function generateHtmlFile(script) {
  var scriptPath = pathMod.resolve(pathMod.join(__dirname, '../', script));
  var contents = fsMod.readFileSync(scriptPath, {'encoding': 'utf-8'});
  var LITERAL = 'goog.module';
  var pos = contents.indexOf(LITERAL);
  if (pos != -1) {
    var pos2 = contents.indexOf(';', pos);
    var moduleName = contents.substring(pos + LITERAL.length + 2, pos2 - 2);
    return createTestModule(script, moduleName);
  } else {
    return createTestFile(script);
  }
}

/**
 * @param {string} script
 * @return {string}
 */
function getFirebaseSpecial(script) {
  var firebaseSpecial = '';
  if (script.indexOf('firebase') != -1 &&
      process.env['FIREBASE_URL'] &&
      process.env['FIREBASE_TOKEN']) {
    firebaseSpecial = [
      ''
    ].join('\r\n') + '\r\n';
    firebaseSpecial =
        '\r\n' + firebaseSpecial;
  }
  return firebaseSpecial;
}

/**
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @param {string} moduleName Test module name.
 * @return {string} Generated file path.
 */
function createTestModule(script, moduleName) {
  var sliceIndex = script.indexOf('/') + 1;
  var target = 'html/' + script.slice(sliceIndex, -2) + 'html';
  var level = target.match(/\//g).length;
  var prefix = new Array(level).join('../') + '../';

  var firebaseSpecial = getFirebaseSpecial(script);
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>' + pathMod.basename(target).slice(0, -5) + '</title>\r\n' +
      '    \r\n' +
      '    \r\n' +
      firebaseSpecial +
      '    \r\n' +
      '    \r\n' +
      '  \r\n' +
      '\r\n';

  fsMod.ensureDirSync(pathMod.dirname(target));
  fsMod.writeFileSync(target, contents);

  return target;
}

/**
 * @param {string} script Path of the script, e.g. tests/foo_test.js.
 * @return {string} Generated file path.
 */
function createTestFile(script) {
  var sliceIndex = script.indexOf('/') + 1;
  var target = 'html/' + script.slice(sliceIndex, -2) + 'html';
  var level = target.match(/\//g).length;
  var prefix = new Array(level).join('../') + '../';
  var fakeName = script.replace('/', '$').replace('.', '_');
  var scriptPath = pathMod.resolve(pathMod.join(__dirname, '../' + script));
  var firebaseSpecial = getFirebaseSpecial(script);
  var contents =
      '<!DOCTYPE html>\r\n' +
      '\r\n' +
      '  \r\n' +
      '    <meta charset="utf-8" />\r\n' +
      '    <title>' + pathMod.basename(target).slice(0, -5) + '</title>\r\n' +
      '    \r\n' +
      '    \r\n' +
      '  \r\n' +
      '  \r\n' +
      firebaseSpecial +
      '    \r\n' +
      '  \r\n' +
      '\r\n';
  fsMod.ensureDirSync(pathMod.dirname(target));
  fsMod.writeFileSync(target, contents);
  return target;
}

/**
 * Removes temp folder.
 * @param {string} tempPath
 */
function cleanUp(tempPath) {
  var origPath = process.cwd();
  removeSymLinks();
  process.chdir(origPath);
  fsMod.removeSync(tempPath);
}

/**
 * @param {string} testsDir
 * @return {!IThenable<string>}
 */
function createTestEnv(testsDir) {
  var testEnv = new TestEnv(testsDir);
  return testEnv.create();
}

/** @type {!Function} */
exports.createTestEnv = createTestEnv;

/** @type {!Function} */
exports.cleanUp = cleanUp;
<|endoftext|>


<commit_before>const tap = require('tap')
const getDetails = require('../lib/get-details')
const host = 'http://opengov.cloudapp.net'

tap.test('It requires an options object', test => {
  const options = false
  const expectedErrorMessage = 'Missing required input: options object'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.done()
    })
})

tap.test('It requires options.host', test => {
  const options = {
    host: false
  }
  const expectedErrorMessage = 'Missing required input: options.host'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.done()
    })
})

tap.test('It requires options.host to be a valid url', test => {
  const options = {
    host: 'pysjepreik'
  }
  const expectedErrorMessage = 'Input error: options.host is not a valid url'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.done()
    })
})

tap.test('It requires options.path', test => {
  const options = {
    host: host,
    path: false
  }
  const expectedErrorMessage = 'Missing required input: options.path'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.done()
    })
})

tap.test('It requires options.agendaId', test => {
  const options = {
    host: host,
    path: '/Meetings/tfk',
    agendaId: false
  }
  const expectedErrorMessage = 'Missing required input: options.agendaId'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.done()
    })
})

tap.test('It returns expected results', test => {
  const options = {
    host: host,
    path: '/Meetings/tfk',
    agendaId: '211236'
  }
  const expectedResult = require('./data/get-details-data-full.json')
  return getDetails(options)
    .then(data => {
      tap.equal(JSON.stringify(expectedResult), JSON.stringify(data), 'Returned details OK')
      test.done()
    })
    .catch(error => {
      throw error
    })
})

tap.test('It returns error on error', test => {
  const options = {
    host: 'http://detteerenurlsomsannsynligviseikkefinnes.no',
    path: '/Meetings/tfk',
    agendaId: '211236'
  }
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.ok(error, 'Error returned for details')
      test.done()
    })
})
<commit_msg>Changes test.done to test.end (patch)<commit_after>const tap = require('tap')
const getDetails = require('../lib/get-details')
const host = 'http://opengov.cloudapp.net'

tap.test('It requires an options object', test => {
  const options = false
  const expectedErrorMessage = 'Missing required input: options object'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.end()
    })
})

tap.test('It requires options.host', test => {
  const options = {
    host: false
  }
  const expectedErrorMessage = 'Missing required input: options.host'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.end()
    })
})

tap.test('It requires options.host to be a valid url', test => {
  const options = {
    host: 'pysjepreik'
  }
  const expectedErrorMessage = 'Input error: options.host is not a valid url'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.end()
    })
})

tap.test('It requires options.path', test => {
  const options = {
    host: host,
    path: false
  }
  const expectedErrorMessage = 'Missing required input: options.path'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.end()
    })
})

tap.test('It requires options.agendaId', test => {
  const options = {
    host: host,
    path: '/Meetings/tfk',
    agendaId: false
  }
  const expectedErrorMessage = 'Missing required input: options.agendaId'
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.equal(error.message, expectedErrorMessage, expectedErrorMessage)
      test.end()
    })
})

tap.test('It returns expected results', test => {
  const options = {
    host: host,
    path: '/Meetings/tfk',
    agendaId: '211236'
  }
  const expectedResult = require('./data/get-details-data-full.json')
  return getDetails(options)
    .then(data => {
      tap.equal(JSON.stringify(expectedResult), JSON.stringify(data), 'Returned details OK')
      test.end()
    })
    .catch(error => {
      throw error
    })
})

tap.test('It returns error on error', test => {
  const options = {
    host: 'http://detteerenurlsomsannsynligviseikkefinnes.no',
    path: '/Meetings/tfk',
    agendaId: '211236'
  }
  return getDetails(options)
    .then(console.log)
    .catch(error => {
      tap.ok(error, 'Error returned for details')
      test.end()
    })
})
<|endoftext|>


<commit_before>/*
 * @license
 * Copyright 2016 Google 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.
 */

/**
  Serves to generate executable examples and unit test cases.
*/
foam.CLASS({
  package: 'test.helpers',
  name: 'Exemplar',

  imports: [ 'exemplarRegistry as registry' ],

  properties: [
    {
      /** this Example's unique name */
      name: 'name',
      required: true
    },
    {
      /** a one-line description of the example */
      name: 'description'
    },
    {
      /** Examplars to load and execute before this one. Output code will
        be the merged result of all dependencies. */
      class: 'StringArray',
      name: 'dependencies'
    },
    {
      /** The code for this example. */
      class: 'String',
      name: 'code',
      adapt: function(old, nu) {
        if ( foam.Function.is(nu) ) {
          return nu.toString();
        }
        return nu;
      }
    }
  ],

  methods: [
    function init() {
      this.registry.register(this);
    },

    function generateExample(indent) {
      if ( ! indent ) indent = { level: 0 };
      var ret = "";

      // output each dependency
      if ( this.dependencies ) {
        this.dependencies.foreach(function(depName) {
          var dep = this.registry.lookup(depName);
          ret += dep.generateExample(indent);
        });
      }

      ret += this.outputSelf(indent);

      return ret;
    },

    function outputSelf(indent) {
      // For non-async code
      var ret = "";
      var lines = this.code.split('\n');
      var tabs = "";
      for ( var i = 0; i < indent.level; i++) { tabs += '\t'; }

      ret += tabs + '//' + this.name + '\n';
      ret += tabs + '//' + this.description + '\n';

      lines.forEach(function(line) {
        ret += tabs + line + '\n';
      });
      ret += '\n';

      return ret;
    }

  ]
});

/**
  Serves to generate executable examples and unit test cases.
*/
foam.CLASS({
  package: 'test.helpers',
  name: 'ExemplarRergistry',

  exports: [ 'as exemplarRegistry' ],

  properties: [
    {
      name: 'registrants',
      factory: function() { return {}; }
    }
  ],

  methods: [
    function lookup(name) {
      var found = this.registrants[name];

      if ( ! found ) throw "Examplar " + name + " not found!";

      return found;
    },
    function register(ex) {
      /** Registers an examplar. Re-registering the same one is allowed, but
        two Examplars with the same name is not. */
      var prev = this.lookup(ex.name);
      if ( prev !== ex ) throw "Exemplar " + ex.name + " already registered!";

      this.registrants[ex.name] = ex;
    }
  ]

});
<commit_msg>Async handling added<commit_after>/*
 * @license
 * Copyright 2016 Google 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.
 */

/**
  Serves to generate executable examples and unit test cases.
*/
foam.CLASS({
  package: 'test.helpers',
  name: 'Exemplar',

  imports: [ 'exemplarRegistry as registry' ],

  properties: [
    {
      /** this Example's unique name */
      name: 'name',
      required: true
    },
    {
      /** a one-line description of the example */
      name: 'description'
    },
    {
      /** Examplars to load and execute before this one. Output code will
        be the merged result of all dependencies. */
      class: 'StringArray',
      name: 'dependencies'
    },
    {
      /** Is true if any dependencies are async */
      class: 'Boolean',
      name: 'hasAsyncDeps',
      expression: function(dependencies) {
        dependencies.forEach(function(depName) {
          var dep = this.registry.lookup(depName);
          if ( dep.hasAsyncDeps || dep.isAsync ) {
            return true;
          }
        });
        return false;
      }
    },
    {
      /** Set to true if your code is a function that returns a promise */
      class: 'Boolean',
      name: 'isAsync',
      value: false
    },
    {
      /** The code for this example. This should be either bare code, or a
        function that returns a promise if async. */
      class: 'String',
      name: 'code',
      adapt: function(old, nu) {
        if ( foam.Function.is(nu) ) {
          return nu.toString();
        }
        return nu;
      }
    }
  ],

  methods: [
    function init() {
      this.registry.register(this);
    },

    function generateExample(indent) {
      if ( ! indent ) indent = { level: 0 };
      var ret = "";

      // output each dependency
      if ( this.dependencies ) {
        this.dependencies.forEach(function(depName) {
          var dep = this.registry.lookup(depName);
          if ( dep.hasAsyncDeps || dep.isAsync ) {
            indent.level += 1;
            ret += tabs + "p.push(()\n";
            ret += dep.generateExample(indent);
            ret += tabs + ")());\n";
            indent.level -= 1;
          }
          ret += dep.generateExample(indent);
        });
        // in the simple case we just concat the code, but if anything is async
        // we have to decorate ourselves to become async
        if ( this.hasAsyncDeps ) {
          ret =  tabs + "function() { var p = [];\n" + ret;
          ret += tabs + "return Promise.all(p); }\n";
        }
      }
      ret += this.outputSelf(indent);

      return ret;
    },

    function outputSelf(indent) {
      // For non-async code
      var ret = "";
      var lines = this.code.split('\n');
      var tabs = "";
      for ( var i = 0; i < indent.level; i++) { tabs += '\t'; }

      ret += tabs + '//' + this.name + '\n';
      ret += tabs + '//' + this.description + '\n';

      lines.forEach(function(line) {
        ret += tabs + line + '\n';
      });
      ret += '\n';

      return ret;
    }

  ]
});

/**
  Serves to generate executable examples and unit test cases.
*/
foam.CLASS({
  package: 'test.helpers',
  name: 'ExemplarRergistry',

  exports: [ 'as exemplarRegistry' ],

  properties: [
    {
      name: 'registrants',
      factory: function() { return {}; }
    }
  ],

  methods: [
    function lookup(name) {
      var found = this.registrants[name];

      if ( ! found ) throw "Examplar " + name + " not found!";

      return found;
    },
    function register(ex) {
      /** Registers an examplar. Re-registering the same one is allowed, but
        two Examplars with the same name is not. */
      var prev = this.lookup(ex.name);
      if ( prev !== ex ) throw "Exemplar " + ex.name + " already registered!";

      this.registrants[ex.name] = ex;
    }
  ]

});
<|endoftext|>


<commit_before>'use strict';

beforeEach(function() {

  function cssMatcher(presentClasses, absentClasses) {
    return function() {
      return {
        compare: function(actual) {
          var element = angular.element(actual);
          var present = true;
          var absent = false;

          angular.forEach(presentClasses.split(' '), function(className) {
            present = present && element.hasClass(className);
          });

          angular.forEach(absentClasses.split(' '), function(className) {
            absent = absent || element.hasClass(className);
          });

          var message = function() {
            return 'Expected to have ' + presentClasses +
              (absentClasses ? (' and not have ' + absentClasses + '') : '') +
              ' but had ' + element[0].className + '.';
          };
          return {
            pass: present && !absent,
            message: message
          };
        }
      };
    };
  }

  function DOMTester(a, b) {
    if (a && b && a.nodeType > 0 && b.nodeType > 0) {
      return a === b;
    }
  }

  function isNgElementHidden(element) {
    // we need to check element.getAttribute for SVG nodes
    var hidden = true;
    forEach(angular.element(element), function(element) {
      if ((' '  + (element.getAttribute('class') || '') + ' ').indexOf(' ng-hide ') === -1) {
        hidden = false;
      }
    });
    return hidden;
  }

  function MinErrMatcher(isNot, namespace, code, content, wording) {
    var codeRegex = new RegExp('^' + escapeRegexp('[' + namespace + ':' + code + ']'));
    var contentRegex = angular.isUndefined(content) || jasmine.isA_('RegExp', content) ?
        content : new RegExp(escapeRegexp(content));

    this.test = test;

    function escapeRegexp(str) {
      // This function escapes all special regex characters.
      // We use it to create matching regex from arbitrary strings.
      // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
      return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
    }

    function test(exception) {
      var exceptionMessage = (exception && exception.message) || exception || '';

      var codeMatches = codeRegex.test(exceptionMessage);
      var contentMatches = angular.isUndefined(contentRegex) || contentRegex.test(exceptionMessage);
      var matches = codeMatches && contentMatches;

      return {
        pass: isNot ? !matches : matches,
        message: message
      };

      function message() {
        return 'Expected ' + wording.inputType + (isNot ? ' not' : '') + ' to ' +
            wording.expectedAction + ' ' + namespace + 'MinErr(\'' + code + '\')' +
            (contentRegex ? ' matching ' + contentRegex.toString() : '') +
            (!exception ? '.' : ', but it ' + wording.actualAction + ': ' + exceptionMessage);
      }
    }
  }

  jasmine.addMatchers({
    toBeEmpty: cssMatcher('ng-empty', 'ng-not-empty'),
    toBeNotEmpty: cssMatcher('ng-not-empty', 'ng-empty'),
    toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
    toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
    toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
    toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
    toBeUntouched: cssMatcher('ng-untouched', 'ng-touched'),
    toBeTouched: cssMatcher('ng-touched', 'ng-untouched'),

    toBeAPromise: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn(
            'Expected object ' + (isNot ? 'not ' : '') + 'to be a promise');
          return { pass: isPromiseLike(actual), message: message };
        };
      }
    },

    toBeShown: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn('Expected element ' + (isNot ? '' : 'not ') + 'to have \'ng-hide\' class');
          var pass = !isNgElementHidden(actual);
          if (isNot) {
            pass = !pass;
          }
          return { pass: pass, message: message };
        };
      }
    },

    toBeHidden: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn('Expected element ' + (isNot ? 'not ' : '') + 'to have \'ng-hide\' class');
          var pass = isNgElementHidden(actual);
          if (isNot) {
            pass = !pass;
          }
          return { pass: pass, message: message };
        };
      }
    },

    toEqual: function(util) {
      return {
        compare: function(actual, expected) {
          if (actual && actual.$$log) {
            actual = (typeof expected === 'string')
                ? actual.toString()
                : actual.toArray();
          }
          return {
            pass: util.equals(actual, expected, [DOMTester])
          };
        }
      };
    },

    toEqualOneOf: function(util) {
      return {
        compare: function(actual) {
          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          return {
            pass: expectedArgs.some(function(expected) {
              return util.equals(actual, expected, [DOMTester]);
            })
          };
        }
      };
    },

    toEqualData: function() {
      return {
        compare: function(actual, expected) {
          return { pass: angular.equals(actual, expected) };
        }
      };
    },

    toHaveBeenCalledOnce: function() {
      return {
        compare: function(actual) {
          if (arguments.length > 1) {
            throw new Error('`toHaveBeenCalledOnce` does not take arguments, ' +
                            'use `toHaveBeenCalledOnceWith`');
          }

          if (!jasmine.isSpy(actual)) {
            throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
          }

          var count = actual.calls.count();
          var pass = count === 1;

          var message = function() {
            var msg = 'Expected spy ' + actual.and.identity() + (pass ? ' not ' : ' ') +
                      'to have been called once, but ';

            switch (count) {
              case 0:
                msg += 'it was never called.';
                break;
              case 1:
                msg += 'it was called once.';
                break;
              default:
                msg += 'it was called ' + count + ' times.';
                break;
            }

            return msg;
          };

          return {
            pass: pass,
            message: message
          };
        }
      };
    },

    toHaveBeenCalledOnceWith: function(util, customEqualityTesters) {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual) {
          if (!jasmine.isSpy(actual)) {
            throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
          }

          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          var actualCount = actual.calls.count();
          var actualArgs = actualCount && actual.calls.argsFor(0);

          var pass = (actualCount === 1) && util.equals(actualArgs, expectedArgs);
          if (isNot) pass = !pass;

          var message = function() {
            var msg = 'Expected spy ' + actual.and.identity() + (isNot ? ' not ' : ' ') +
                      'to have been called once with ' + jasmine.pp(expectedArgs) + ', but ';

            if (isNot) {
              msg += 'it was.';
            } else {
              switch (actualCount) {
                case 0:
                  msg += 'it was never called.';
                  break;
                case 1:
                  msg += 'it was called with ' + jasmine.pp(actualArgs) + '.';
                  break;
                default:
                  msg += 'it was called ' + actualCount + ' times.';
                  break;
              }
            }

            return msg;
          };

          return {
            pass: pass,
            message: message
          };
        };
      }
    },

    toBeOneOf: function() {
      return {
        compare: function(actual) {
          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          return { pass: expectedArgs.indexOf(actual) !== -1 };
        }
      };
    },

    toHaveClass: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function hasClass(element, selector) {
        if (!element.getAttribute) return false;
        return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
            indexOf(' ' + selector + ' ') > -1);
      }
      function generateCompare(isNot) {
        return function(actual, clazz) {
          var message = function() {
            return 'Expected \'' + angular.mock.dump(actual) + '\'' + (isNot ? ' not ' : '') + ' to have class \'' + clazz + '\'.';
          };
          var classes = clazz.trim().split(/\s+/);
          for (var i = 0; i < classes.length; ++i) {
            if (!hasClass(actual[0], classes[i])) {
              return { pass: isNot };
            }
          }
          return { pass: !isNot };
        };
      }
    },

    toEqualMinErr: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual, namespace, code, content) {
          var matcher = new MinErrMatcher(isNot, namespace, code, content, {
            inputType: 'error',
            expectedAction: 'equal',
            actualAction: 'was'
          });

          return matcher.test(actual);
        };
      }
    },

    toThrowMinErr: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual, namespace, code, content) {
          var exception;

          if (!angular.isFunction(actual)) {
            throw new Error('Actual is not a function');
          }

          try {
            actual();
          } catch (e) {
            exception = e;
          }

          var matcher = new MinErrMatcher(isNot, namespace, code, content, {
            inputType: 'function',
            expectedAction: 'throw',
            actualAction: 'threw'
          });

          return matcher.test(exception);
        };
      }
    },

    toBeMarkedAsSelected: function() {
      // Selected is special because the element property and attribute reflect each other's state.

      // Support: IE 9 only
      // IE9 will wrongly report hasAttribute('selected') === true when the property is
      // undefined or null, and the dev tools show that no attribute is set

      return {
        compare: function(actual) {
          var errors = [];
          if (actual.selected === null || typeof actual.selected === 'undefined' || actual.selected === false) {
            errors.push('Expected option property "selected" to be truthy');
          }

          // Support: IE 9 only
          if (msie !== 9 && actual.hasAttribute('selected') === false) {
            errors.push('Expected option to have attribute "selected"');
          }

          var result = {
            pass: errors.length === 0,
            message: errors.join('\n')
          };

          return result;
        },
        negativeCompare: function(actual) {
          var errors = [];
          if (actual.selected) {
            errors.push('Expected option property "selected" to be falsy');
          }

          // Support: IE 9 only
          if (msie !== 9 && actual.hasAttribute('selected')) {
            errors.push('Expected option not to have attribute "selected"');
          }

          var result = {
            pass: errors.length === 0,
            message: errors.join('\n')
          };

          return result;
        }
      };
    },
    toEqualSelect: function() {
      return {
        compare: function(actual, expected) {
          var actualValues = [],
              expectedValues = [].slice.call(arguments, 1);

          forEach(actual.find('option'), function(option) {
            actualValues.push(option.selected ? [option.value] : option.value);
          });

          var message = function() {
            return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expectedValues) + '.';
          };

          return {
            pass: equals(expectedValues, actualValues),
            message: message
          };
        }
      };
    }
  });
});

/**
 * Create jasmine.Spy on given method, but ignore calls without arguments
 * This is helpful when need to spy only setter methods and ignore getters
 */
function spyOnlyCallsWithArgs(obj, method) {
  var originalFn = obj[method];
  var spy = spyOn(obj, method);
  obj[method] = function() {
    if (arguments.length) return spy.apply(this, arguments);
    return originalFn.apply(this);
  };
  return spy;
}

// Minimal implementation to mock what was removed from Jasmine 1.x
function createAsync(doneFn) {
  function Job() {
    this.next = [];
  }
  Job.prototype.done = function() {
    return this.runs(doneFn);
  };
  Job.prototype.runs = function(fn) {
    var newJob = new Job();
    this.next.push(function() {
      fn();
      newJob.start();
    });
    return newJob;
  };
  Job.prototype.waitsFor = function(fn, error, timeout) {
    var newJob = new Job();
    timeout = timeout || 5000;
    this.next.push(function() {
      var counter = 0,
        intervalId = window.setInterval(function() {
          if (fn()) {
            window.clearInterval(intervalId);
            newJob.start();
          }
          counter += 5;
          if (counter > timeout) {
            window.clearInterval(intervalId);
            throw new Error(error);
          }
        }, 5);
    });
    return newJob;
  };
  Job.prototype.waits = function(timeout) {
    return this.waitsFor(function() { return true; }, undefined, timeout);
  };
  Job.prototype.start = function() {
    var i;
    for (i = 0; i < this.next.length; i += 1) {
      this.next[i]();
    }
  };
  return new Job();
}

<commit_msg>chore(matchers): improve output for toBeMarkedAsSelected<commit_after>'use strict';

beforeEach(function() {

  function cssMatcher(presentClasses, absentClasses) {
    return function() {
      return {
        compare: function(actual) {
          var element = angular.element(actual);
          var present = true;
          var absent = false;

          angular.forEach(presentClasses.split(' '), function(className) {
            present = present && element.hasClass(className);
          });

          angular.forEach(absentClasses.split(' '), function(className) {
            absent = absent || element.hasClass(className);
          });

          var message = function() {
            return 'Expected to have ' + presentClasses +
              (absentClasses ? (' and not have ' + absentClasses + '') : '') +
              ' but had ' + element[0].className + '.';
          };
          return {
            pass: present && !absent,
            message: message
          };
        }
      };
    };
  }

  function DOMTester(a, b) {
    if (a && b && a.nodeType > 0 && b.nodeType > 0) {
      return a === b;
    }
  }

  function isNgElementHidden(element) {
    // we need to check element.getAttribute for SVG nodes
    var hidden = true;
    forEach(angular.element(element), function(element) {
      if ((' '  + (element.getAttribute('class') || '') + ' ').indexOf(' ng-hide ') === -1) {
        hidden = false;
      }
    });
    return hidden;
  }

  function MinErrMatcher(isNot, namespace, code, content, wording) {
    var codeRegex = new RegExp('^' + escapeRegexp('[' + namespace + ':' + code + ']'));
    var contentRegex = angular.isUndefined(content) || jasmine.isA_('RegExp', content) ?
        content : new RegExp(escapeRegexp(content));

    this.test = test;

    function escapeRegexp(str) {
      // This function escapes all special regex characters.
      // We use it to create matching regex from arbitrary strings.
      // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
      return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
    }

    function test(exception) {
      var exceptionMessage = (exception && exception.message) || exception || '';

      var codeMatches = codeRegex.test(exceptionMessage);
      var contentMatches = angular.isUndefined(contentRegex) || contentRegex.test(exceptionMessage);
      var matches = codeMatches && contentMatches;

      return {
        pass: isNot ? !matches : matches,
        message: message
      };

      function message() {
        return 'Expected ' + wording.inputType + (isNot ? ' not' : '') + ' to ' +
            wording.expectedAction + ' ' + namespace + 'MinErr(\'' + code + '\')' +
            (contentRegex ? ' matching ' + contentRegex.toString() : '') +
            (!exception ? '.' : ', but it ' + wording.actualAction + ': ' + exceptionMessage);
      }
    }
  }

  jasmine.addMatchers({
    toBeEmpty: cssMatcher('ng-empty', 'ng-not-empty'),
    toBeNotEmpty: cssMatcher('ng-not-empty', 'ng-empty'),
    toBeInvalid: cssMatcher('ng-invalid', 'ng-valid'),
    toBeValid: cssMatcher('ng-valid', 'ng-invalid'),
    toBeDirty: cssMatcher('ng-dirty', 'ng-pristine'),
    toBePristine: cssMatcher('ng-pristine', 'ng-dirty'),
    toBeUntouched: cssMatcher('ng-untouched', 'ng-touched'),
    toBeTouched: cssMatcher('ng-touched', 'ng-untouched'),

    toBeAPromise: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn(
            'Expected object ' + (isNot ? 'not ' : '') + 'to be a promise');
          return { pass: isPromiseLike(actual), message: message };
        };
      }
    },

    toBeShown: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn('Expected element ' + (isNot ? '' : 'not ') + 'to have \'ng-hide\' class');
          var pass = !isNgElementHidden(actual);
          if (isNot) {
            pass = !pass;
          }
          return { pass: pass, message: message };
        };
      }
    },

    toBeHidden: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function generateCompare(isNot) {
        return function(actual) {
          var message = valueFn('Expected element ' + (isNot ? 'not ' : '') + 'to have \'ng-hide\' class');
          var pass = isNgElementHidden(actual);
          if (isNot) {
            pass = !pass;
          }
          return { pass: pass, message: message };
        };
      }
    },

    toEqual: function(util) {
      return {
        compare: function(actual, expected) {
          if (actual && actual.$$log) {
            actual = (typeof expected === 'string')
                ? actual.toString()
                : actual.toArray();
          }
          return {
            pass: util.equals(actual, expected, [DOMTester])
          };
        }
      };
    },

    toEqualOneOf: function(util) {
      return {
        compare: function(actual) {
          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          return {
            pass: expectedArgs.some(function(expected) {
              return util.equals(actual, expected, [DOMTester]);
            })
          };
        }
      };
    },

    toEqualData: function() {
      return {
        compare: function(actual, expected) {
          return { pass: angular.equals(actual, expected) };
        }
      };
    },

    toHaveBeenCalledOnce: function() {
      return {
        compare: function(actual) {
          if (arguments.length > 1) {
            throw new Error('`toHaveBeenCalledOnce` does not take arguments, ' +
                            'use `toHaveBeenCalledOnceWith`');
          }

          if (!jasmine.isSpy(actual)) {
            throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
          }

          var count = actual.calls.count();
          var pass = count === 1;

          var message = function() {
            var msg = 'Expected spy ' + actual.and.identity() + (pass ? ' not ' : ' ') +
                      'to have been called once, but ';

            switch (count) {
              case 0:
                msg += 'it was never called.';
                break;
              case 1:
                msg += 'it was called once.';
                break;
              default:
                msg += 'it was called ' + count + ' times.';
                break;
            }

            return msg;
          };

          return {
            pass: pass,
            message: message
          };
        }
      };
    },

    toHaveBeenCalledOnceWith: function(util, customEqualityTesters) {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual) {
          if (!jasmine.isSpy(actual)) {
            throw new Error('Expected a spy, but got ' + jasmine.pp(actual) + '.');
          }

          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          var actualCount = actual.calls.count();
          var actualArgs = actualCount && actual.calls.argsFor(0);

          var pass = (actualCount === 1) && util.equals(actualArgs, expectedArgs);
          if (isNot) pass = !pass;

          var message = function() {
            var msg = 'Expected spy ' + actual.and.identity() + (isNot ? ' not ' : ' ') +
                      'to have been called once with ' + jasmine.pp(expectedArgs) + ', but ';

            if (isNot) {
              msg += 'it was.';
            } else {
              switch (actualCount) {
                case 0:
                  msg += 'it was never called.';
                  break;
                case 1:
                  msg += 'it was called with ' + jasmine.pp(actualArgs) + '.';
                  break;
                default:
                  msg += 'it was called ' + actualCount + ' times.';
                  break;
              }
            }

            return msg;
          };

          return {
            pass: pass,
            message: message
          };
        };
      }
    },

    toBeOneOf: function() {
      return {
        compare: function(actual) {
          var expectedArgs = Array.prototype.slice.call(arguments, 1);
          return { pass: expectedArgs.indexOf(actual) !== -1 };
        }
      };
    },

    toHaveClass: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };
      function hasClass(element, selector) {
        if (!element.getAttribute) return false;
        return ((' ' + (element.getAttribute('class') || '') + ' ').replace(/[\n\t]/g, ' ').
            indexOf(' ' + selector + ' ') > -1);
      }
      function generateCompare(isNot) {
        return function(actual, clazz) {
          var message = function() {
            return 'Expected \'' + angular.mock.dump(actual) + '\'' + (isNot ? ' not ' : '') + ' to have class \'' + clazz + '\'.';
          };
          var classes = clazz.trim().split(/\s+/);
          for (var i = 0; i < classes.length; ++i) {
            if (!hasClass(actual[0], classes[i])) {
              return { pass: isNot };
            }
          }
          return { pass: !isNot };
        };
      }
    },

    toEqualMinErr: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual, namespace, code, content) {
          var matcher = new MinErrMatcher(isNot, namespace, code, content, {
            inputType: 'error',
            expectedAction: 'equal',
            actualAction: 'was'
          });

          return matcher.test(actual);
        };
      }
    },

    toThrowMinErr: function() {
      return {
        compare: generateCompare(false),
        negativeCompare: generateCompare(true)
      };

      function generateCompare(isNot) {
        return function(actual, namespace, code, content) {
          var exception;

          if (!angular.isFunction(actual)) {
            throw new Error('Actual is not a function');
          }

          try {
            actual();
          } catch (e) {
            exception = e;
          }

          var matcher = new MinErrMatcher(isNot, namespace, code, content, {
            inputType: 'function',
            expectedAction: 'throw',
            actualAction: 'threw'
          });

          return matcher.test(exception);
        };
      }
    },

    toBeMarkedAsSelected: function() {
      // Selected is special because the element property and attribute reflect each other's state.

      // Support: IE 9 only
      // IE9 will wrongly report hasAttribute('selected') === true when the property is
      // undefined or null, and the dev tools show that no attribute is set

      return {
        compare: function(actual) {
          var errors = [];
          var optionVal = toJson(actual.value);

          if (actual.selected === null || typeof actual.selected === 'undefined' || actual.selected === false) {
            errors.push('Expected option with value ' + optionVal + ' to have property "selected" set to truthy');
          }

          // Support: IE 9 only
          if (msie !== 9 && actual.hasAttribute('selected') === false) {
            errors.push('Expected option with value ' + optionVal + ' to have attribute "selected"');
          }

          var result = {
            pass: errors.length === 0,
            message: errors.join('\n')
          };

          return result;
        },
        negativeCompare: function(actual) {
          var errors = [];
          var optionVal = toJson(actual.value);

          if (actual.selected) {
            errors.push('Expected option with value ' + optionVal + ' property "selected" to be falsy');
          }

          // Support: IE 9 only
          if (msie !== 9 && actual.hasAttribute('selected')) {
            errors.push('Expected option with value ' + optionVal + ' not to have attribute "selected"');
          }

          var result = {
            pass: errors.length === 0,
            message: errors.join('\n')
          };

          return result;
        }
      };
    },
    toEqualSelect: function() {
      return {
        compare: function(actual, expected) {
          var actualValues = [],
              expectedValues = [].slice.call(arguments, 1);

          forEach(actual.find('option'), function(option) {
            actualValues.push(option.selected ? [option.value] : option.value);
          });

          var message = function() {
            return 'Expected ' + toJson(actualValues) + ' to equal ' + toJson(expectedValues) + '.';
          };

          return {
            pass: equals(expectedValues, actualValues),
            message: message
          };
        }
      };
    }
  });
});

/**
 * Create jasmine.Spy on given method, but ignore calls without arguments
 * This is helpful when need to spy only setter methods and ignore getters
 */
function spyOnlyCallsWithArgs(obj, method) {
  var originalFn = obj[method];
  var spy = spyOn(obj, method);
  obj[method] = function() {
    if (arguments.length) return spy.apply(this, arguments);
    return originalFn.apply(this);
  };
  return spy;
}

// Minimal implementation to mock what was removed from Jasmine 1.x
function createAsync(doneFn) {
  function Job() {
    this.next = [];
  }
  Job.prototype.done = function() {
    return this.runs(doneFn);
  };
  Job.prototype.runs = function(fn) {
    var newJob = new Job();
    this.next.push(function() {
      fn();
      newJob.start();
    });
    return newJob;
  };
  Job.prototype.waitsFor = function(fn, error, timeout) {
    var newJob = new Job();
    timeout = timeout || 5000;
    this.next.push(function() {
      var counter = 0,
        intervalId = window.setInterval(function() {
          if (fn()) {
            window.clearInterval(intervalId);
            newJob.start();
          }
          counter += 5;
          if (counter > timeout) {
            window.clearInterval(intervalId);
            throw new Error(error);
          }
        }, 5);
    });
    return newJob;
  };
  Job.prototype.waits = function(timeout) {
    return this.waitsFor(function() { return true; }, undefined, timeout);
  };
  Job.prototype.start = function() {
    var i;
    for (i = 0; i < this.next.length; i += 1) {
      this.next[i]();
    }
  };
  return new Job();
}

<|endoftext|>


<commit_before>"use strict";

var db       = require('../../models');
var generate = require('../../lib/generate');

var _ = require('lodash');

var sendPostRequest = function(context, path, data, done) {
  request
    .post(path)
    .type('form') // sets Content-Type: application/x-www-form-urlencoded
    .send(data)
    .end(function(err, res) {
      context.res = res;
      done(err);
    });
};

var createRequestBody = function(clientId, grantType) {
  var requestBody = {};

  if (!_.isUndefined(clientId)) {
    requestBody.client_id = clientId;
  }

  if (!_.isUndefined(grantType)) {
    requestBody.grant_type = grantType;
  }

  return requestBody;
}

var resetDatabase = function(done) {
  db.sequelize.query('DELETE FROM PairingCodes').then(function() {
    return db.sequelize.query('DELETE FROM ServiceAccessTokens');
  })
  .then(function() {
    done();
  },
  function(error) {
    done(error);
  });
}

var createPairingCode = function(attributes, done) {
  var data = {
    ClientId:         attributes.clientId,
    device_code:      '8ecf4b2a0df2df7fd69df128e0ac4fcc',
    user_code:        '0a264',
    verification_uri: 'http://www.example.com/verify',
    verified:         attributes.verified
  };

  db.PairingCode
    .create(data)
    .success(function(pairingCode) {
      done();
    });
}

describe("POST /token", function() {
  before(function() {
    sinon.stub(generate, 'accessToken').returns('aed201ffb3362de42700a293bdebf694');
  });

  after(function() {
    generate.accessToken.restore();
  });

  context("Polling to obtain an access token", function() {
    context("with a missing client_id", function() {
      before(function(done) {
        var requestBody = createRequestBody(undefined, 'authorization_code');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with an invalid client_id", function() {
      before(function(done) {
        var requestBody = createRequestBody('unknown', 'authorization_code');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with an invalid grant_type", function() {
      before(function(done) {
        var requestBody = createRequestBody('101', 'unknown');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with a valid client_id", function() {
      context("when the authorization request is pending", function() {
        before(resetDatabase);

        before(function(done) {
          createPairingCode({ clientId: 100, verified: false }, done);
        });

        before(function(done) {
          var requestBody = createRequestBody('100', 'authorization_code');

          sendPostRequest(this, '/token', requestBody, done);
        });

        it("should return status 400", function() {
          expect(this.res.statusCode).to.equal(400);
        });

        it("should return a JSON object", function() {
          expect(this.res.headers['content-type']).to.equal('application/json; charset=utf-8');
          expect(this.res.body).to.be.an('object');
        });

        it("should return authorization_pending error", function() {
          expect(this.res.body).to.have.property('error');
          expect(this.res.body.error).to.equal('authorization_pending');
        });
      });

      // RFC 6749, section 5.1
      context("when the authorization request is successful", function() {
        before(resetDatabase);

        before(function(done) {
          createPairingCode({ clientId: 101, verified: true }, done);
        });

        before(function(done) {
          var requestBody = createRequestBody('101', 'authorization_code');

          sendPostRequest(this, '/token', requestBody, done);
        });

        it("should return status 200", function() {
          expect(this.res.statusCode).to.equal(200);
        });

        it("should return a Cache-Control: no-store header", function() {
          expect(this.res.headers).to.have.property('cache-control');
          expect(this.res.headers['cache-control']).to.equal('no-store');
        });

        it("should return a Pragma: no-cache header", function() {
          expect(this.res.headers).to.have.property('pragma');
          expect(this.res.headers['pragma']).to.equal('no-cache');
        });

        it("should return a JSON object", function() {
          expect(this.res.headers['content-type']).to.equal('application/json; charset=utf-8');
          expect(this.res.body).to.be.an('object');
        });

        describe("the response body", function() {
          it("should include a valid access token", function() {
            expect(this.res.body).to.have.property('token');
            expect(this.res.body.token).to.equal('aed201ffb3362de42700a293bdebf694');
          });

          it("should include the token type", function() {
            expect(this.res.body).to.have.property('token_type');
            expect(this.res.body.token_type).to.equal('bearer');
          });

          // it("should include a valid refresh token"); // optional: refresh_token
          // it("should include the lifetime of the access token"); // recommended: expires_in
          // it("should include the scope of the access token"); // optional(?): scope
        });

        describe("the database", function() {
          before(function(done) {
            var self = this;

            db.ServiceAccessToken
              .findAll()
              .success(function(accessTokens) {
                self.accessTokens = accessTokens;
                done();
              })
              .error(function(error) {
                done(error);
              });
          });

          it("should contain a new access token", function() {
            expect(this.accessTokens).to.be.ok;
            expect(this.accessTokens).to.be.an('array');
            expect(this.accessTokens.length).to.equal(1);
          });

          describe("the access token", function() {
            it("should have correct value", function() {
              expect(this.accessTokens[0].token).to.equal('aed201ffb3362de42700a293bdebf694');
            });

            it("should be associated with the correct client device", function() {
              expect(this.accessTokens[0].ClientId).to.equal(101);
            });

            it("should be associated with the correct user");

            it("should be associated with the correct service provider");
          });
        });
      });
    });

    context("when the client polls too quickly", function() {
      it("should return status 400");
      it("should return slow_down error");
    });
  });
});

describe("generate.accessToken", function() {
  it("should generate access tokens valid according to RFC6750 section 2.1", function() {
    var accessToken = generate.accessToken();

    // b64token    = 1*( ALPHA / DIGIT /
    //       "-" / "." / "_" / "~" / "+" / "/" ) *"="
    expect(accessToken).to.match(/^[A-Za-z0-9\-._~+/]+=*$/);
  });
});
<commit_msg>Added pending tests for refresh token and access token expiry and scope<commit_after>"use strict";

var db       = require('../../models');
var generate = require('../../lib/generate');

var _ = require('lodash');

var sendPostRequest = function(context, path, data, done) {
  request
    .post(path)
    .type('form') // sets Content-Type: application/x-www-form-urlencoded
    .send(data)
    .end(function(err, res) {
      context.res = res;
      done(err);
    });
};

var createRequestBody = function(clientId, grantType) {
  var requestBody = {};

  if (!_.isUndefined(clientId)) {
    requestBody.client_id = clientId;
  }

  if (!_.isUndefined(grantType)) {
    requestBody.grant_type = grantType;
  }

  return requestBody;
}

var resetDatabase = function(done) {
  db.sequelize.query('DELETE FROM PairingCodes').then(function() {
    return db.sequelize.query('DELETE FROM ServiceAccessTokens');
  })
  .then(function() {
    done();
  },
  function(error) {
    done(error);
  });
}

var createPairingCode = function(attributes, done) {
  var data = {
    ClientId:         attributes.clientId,
    device_code:      '8ecf4b2a0df2df7fd69df128e0ac4fcc',
    user_code:        '0a264',
    verification_uri: 'http://www.example.com/verify',
    verified:         attributes.verified
  };

  db.PairingCode
    .create(data)
    .success(function(pairingCode) {
      done();
    });
}

describe("POST /token", function() {
  before(function() {
    sinon.stub(generate, 'accessToken').returns('aed201ffb3362de42700a293bdebf694');
  });

  after(function() {
    generate.accessToken.restore();
  });

  context("Polling to obtain an access token", function() {
    context("with a missing client_id", function() {
      before(function(done) {
        var requestBody = createRequestBody(undefined, 'authorization_code');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with an invalid client_id", function() {
      before(function(done) {
        var requestBody = createRequestBody('unknown', 'authorization_code');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with an invalid grant_type", function() {
      before(function(done) {
        var requestBody = createRequestBody('101', 'unknown');

        sendPostRequest(this, '/token', requestBody, done);
      });

      it("should return status 400", function() {
        expect(this.res.statusCode).to.equal(400);
      });
    });

    context("with a valid client_id", function() {
      context("when the authorization request is pending", function() {
        before(resetDatabase);

        before(function(done) {
          createPairingCode({ clientId: 100, verified: false }, done);
        });

        before(function(done) {
          var requestBody = createRequestBody('100', 'authorization_code');

          sendPostRequest(this, '/token', requestBody, done);
        });

        it("should return status 400", function() {
          expect(this.res.statusCode).to.equal(400);
        });

        it("should return a JSON object", function() {
          expect(this.res.headers['content-type']).to.equal('application/json; charset=utf-8');
          expect(this.res.body).to.be.an('object');
        });

        it("should return authorization_pending error", function() {
          expect(this.res.body).to.have.property('error');
          expect(this.res.body.error).to.equal('authorization_pending');
        });
      });

      // RFC 6749, section 5.1
      context("when the authorization request is successful", function() {
        before(resetDatabase);

        before(function(done) {
          createPairingCode({ clientId: 101, verified: true }, done);
        });

        before(function(done) {
          var requestBody = createRequestBody('101', 'authorization_code');

          sendPostRequest(this, '/token', requestBody, done);
        });

        it("should return status 200", function() {
          expect(this.res.statusCode).to.equal(200);
        });

        it("should return a Cache-Control: no-store header", function() {
          expect(this.res.headers).to.have.property('cache-control');
          expect(this.res.headers['cache-control']).to.equal('no-store');
        });

        it("should return a Pragma: no-cache header", function() {
          expect(this.res.headers).to.have.property('pragma');
          expect(this.res.headers['pragma']).to.equal('no-cache');
        });

        it("should return a JSON object", function() {
          expect(this.res.headers['content-type']).to.equal('application/json; charset=utf-8');
          expect(this.res.body).to.be.an('object');
        });

        describe("the response body", function() {
          it("should include a valid access token", function() {
            expect(this.res.body).to.have.property('token');
            expect(this.res.body.token).to.equal('aed201ffb3362de42700a293bdebf694');
          });

          it("should include the token type", function() {
            expect(this.res.body).to.have.property('token_type');
            expect(this.res.body.token_type).to.equal('bearer');
          });

          it("should include a valid refresh token"); // TODO: optional: refresh_token
          it("should include the lifetime of the access token"); // TODO: recommended: expires_in
          it("should include the scope of the access token"); // TODO: optional(?): scope
        });

        describe("the database", function() {
          before(function(done) {
            var self = this;

            db.ServiceAccessToken
              .findAll()
              .success(function(accessTokens) {
                self.accessTokens = accessTokens;
                done();
              })
              .error(function(error) {
                done(error);
              });
          });

          it("should contain a new access token", function() {
            expect(this.accessTokens).to.be.ok;
            expect(this.accessTokens).to.be.an('array');
            expect(this.accessTokens.length).to.equal(1);
          });

          describe("the access token", function() {
            it("should have correct value", function() {
              expect(this.accessTokens[0].token).to.equal('aed201ffb3362de42700a293bdebf694');
            });

            it("should be associated with the correct client device", function() {
              expect(this.accessTokens[0].ClientId).to.equal(101);
            });

            it("should be associated with the correct user");

            it("should be associated with the correct service provider");
          });
        });
      });
    });

    context("when the client polls too quickly", function() {
      it("should return status 400");
      it("should return slow_down error");
    });
  });
});

describe("generate.accessToken", function() {
  it("should generate access tokens valid according to RFC6750 section 2.1", function() {
    var accessToken = generate.accessToken();

    // b64token    = 1*( ALPHA / DIGIT /
    //       "-" / "." / "_" / "~" / "+" / "/" ) *"="
    expect(accessToken).to.match(/^[A-Za-z0-9\-._~+/]+=*$/);
  });
});
<|endoftext|>


<commit_before>import isGlob from '../../src/lib/is-glob'

// eslint-disable-next-line no-bitwise
const isGloby = value => !!~isGlob(value)

describe('lib/is-glob', () => {
  describe('truthy', () => {
    it('should match asterisk wildcard', () => {
      expect(isGloby('*')).toBe(true)
      expect(isGloby('*.js')).toBe(true)
      expect(isGloby('/*.js')).toBe(true)
    })

    it('should match globstar wildcard', () => {
      expect(isGloby('**')).toBe(true)
      expect(isGloby('**/a.js')).toBe(true)
    })

    it('should match question mark wildcard', () => {
      expect(isGloby('?')).toBe(true)
      expect(isGloby('?.js')).toBe(true)
      expect(isGloby('/?.js')).toBe(true)
    })

    it('should match braced sections', () => {
      expect(isGloby('{a,b}')).toBe(true)
      expect(isGloby('abc/{a,b}')).toBe(true)
    })

    it('should match range wildcards', () => {
      expect(isGloby('[a-z]')).toBe(true)
      expect(isGloby('[a-z].js')).toBe(true)
      expect(isGloby('/[a-z]')).toBe(true)
      expect(isGloby('[!a-z]')).toBe(true)
      expect(isGloby('[^a-z]')).toBe(true)
    })

    it('should match extended globs', () => {
      expect(isGloby('!(a|b|c)')).toBe(true)
      expect(isGloby('?(a|b|c)')).toBe(true)
      expect(isGloby('+(a|b|c)')).toBe(true)
      expect(isGloby('*(a|b|c)')).toBe(true)
      expect(isGloby('@(a|b|c)')).toBe(true)
    })
  })

  describe('falsy', () => {
    it('should not match escaped asterisks', () => {
      expect(isGloby('\\*')).toBe(false)
      expect(isGloby('\\*.js')).toBe(false)
      expect(isGloby('/\\*.js')).toBe(false)
    })

    it('should not match escaped globstar', () => {
      expect(isGloby('\\*\\*')).toBe(false)
      expect(isGloby('\\*\\*/a.js')).toBe(false)
    })

    it('should not match escaped question mark', () => {
      expect(isGloby('\\?')).toBe(false)
      expect(isGloby('\\?.js')).toBe(false)
      expect(isGloby('/\\?.js')).toBe(false)
    })

    it('should not match escaped braced sections', () => {
      expect(isGloby('\\{a,b}')).toBe(false)
      expect(isGloby('abc/\\{a,b}')).toBe(false)

      expect(isGloby('{a,b\\}')).toBe(false)
      expect(isGloby('abc/{a,b\\}')).toBe(false)
    })

    it('should not match escaped range wildcards', () => {
      expect(isGloby('\\[a-z]')).toBe(false)
      expect(isGloby('\\[a-z].js')).toBe(false)
      expect(isGloby('/\\[a-z]')).toBe(false)
      expect(isGloby('\\[!a-z]')).toBe(false)
      expect(isGloby('\\[^a-z]')).toBe(false)

      expect(isGloby('[a-z\\]')).toBe(false)
      expect(isGloby('[a-z\\].js')).toBe(false)
      expect(isGloby('/[a-z\\]')).toBe(false)
      expect(isGloby('[!a-z\\]')).toBe(false)
      expect(isGloby('[^a-z\\]')).toBe(false)
    })

    it('should not match escaped extended globs', () => {
      expect(isGloby('\\!(a|b|c)')).toBe(false)
      expect(isGloby('\\?(a|b|c)')).toBe(false)
      expect(isGloby('\\+(a|b|c)')).toBe(false)
      expect(isGloby('\\*(a|b|c)')).toBe(false)
      expect(isGloby('\\@(a|b|c)')).toBe(false)

      expect(isGloby('!\\(a|b|c)')).toBe(false)
      // expect(isGloby('?\\(a|b|c)')).toBe(false)
      expect(isGloby('+\\(a|b|c)')).toBe(false)
      // expect(isGloby('*\\(a|b|c)')).toBe(false)
      expect(isGloby('@\\(a|b|c)')).toBe(false)

      expect(isGloby('!(a|b|c\\)')).toBe(false)
      // expect(isGloby('?(a|b|c\\)')).toBe(false)
      expect(isGloby('+(a|b|c\\)')).toBe(false)
      // expect(isGloby('*(a|b|c\\)')).toBe(false)
      expect(isGloby('@(a|b|c\\)')).toBe(false)
    })

    it('should not match non-globs', () => {
      expect(isGloby('abc.js')).toBe(false)
      expect(isGloby('abc/def/ghi.js')).toBe(false)
      expect(isGloby('foo.js')).toBe(false)
      expect(isGloby('abc/@.js')).toBe(false)
      expect(isGloby('abc/+.js')).toBe(false)
      expect(isGloby()).toBe(false)
      expect(isGloby(null)).toBe(false)
    })
  })
})
<commit_msg>added tests for glob exclusion<commit_after>import isGlob from '../../src/lib/is-glob'

// eslint-disable-next-line no-bitwise
const isGloby = value => !!~isGlob(value)

describe('lib/is-glob', () => {
  describe('truthy', () => {
    it('should match asterisk wildcard', () => {
      expect(isGloby('*')).toBe(true)
      expect(isGloby('*.js')).toBe(true)
      expect(isGloby('/*.js')).toBe(true)
    })

    it('should match globstar wildcard', () => {
      expect(isGloby('**')).toBe(true)
      expect(isGloby('**/a.js')).toBe(true)
    })

    it('should match question mark wildcard', () => {
      expect(isGloby('?')).toBe(true)
      expect(isGloby('?.js')).toBe(true)
      expect(isGloby('/?.js')).toBe(true)
    })

    it('should match braced sections', () => {
      expect(isGloby('{a,b}')).toBe(true)
      expect(isGloby('abc/{a,b}')).toBe(true)
    })

    it('should match range wildcards', () => {
      expect(isGloby('[a-z]')).toBe(true)
      expect(isGloby('[a-z].js')).toBe(true)
      expect(isGloby('/[a-z]')).toBe(true)
      expect(isGloby('[!a-z]')).toBe(true)
      expect(isGloby('[^a-z]')).toBe(true)
    })

    it('should match extended globs', () => {
      expect(isGloby('!(a|b|c)')).toBe(true)
      expect(isGloby('?(a|b|c)')).toBe(true)
      expect(isGloby('+(a|b|c)')).toBe(true)
      expect(isGloby('*(a|b|c)')).toBe(true)
      expect(isGloby('@(a|b|c)')).toBe(true)
    })

    it('should match glob exclusion', () => {
      expect(isGloby('!foo')).toBe(true)
      expect(isGloby('!foo/bar')).toBe(true)
      expect(isGloby('!foo/*')).toBe(true)
      expect(isGloby('!foo/bar/**/*')).toBe(true)
    })
  })

  describe('falsy', () => {
    it('should not match escaped asterisks', () => {
      expect(isGloby('\\*')).toBe(false)
      expect(isGloby('\\*.js')).toBe(false)
      expect(isGloby('/\\*.js')).toBe(false)
    })

    it('should not match escaped globstar', () => {
      expect(isGloby('\\*\\*')).toBe(false)
      expect(isGloby('\\*\\*/a.js')).toBe(false)
    })

    it('should not match escaped question mark', () => {
      expect(isGloby('\\?')).toBe(false)
      expect(isGloby('\\?.js')).toBe(false)
      expect(isGloby('/\\?.js')).toBe(false)
    })

    it('should not match escaped braced sections', () => {
      expect(isGloby('\\{a,b}')).toBe(false)
      expect(isGloby('abc/\\{a,b}')).toBe(false)

      expect(isGloby('{a,b\\}')).toBe(false)
      expect(isGloby('abc/{a,b\\}')).toBe(false)
    })

    it('should not match escaped range wildcards', () => {
      expect(isGloby('\\[a-z]')).toBe(false)
      expect(isGloby('\\[a-z].js')).toBe(false)
      expect(isGloby('/\\[a-z]')).toBe(false)
      expect(isGloby('\\[!a-z]')).toBe(false)
      expect(isGloby('\\[^a-z]')).toBe(false)

      expect(isGloby('[a-z\\]')).toBe(false)
      expect(isGloby('[a-z\\].js')).toBe(false)
      expect(isGloby('/[a-z\\]')).toBe(false)
      expect(isGloby('[!a-z\\]')).toBe(false)
      expect(isGloby('[^a-z\\]')).toBe(false)
    })

    it('should not match escaped extended globs', () => {
      expect(isGloby('\\!(a|b|c)')).toBe(false)
      expect(isGloby('\\?(a|b|c)')).toBe(false)
      expect(isGloby('\\+(a|b|c)')).toBe(false)
      expect(isGloby('\\*(a|b|c)')).toBe(false)
      expect(isGloby('\\@(a|b|c)')).toBe(false)

      expect(isGloby('!\\(a|b|c)')).toBe(false)
      // expect(isGloby('?\\(a|b|c)')).toBe(false)
      expect(isGloby('+\\(a|b|c)')).toBe(false)
      // expect(isGloby('*\\(a|b|c)')).toBe(false)
      expect(isGloby('@\\(a|b|c)')).toBe(false)

      expect(isGloby('!(a|b|c\\)')).toBe(false)
      // expect(isGloby('?(a|b|c\\)')).toBe(false)
      expect(isGloby('+(a|b|c\\)')).toBe(false)
      // expect(isGloby('*(a|b|c\\)')).toBe(false)
      expect(isGloby('@(a|b|c\\)')).toBe(false)
    })

    it('should not match empty glob exclusion', () => {
      expect(isGloby('!')).toBe(false)
    })

    it('should not match escaped glob exclusion', () => {
      expect(isGloby('\\!')).toBe(false)
      expect(isGloby('\\!foo')).toBe(false)
      expect(isGloby('\\!foo/bar')).toBe(false)
    })

    it('should not match non-globs', () => {
      expect(isGloby('abc.js')).toBe(false)
      expect(isGloby('abc/def/ghi.js')).toBe(false)
      expect(isGloby('foo.js')).toBe(false)
      expect(isGloby('abc/@.js')).toBe(false)
      expect(isGloby('abc/+.js')).toBe(false)
      expect(isGloby()).toBe(false)
      expect(isGloby(null)).toBe(false)
    })
  })
})
<|endoftext|>