<commit_before>/**
*  # Group View module
*
*  ## License
*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you 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.
*
*  ## Description
*
*  This module lists all pads linked to the group.
*/

module.exports = (function () {
  'use strict';
  // Global dependencies
  var m = require('mithril');
  var ld = require('lodash');
  // Local dependencies
  var conf = require('../configuration.js');
  var GROUP = conf.LANG.GROUP;
  var auth = require('../auth.js');
  var layout = require('./layout.js');
  var model = require('../model/group.js');

  var group = {};

  /**
  * ## Controller
  *
  * Used for group, pads and users data.
  * Ensures that models are already loaded, either load them.
  */

  group.controller = function () {
    if (!auth.isAuthenticated()) {
      return m.route('/login');
    }

    var c = {};
    c.bookmarks = auth.userInfo().bookmarks.pads;

    var init = function () {
      var key = m.route.param('key');
      c.group = model.data()[key];
      ld.forEach(['pads', 'admins', 'users'], function (f) {
        c[f] = ld.map(c.group[f], function (x) { return model[f]()[x]; });
      });
    };

    if (ld.isEmpty(model.data())) { model.fetch(init); } else { init(); }

    return c;
  };

  /**
  * ## Views
  */

  var view = {};

  /*
  * ### group view
  *
  * `properties` section for displaying chodsen options of the group
  */

  view.properties = function (c) {
    return m('section', [
      m('dl.block-group.group', [
        m('dt.block', GROUP.PAD.PADS),
        m('dd.block', ld.size(c.group.pads)),
        m('dt.block', GROUP.PAD.ADMINS),
        m('dd.block', ld.size(c.group.admins)),
        m('dt.block', GROUP.PAD.USERS),
        m('dd.block', ld.size(c.group.users)),
        m('dt.block', GROUP.PAD.VISIBILITY),
        m('dd.block', c.group.visibility),
        m('dt.block', GROUP.FIELD.READONLY),
        m('dd.block', c.group.readonly),
        m('dt.block', GROUP.TAGS.TITLE),
        m('dd.block', c.group.tags.join(', '))
      ])
    ]);
  };

  /**
  * ### pads
  *
  * View for all linked `pads`, name and actions.
  */

  view.pads = function (c) {
    var route = '/mypads/group/' + c.group._id;
    return m('section.pad', [
      m('a.add', { href: route + '/pad/add', config: m.route }, [
        m('i.icon-plus-squared'),
        GROUP.PAD.ADD
      ]),
      (function () {
        if (ld.size(c.group.pads) === 0) {
          return m('p', GROUP.PAD.NONE);
        } else {
          return m('ul', ld.map(c.pads, function (p) {
            var isBookmarked = ld.includes(c.bookmarks, p._id);
            return m('li.block-group', [
              m('span.block.name', [
                m('a', {
                  href: route + '/pad/view/' + p._id,
                  config: m.route,
                  title: GROUP.VIEW
                }, p.name)
              ]),
              m('span.block.actions', [
                m('a', {
                  href: route + '/pad/mark/' + p._id,
                  config: m.route,
                  title: (isBookmarked ? GROUP.UNMARK : GROUP.BOOKMARK)
                }, [
                  m('i',
                    { class: 'icon-star' + (isBookmarked ? '' : '-empty') })
                ]),
                m('a', {
                  href: route + '/pad/view/' + p._id,
                  config: m.route,
                  title: GROUP.VIEW
                }, [ m('i.icon-book-open') ]),
                m('a', {
                  href: route + '/pad/edit/' + p._id,
                  config: m.route,
                  title: GROUP.EDIT
                }, [ m('i.icon-pencil') ]),
                (function () {
                  if (c.group.visibility !== 'restricted') {
                    return m('a', {
                      href: route + '/pad/share/' + p._id,
                      config: m.route,
                      title: GROUP.SHARE
                    }, [ m('i.icon-share') ]);
                  }
                })(),
                m('a', {
                  href: route + '/pad/remove/' + p._id,
                  config: m.route,
                  title: GROUP.REMOVE
                }, [ m('i.icon-trash') ]),
              ])
            ]);
          }));
        }
      })()
    ]);
  };

  /**
  * ### users
  *
  * View for all `users` and admins, displayed with some information and quick
  * actions. `users` block is shown only if group has `visibility` *restricted*.
  */

  view.users = function (c) {
    var userView = function (u) {
      var res = '';
      if (u.firstname) {
        res += (u.firstname + ' ' + u.lastname + ' ');
      } else {
        res += u.login;
      }
      return res + ' : ' + u.email;

    };
    var list = function (users) {
      if (ld.size(users) === 0) {
        return m('p', GROUP.PAD.USERS_NONE);
      } else {
        return m('ul', ld.map(users, function (u) {
          return m('li', userView(u));
        }));
      }
    };
    var route = '/mypads/group/' + c.group._id;
    var sectionElements = [
      m('h4.block', GROUP.PAD.ADMINS),
      m('a.add', { href: route + '/user/share', config: m.route },
        [ m('i.icon-plus-squared'), GROUP.SHARE_ADMIN ]),
      list(c.admins)
    ];
    if (c.group.visibility === 'restricted') {
      sectionElements.push(m('h4.block', GROUP.PAD.USERS),
        m('a.add', { href: route + '/user/invite', config: m.route },
          [ m('i.icon-plus-squared'), GROUP.INVITE_USER.IU ]),
        list(c.users));
    }
    return m('section', sectionElements);
  };

  /**
  * ### main
  *
  * `main` view, composed by properties, pads and users.
  */

  view.main = function (c) {
    return m('section', { class: 'block-group group' }, [
      m('h2.block', GROUP.GROUP + ' ' + c.group.name),
      m('section.block.props', [
        m('h3.title', GROUP.PROPERTIES),
        view.properties(c)
      ]),
      m('section.block.pads', [
        m('h3.title', GROUP.PAD.PADS),
        view.pads(c)
      ]),
      m('section.block.users', [
        m('h3.title', GROUP.PAD.ADMINS + ' & ' + GROUP.PAD.USERS),
        view.users(c)
      ])
    ]);
  };

  /**
  * ### aside
  *
  * aside function, here some help and explanation
  */

  view.aside = function () {
    return m('section.user-aside', [
      m('h2', conf.LANG.ACTIONS.HELP),
      m('article', m.trust(GROUP.VIEW_HELP))
    ]);
  };

  group.view = function (c) {
    return layout.view(view.main(c), view.aside(c));
  };

  return group;
}).call(this);
<commit_msg>frontend group view module enhancement : edit and remove directly<commit_after>/**
*  # Group View module
*
*  ## License
*
*  Licensed to the Apache Software Foundation (ASF) under one
*  or more contributor license agreements.  See the NOTICE file
*  distributed with this work for additional information
*  regarding copyright ownership.  The ASF licenses this file
*  to you 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.
*
*  ## Description
*
*  This module lists all pads linked to the group.
*/

module.exports = (function () {
  'use strict';
  // Global dependencies
  var m = require('mithril');
  var ld = require('lodash');
  // Local dependencies
  var conf = require('../configuration.js');
  var GROUP = conf.LANG.GROUP;
  var auth = require('../auth.js');
  var layout = require('./layout.js');
  var model = require('../model/group.js');

  var group = {};

  /**
  * ## Controller
  *
  * Used for group, pads and users data.
  * Ensures that models are already loaded, either load them.
  */

  group.controller = function () {
    if (!auth.isAuthenticated()) {
      return m.route('/login');
    }

    var c = {};
    c.bookmarks = auth.userInfo().bookmarks.pads;

    var init = function () {
      var key = m.route.param('key');
      c.group = model.data()[key];
      ld.forEach(['pads', 'admins', 'users'], function (f) {
        c[f] = ld.map(c.group[f], function (x) { return model[f]()[x]; });
      });
    };

    if (ld.isEmpty(model.data())) { model.fetch(init); } else { init(); }

    return c;
  };

  /**
  * ## Views
  */

  var view = {};

  /*
  * ### group view
  *
  * `properties` section for displaying chodsen options of the group
  */

  view.properties = function (c) {
    return m('section', [
      m('dl.block-group.group', [
        m('dt.block', GROUP.PAD.PADS),
        m('dd.block', ld.size(c.group.pads)),
        m('dt.block', GROUP.PAD.ADMINS),
        m('dd.block', ld.size(c.group.admins)),
        m('dt.block', GROUP.PAD.USERS),
        m('dd.block', ld.size(c.group.users)),
        m('dt.block', GROUP.PAD.VISIBILITY),
        m('dd.block', c.group.visibility),
        m('dt.block', GROUP.FIELD.READONLY),
        m('dd.block', c.group.readonly),
        m('dt.block', GROUP.TAGS.TITLE),
        m('dd.block', c.group.tags.join(', '))
      ])
    ]);
  };

  /**
  * ### pads
  *
  * View for all linked `pads`, name and actions.
  */

  view.pads = function (c) {
    var route = '/mypads/group/' + c.group._id;
    return m('section.pad', [
      m('a.add', { href: route + '/pad/add', config: m.route }, [
        m('i.icon-plus-squared'),
        GROUP.PAD.ADD
      ]),
      (function () {
        if (ld.size(c.group.pads) === 0) {
          return m('p', GROUP.PAD.NONE);
        } else {
          return m('ul', ld.map(c.pads, function (p) {
            var isBookmarked = ld.includes(c.bookmarks, p._id);
            return m('li.block-group', [
              m('span.block.name', [
                m('a', {
                  href: route + '/pad/view/' + p._id,
                  config: m.route,
                  title: GROUP.VIEW
                }, p.name)
              ]),
              m('span.block.actions', [
                m('a', {
                  href: route + '/pad/mark/' + p._id,
                  config: m.route,
                  title: (isBookmarked ? GROUP.UNMARK : GROUP.BOOKMARK)
                }, [
                  m('i',
                    { class: 'icon-star' + (isBookmarked ? '' : '-empty') })
                ]),
                m('a', {
                  href: route + '/pad/view/' + p._id,
                  config: m.route,
                  title: GROUP.VIEW
                }, [ m('i.icon-book-open') ]),
                m('a', {
                  href: route + '/pad/edit/' + p._id,
                  config: m.route,
                  title: GROUP.EDIT
                }, [ m('i.icon-pencil') ]),
                (function () {
                  if (c.group.visibility !== 'restricted') {
                    return m('a', {
                      href: route + '/pad/share/' + p._id,
                      config: m.route,
                      title: GROUP.SHARE
                    }, [ m('i.icon-share') ]);
                  }
                })(),
                m('a', {
                  href: route + '/pad/remove/' + p._id,
                  config: m.route,
                  title: GROUP.REMOVE
                }, [ m('i.icon-trash') ]),
              ])
            ]);
          }));
        }
      })()
    ]);
  };

  /**
  * ### users
  *
  * View for all `users` and admins, displayed with some information and quick
  * actions. `users` block is shown only if group has `visibility` *restricted*.
  */

  view.users = function (c) {
    var userView = function (u) {
      var res = '';
      if (u.firstname) {
        res += (u.firstname + ' ' + u.lastname + ' ');
      } else {
        res += u.login;
      }
      return res + ' : ' + u.email;

    };
    var list = function (users) {
      if (ld.size(users) === 0) {
        return m('p', GROUP.PAD.USERS_NONE);
      } else {
        return m('ul', ld.map(users, function (u) {
          return m('li', userView(u));
        }));
      }
    };
    var route = '/mypads/group/' + c.group._id;
    var sectionElements = [
      m('h4.block', GROUP.PAD.ADMINS),
      m('a.add', { href: route + '/user/share', config: m.route },
        [ m('i.icon-plus-squared'), GROUP.SHARE_ADMIN ]),
      list(c.admins)
    ];
    if (c.group.visibility === 'restricted') {
      sectionElements.push(m('h4.block', GROUP.PAD.USERS),
        m('a.add', { href: route + '/user/invite', config: m.route },
          [ m('i.icon-plus-squared'), GROUP.INVITE_USER.IU ]),
        list(c.users));
    }
    return m('section', sectionElements);
  };

  /**
  * ### main
  *
  * `main` view, composed by properties, pads and users.
  */

  view.main = function (c) {
    return m('section', { class: 'block-group group' }, [
      m('h2.block', [
        m('span', GROUP.GROUP + ' ' + c.group.name),
        m('a', {
          href: '/mypads/group/' + c.group._id + '/edit',
          config: m.route,
          title: GROUP.EDIT
        }, [ m('i.icon-pencil') ]),
        m('a', {
          href: '/mypads/group/' + c.group._id + '/remove',
          config: m.route,
          title: GROUP.REMOVE
        }, [ m('i.icon-trash') ])
      ]),
      m('section.block.props', [
        m('h3.title', GROUP.PROPERTIES),
        view.properties(c)
      ]),
      m('section.block.pads', [
        m('h3.title', GROUP.PAD.PADS),
        view.pads(c)
      ]),
      m('section.block.users', [
        m('h3.title', GROUP.PAD.ADMINS + ' & ' + GROUP.PAD.USERS),
        view.users(c)
      ])
    ]);
  };

  /**
  * ### aside
  *
  * aside function, here some help and explanation
  */

  view.aside = function () {
    return m('section.user-aside', [
      m('h2', conf.LANG.ACTIONS.HELP),
      m('article', m.trust(GROUP.VIEW_HELP))
    ]);
  };

  group.view = function (c) {
    return layout.view(view.main(c), view.aside(c));
  };

  return group;
}).call(this);
<|endoftext|>


<commit_before>/**
 * Event.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    title: {
      type: 'STRING',
      required: true
    },
    content: {
      type: 'STRING'
    },
    readed: {
      type: 'JSON'
    },
    createdBy: {
      model: 'user'
    },
    guests: {
      collection: 'user',
      via: 'participated',
      dominant: true
    }
  }
};<commit_msg>[master] Change association<commit_after>/**
 * Event.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/#!documentation/models
 */

module.exports = {

  attributes: {
    title: {
      type: 'STRING',
      required: true
    },
    content: {
      type: 'STRING'
    },
    readed: {
      type: 'JSON'
    },
    createdBy: {
      collection: 'user',
      via: 'owner'
    },
    guests: {
      collection: 'user',
      via: 'participated',
      dominant: true
    }
  }
};<|endoftext|>


<commit_before>global.stub_out_jquery();

set_global('ui', {
    update_scrollbar: function () {},
});

set_global('i18n', global.stub_i18n);
set_global('channel', {});

zrequire('stream_data');
zrequire('Handlebars', 'handlebars');
zrequire('templates');
zrequire('subs');

var jsdom = require("jsdom");
var window = jsdom.jsdom().defaultView;
global.$ = require('jquery')(window);
set_global('window', window);
zrequire('bootstrap', 'third/bootstrap/js/bootstrap');

subs.stream_name_match_stream_ids = [];
subs.stream_description_match_stream_ids = [];

run_test('filter_table', () => {
    var denmark = {
        subscribed: false,
        name: 'Denmark',
        stream_id: 1,
        description: 'Copenhagen',
    };
    var poland = {
        subscribed: true,
        name: 'Poland',
        stream_id: 2,
        description: 'monday',
    };
    var pomona = {
        subscribed: true,
        name: 'Pomona',
        stream_id: 3,
        description: 'college',
    };
    var cpp = {
        subscribed: true,
        name: 'C++',
        stream_id: 4,
    };

    var elem_1 = $(global.render_template("subscription", denmark));
    var elem_2 = $(global.render_template("subscription", poland));
    var elem_3 = $(global.render_template("subscription", pomona));
    var elem_4 = $(global.render_template("subscription", cpp));

    $("body").empty();
    $("body").append('

');
    var streams_list = $('

');
    $("#subscriptions_table").append(streams_list);

    stream_data.add_sub("Denmark", denmark);
    stream_data.add_sub("Poland", poland);
    stream_data.add_sub("Pomona", pomona);
    stream_data.add_sub("C++", cpp);

    streams_list.append(elem_1);
    streams_list.append(elem_2);
    streams_list.append(elem_3);

    // Search with single keyword
    subs.filter_table({input: "Po", subscribed_only: false});
    assert(elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(!elem_3.hasClass("notdisplayed"));

    // Search with multiple keywords
    subs.filter_table({input: "Denmark, Pol", subscribed_only: false});
    assert(!elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));

    subs.filter_table({input: "Den, Pol", subscribed_only: false});
    assert(!elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));

    // Search is case-insensitive
    subs.filter_table({input: "po", subscribed_only: false});
    assert(elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(!elem_3.hasClass("notdisplayed"));

    // Search handles unusual characters like C++
    subs.filter_table({input: "c++", subscribed_only: false});
    assert(elem_1.hasClass("notdisplayed"));
    assert(elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));
    assert(!elem_4.hasClass("notdisplayed"));

    // Search subscribed streams only
    subs.filter_table({input: "d", subscribed_only: true});
    assert(elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));

    // data-temp-view condition
    elem_1.attr("data-temp-view", "true");

    subs.filter_table({input: "d", subscribed_only: true});
    assert(!elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));

    elem_1.attr("data-temp-view", "false");

    subs.filter_table({input: "d", subscribed_only: true});
    assert(elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(elem_3.hasClass("notdisplayed"));

    elem_1.removeAttr("data-temp-view");

    // active stream-row is not included in results
    elem_1.addClass("active");
    $("#subscriptions_table").append($('

'));
    $(".right").append($('

'));
    $(".right").append($('

').hide());

    subs.filter_table({input: "d", subscribed_only: true});
    assert(!elem_1.hasClass("active"));
    assert.equal($(".right .settings").css("display"), "none");
    assert.notEqual($(".right .nothing-selected").css("display"), "none");

    // Search terms match stream description
    subs.filter_table({input: "Co", subscribed_only: false});
    assert(!elem_1.hasClass("notdisplayed"));
    assert(elem_2.hasClass("notdisplayed"));
    assert(!elem_3.hasClass("notdisplayed"));

    subs.filter_table({input: "Mon", subscribed_only: false});
    assert(elem_1.hasClass("notdisplayed"));
    assert(!elem_2.hasClass("notdisplayed"));
    assert(!elem_3.hasClass("notdisplayed"));

    subs.filter_table({input: "p", subscribed_only: false});
    assert.equal(subs.stream_name_match_stream_ids.length, 2);
    assert.equal(subs.stream_description_match_stream_ids, 1);
    assert.equal(subs.stream_name_match_stream_ids[0], 2);
    assert.equal(subs.stream_name_match_stream_ids[1], 3);
    assert.equal(subs.stream_description_match_stream_ids[0], 1);

    subs.filter_table({input: "d", subscribed_only: false});
    assert.equal(subs.stream_name_match_stream_ids.length, 2);
    assert.equal(subs.stream_description_match_stream_ids, 0);
    assert.equal(subs.stream_name_match_stream_ids[0], 1);
    assert.equal(subs.stream_name_match_stream_ids[1], 2);
});

run_test('sub_or_unsub', () => {
    var denmark = {
        subscribed: false,
        name: 'Denmark',
        stream_id: 1,
        description: 'Copenhagen',
    };
    stream_data.clear_subscriptions();
    stream_data.add_sub("Denmark", denmark);

    var post_params;

    global.channel.post = function (params) {
        post_params = params;
    };

    subs.sub_or_unsub(denmark);
    assert.equal(post_params.url, '/json/users/me/subscriptions');
    assert.deepEqual(post_params.data,
                     {subscriptions: '[{"name":"Denmark"}]'});

    global.channel.post = undefined;

    global.channel.del = function (params) {
        post_params = params;
    };

    stream_data.get_sub_by_id(denmark.stream_id).subscribed = true;
    subs.sub_or_unsub(denmark);
    assert.equal(post_params.url, '/json/users/me/subscriptions');
    assert.deepEqual(post_params.data,
                     {subscriptions: '["Denmark"]'});

});

<commit_msg>tests: Rewrite tests for subs.js using zjquery.<commit_after>global.stub_out_jquery();

set_global('ui', {});
set_global('stream_data', {});
set_global('window', {
    location: {
        hash: "#streams/1/announce",
    },
});

zrequire('subs');

set_global('$', global.make_zjquery());

run_test('filter_table', () => {
    var stream_list = $(".streams-list");

    var scrolltop_called = false;
    stream_list.scrollTop = function (set) {
        scrolltop_called = true;
        if (!set) {
            return 10;
        }
        assert.equal(set, 10);
    };

    // set-up sub rows stubs
    var sub_row_data = {};
    sub_row_data[1] = {
        elem: 'denmark',
        subscribed: false,
        name: 'Denmark',
        stream_id: 1,
        description: 'Copenhagen',
    };
    sub_row_data[2] = {
        elem: 'poland',
        subscribed: true,
        name: 'Poland',
        stream_id: 2,
        description: 'monday',
    };
    sub_row_data[3] = {
        elem: 'pomona',
        subscribed: true,
        name: 'Pomona',
        stream_id: 3,
        description: 'college',
    };
    sub_row_data[4] = {
        elem: 'cpp',
        subscribed: true,
        name: 'C++',
        stream_id: 4,
        description: 'programming lang',
    };

    var sub_stubs = [];
    _.each(sub_row_data, function (data) {
        var sub_row = ".stream-row-" + data.elem;
        sub_stubs.push(sub_row);

        $(sub_row).attr("data-stream-id", data.stream_id);
        $(sub_row).set_find_results('.sub-info-box [class$="-bar"] [class$="-count"]', $(".tooltip"));
        $(sub_row).detach = function () {
            return sub_row;
        };
    });

    var tooltip_called = false;
    $(".tooltip").tooltip = function (obj) {
        tooltip_called = true;
        assert.deepEqual(obj, {
            placement: 'left',
            animation: false,
        });
    };

    stream_data.get_sub_by_id = function (id) {
        return sub_row_data[id];
    };

    $.stub_selector("#subscriptions_table .stream-row", sub_stubs);

    var sub_table = $('#subscriptions_table .streams-list');
    var sub_table_append = [];
    sub_table.append = function (rows) {
        if (typeof rows === "string") {
            sub_table_append.push(rows);
        } else {
            _.each(rows, function (row) {
                sub_table_append.push(row);
            });
        }
    };

    var ui_called = false;
    ui.update_scrollbar = function (elem) {
        ui_called = true;
        assert.equal(elem, $("#subscription_overlay .streams-list"));
    };

    // Search with single keyword
    subs.filter_table({input: "Po", subscribed_only: false});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert(!$(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // assert these once and call it done
    assert(ui_called);
    assert(scrolltop_called);
    assert(tooltip_called);
    assert.deepEqual(sub_table_append, [
        '.stream-row-poland',
        '.stream-row-pomona',
        '.stream-row-denmark',
        '.stream-row-cpp',
    ]);

    // Search with multiple keywords
    subs.filter_table({input: "Denmark, Pol", subscribed_only: false});
    assert(!$(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    subs.filter_table({input: "Den, Pol", subscribed_only: false});
    assert(!$(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // Search is case-insensitive
    subs.filter_table({input: "po", subscribed_only: false});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert(!$(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // Search handles unusual characters like C++
    subs.filter_table({input: "c++", subscribed_only: false});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert($(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert(!$(".stream-row-cpp").hasClass("notdisplayed"));

    // Search subscribed streams only
    subs.filter_table({input: "d", subscribed_only: true});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // Search terms match stream description
    subs.filter_table({input: "Co", subscribed_only: false});
    assert(!$(".stream-row-denmark").hasClass("notdisplayed"));
    assert($(".stream-row-poland").hasClass("notdisplayed"));
    assert(!$(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // Search names AND descriptions
    sub_table_append = [];

    subs.filter_table({input: "Mon", subscribed_only: false});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert(!$(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));
    assert.deepEqual(sub_table_append, [
        '.stream-row-pomona',
        '.stream-row-poland',
        '.stream-row-denmark',
        '.stream-row-cpp',
    ]);

    // active stream-row is not included in results
    $(".stream-row-denmark").addClass("active");
    $(".stream-row.active").hasClass = function (cls) {
        assert.equal(cls, "notdisplayed");
        return $(".stream-row-denmark").hasClass("active");
    };
    $(".stream-row.active").removeClass = function (cls) {
        assert.equal(cls, "active");
        $(".stream-row-denmark").removeClass("active");
    };

    subs.filter_table({input: "d", subscribed_only: true});
    assert(!$(".stream-row-denmark").hasClass("active"));
    assert(!$(".right .settings").visible());
    assert($(".nothing-selected").visible());

    // data-temp-view condition
    $(".stream-row-denmark").attr("data-temp-view", "true");

    subs.filter_table({input: "d", subscribed_only: true});
    assert(!$(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    $(".stream-row-denmark").attr("data-temp-view", "false");

    subs.filter_table({input: "d", subscribed_only: true});
    assert($(".stream-row-denmark").hasClass("notdisplayed"));
    assert(!$(".stream-row-poland").hasClass("notdisplayed"));
    assert($(".stream-row-pomona").hasClass("notdisplayed"));
    assert($(".stream-row-cpp").hasClass("notdisplayed"));

    // test selected row set to active
    $(".stream-row[data-stream-id='1']").removeClass("active");
    subs.filter_table({input: "", subscribed_only: false});
    assert($(".stream-row[data-stream-id='1']").hasClass("active"));
});

<|endoftext|>


<commit_before>/**
 * Story
 *
 * @module      ::  Model
 * @description ::  This model represent user story on taskboard. User stories are attached to project and
 *                  sprint. Sprint relation is not required, if sprintId is 0 or null current user story is
 *                  in project backlog.
 */
module.exports = {
    attributes: {
        projectId: {
            type:       'integer',
            required:   true
        },
        sprintId: {
            type:       'integer'
        },
        title: {
            type:       'string',
            required:   true
        },
        description: {
            type:       'text',
            required:   true
        },
        estimate: {
            type:       'integer',
            required:   true
        },
        priority: {
            type:       'integer'
        },
        vfCase: {
            type:       'integer'
        }
    }
};
<commit_msg>Fixed story priority problem when adding new story.<commit_after>/**
 * Story
 *
 * @module      ::  Model
 * @description ::  This model represent user story on taskboard. User stories are attached to project and
 *                  sprint. Sprint relation is not required, if sprintId is 0 or null current user story is
 *                  in project backlog.
 */
module.exports = {
    attributes: {
        projectId: {
            type:       'integer',
            required:   true
        },
        sprintId: {
            type:       'integer'
        },
        title: {
            type:       'string',
            required:   true
        },
        description: {
            type:       'text',
            required:   true
        },
        estimate: {
            type:       'integer',
            required:   true
        },
        priority: {
            type:       'integer'
        },
        vfCase: {
            type:       'integer'
        }
    },

    // Lifecycle Callbacks
    beforeCreate: function(values, cb) {
        Story
            .findOne({
                projectId: values.projectId,
                sprintId: values.sprintId
            })
            .sort('priority DESC')
            .done(function(error, story) {
                var priority;

                if (error) {
                    cb(error)
                } else if (!story) {
                    priority = 0;
                } else {
                    priority = story.priority + 1;
                }

                values.priority = priority;

                cb();
            });
    }
};
<|endoftext|>


<commit_before>"use strict";

var fs        = require("fs");
var path      = require("path");
var Sequelize = require("sequelize");
var env       = process.env.NODE_ENV || "development";
// var config    = require(__dirname + '/../config/config.json')[env];

if (process.env.DATABASE_URL) {
  var match = process.env.DATABASE_URL.match(/postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
  var sequelize = new Sequelize(process.env.DATABASE_URL, {
    dialect: 'postgres',
    protocol: 'postgres',
    host: match[3],
    port: match[4],
    logging: false,
    dialectOptions: {
      ssl: true
    }
  })
} else {
  var sequelize = new Sequelize('obsvtr', 'juicyigor', '', {
    host: 'localhost',
    dialect: 'postgres'
  });
}

var db = {};

fs
  .readdirSync(__dirname)
  .filter(function(file) {
    return (file.indexOf(".") !== 0) && (file !== "index.js");
  })
  .forEach(function(file) {
    var model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(function(modelName) {
  if ("associate" in db[modelName]) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
<commit_msg>Fixed server params for prod run.<commit_after>"use strict";

var fs        = require("fs");
var path      = require("path");
var Sequelize = require("sequelize");
var env       = process.env.NODE_ENV || "development";
// var config    = require(__dirname + '/../config/config.json')[env];

if (process.env.DATABASE_URL) {
  var match = process.env.DATABASE_URL.match(/postgres:\/\/([^:]+):([^@]+)@([^:]+):(\d+)\/(.+)/);
  var sequelize = new Sequelize(process.env.DATABASE_URL, {
    dialect: 'postgres',
    protocol: 'postgres',
    host: match[3],
    port: match[4],
    logging: false,
    dialectOptions: {
      ssl: true
    }
  })
} else {
  var sequelize = new Sequelize('obsvtr3000', 'd5fg6idjjok6ha', 'yLjMxb6fc1leBx5ElHV05GrKJH', {
    host: 'localhost',
    dialect: 'postgres'
  });
}

var db = {};

fs
  .readdirSync(__dirname)
  .filter(function(file) {
    return (file.indexOf(".") !== 0) && (file !== "index.js");
  })
  .forEach(function(file) {
    var model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach(function(modelName) {
  if ("associate" in db[modelName]) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;
<|endoftext|>


<commit_before>var express      = require('express'),
	bodyParser   = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize    = require('azure-mobile-apps/src/express/middleware/authorize');

var sql          = require('mssql'),
	util         = require('../util');

module.exports = function (configuration) {
    var router = express.Router();

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

    router.get('/:voucher', (req, res, next) => {
		console.log('in GET /private_invitation/:voucher');
		if (! req.azureMobile.user) {
			console.log('redirecting...');
			res.writeHead(302, {
				'Location': '/.auth/login/google',
				'redirect_uri': 'https://tertulias.azurewebsites.net/.auth/login/google/callback'
			});

			// res.redirect('tertulias.azurewebsites.net/.auth/login/google?redirect_uri=' +
			// 	encodeURI('https://tertulias.azurewebsites.net/.auth/login/google/callback'));
			res.end();
			return;
		}
		console.log('proceeding...');
		var voucher = req.params.voucher;
		var userSid = 'req.azureMobile.user.id';
		var body = '' +
			'' +
			'
Tertulias
\n' +
			'
Welcome to Tertulias platform site.
' +
			'
You arrived at this page because you followed a link with a private invitation from a friend of yours to join a Tertulia.
' +
			'
Your voucher number is ' + voucher + '.
' +
			'
Your user id is ' + '.
' +
			'' +
			'';
		res.send(body);
    	next();
    });

    router.access = 'authenticated';
    express.access = 'authenticated';

    return router;
};
<commit_msg>wip save<commit_after>var express      = require('express'),
	bodyParser   = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize    = require('azure-mobile-apps/src/express/middleware/authorize');

var sql          = require('mssql'),
	util         = require('../util');

module.exports = function (configuration) {
    var router = express.Router();

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

    router.get('/:voucher', (req, res, next) => {
		console.log('in GET /private_invitation/:voucher');
		// if (! req.azureMobile.user) {
		// 	console.log('redirecting...');
		// 	res.writeHead(302, {
		// 		'Location': '/.auth/login/google',
		// 		'redirect_uri': 'https://tertulias.azurewebsites.net/.auth/login/google/callback'
		// 	});

		// 	// res.redirect('tertulias.azurewebsites.net/.auth/login/google?redirect_uri=' +
		// 	// 	encodeURI('https://tertulias.azurewebsites.net/.auth/login/google/callback'));
		// 	res.end();
		// 	return;
		// }
		// console.log('proceeding...');
		var voucher = req.params.voucher;
		var userSid = 'req.azureMobile.user.id';
		var body = '' +
			'' +
			'
Tertulias
\n' +
			'
Welcome to Tertulias platform site.
' +
			'
You arrived at this page because you followed a link with a private invitation from a friend of yours to join a Tertulia.
' +
			'
Your voucher number is ' + voucher + '.
' +
			'
Your user id is ' + '.
' +
			'' +
			'';
		res.send(body);
    	next();
    });

    router.access = 'authenticated';
    express.access = 'authenticated';

    return router;
};
<|endoftext|>


<commit_before>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

const queryTertulias = 'SELECT' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' ev_targetdate AS nextEventDate,' +
' lo_name       AS nextEventLocation,' +
' no_count      AS messages,' +
' nv_name       AS role ' +
' FROM Tertulias' +
' INNER JOIN Members ON mb_tertulia = tr_id' +
' INNER JOIN Enumvalues ON mb_role = nv_id' +
' INNER JOIN Users ON mb_user = us_id' +
' LEFT JOIN' +
' (SELECT * FROM' +
' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
'  INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
' ON ev_tertulia = tr_id' +
' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid';

const queryTertuliasPaged = 'SELECT * FROM (' + queryTertulias + ') AS tbl' +
' ORDER BY nextEventDate DESC, name' +
' OFFSET ((@pageNr - 1) * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY';

const queryTertuliaX = 'SELECT DISTINCT' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description,' + // Schedule
	' tr_is_private, ' +
	' _Member.nv_name AS nv_name' + // Role
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN Members    ON mb_tertulia = tr_id' +
' INNER JOIN Users      ON mb_user = us_id' +
' INNER JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid' +
' AND tr_id = @tertulia';

const queryPublicTertulias = 'SELECT ' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description' + // Schedule
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND tr_is_private = 0';

const queryPublicTertuliaX = 'SELECT' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description' + // Schedule
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND tr_is_private = 0' +
' AND tr_id = @tertulia';

const queryScheduleMonthlyW = 'SELECT mw_id, mw_dow, mw_weeknr, mw_is_fromstart, mw_skip' +
' FROM MonthlyW' +
' INNER JOIN Schedules  ON mw_schedule = sc_id' +
' INNER JOIN Tertulias  ON sc_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid' +
' AND tr_id = @tertulia';

module.exports = function (configuration) {
    var router = express.Router();

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

    router.get('/', (req, res, next) => {
		req.selectedQuery = queryTertulias;
	    req.paramsT = { 'sid': sql.NVarChar, 'pageNr': sql.Int, 'pageSize': sql.Int }; // String -> sql.NVarChar; Number -> sql.Int; Boolean -> sql.Bit; Date -> sql.DateTime; Buffer -> sql.VarBinary; sql.Table -> sql.TVP
	    req.paramsV = { 'sid': req.azureMobile.user.id, 'pageNr': 1, 'pageSize': 3 };
	    req.t_links = '{ "details": { "href": "tertulias/:tertulia" } }';
	    queryTertuliasPaged(req, res, next);
	});

    router.get('/public', (req, res, next) => {
		req.selectedQuery = queryPublicTertulias;
	    req.t_links = '{ "details": { "href": "tertulias/public/:tertulia" } }';
	    goGet(req, res, next);
	});

    router.get('/:tertulia', (req, res, next) => {
		req.selectedQuery = queryTertuliaX;
	    req.paramsT = { sid: sql.NVarChar, tertulia: sql.NVarChar };
	    req.paramsV = { sid: req.azureMobile.user.id, tertulia: req.params.tertulia };
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/:tertulia" }, ' +
	    	'"edit":      { "href": "tertulias/:tertulia/edit" }, ' +
	    	'"location":  { "href": "tertulias/:tertulia/location" }, ' +
	    	'"schedule":  { "href": "tertulias/:tertulia/schedule" }, ' +
	    	'"members":   { "href": "tertulias/:tertulia/members" }, ' +
	    	'"messages":  { "href": "tertulias/:tertulia/messages" }, ' +
	    	'"events":    { "href": "tertulias/:tertulia/events" }, ' +
	    	'"nextevent": { "href": "tertulias/:tertulia/nextevent" } ' +
    	'}';
	    goGet(req, res, next);
	});

    router.get('/public/:tertulia', (req, res, next) => {
		req.selectedQuery = queryPublicTertuliaX;
	    req.paramsT = {};
	    req.paramsV = {};
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/public/:tertulia" }, ' +
	    	'"subscribe": { "href": "tertulias/public/:tertulia/subscribe" }, ' +
	    	'"location":  { "href": "tertulias/public/:tertulia/location" }, ' +
	    	'"schedule":  { "href": "tertulias/public/:tertulia/schedule" }, ' +
	    	'"events":    { "href": "tertulias/public/:tertulia/events" }, ' +
	    	'"nextevent": { "href": "tertulias/public/:tertulia/nextevent" } ' +
    	'}';
	    goGet(req, res, next);
	});

    router.get('/public/:tertulia/subscribe', (req, res, next) => {
		req.selectedQuery = queryTertuliaX;
	    req.paramsT = {};
	    req.paramsV = {};
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/public/:tertulia/subscribe" } ' +
    	'}';
	    goGet(req, res, next);
	});

	var goGetTertulias = function(req, res, next) {
		var selectedQuery = req.selectedQuery;
	    var paramsT = req.paramsT;
	    var paramsV = req.paramsV;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsT) preparedStatement.input(key, paramsT[key]);
	        preparedStatement.prepare(selectedQuery, function(err) {
	            if (err) { completeError(err, res); return; }
	            preparedStatement.execute(paramsV,
	                function(err, recordset, affected) {
	                    if (err) { completeError(err, res); return; }
	                    res.type('application/json');
	                    recordset.forEach(function(elem) {
	                    	if (typeof req.t_links !== typeof undefined)
		                    	elem['_links'] = JSON.parse(req.t_links.replace(/:tertulia/g, elem.tr_id));
	                    });
	                    preparedStatement.unprepare();
	                    console.log(recordset);
	                    res.json(recordset);
	                    next();
	                }
	            );
	        });
	    });
	}

	var goGet = function(req, res, next) {
		var selectedQuery = req.selectedQuery;
	    var paramsT = req.paramsT;
	    var paramsV = req.paramsV;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsT) preparedStatement.input(key, paramsT[key]);
	        preparedStatement.prepare(selectedQuery, function(err) {
	            if (err) { completeError(err, res); return; }
	            preparedStatement.execute(paramsV,
	                function(err, recordset, affected) {
	                    if (err) { completeError(err, res); return; }
	                    res.type('application/json');
	                    recordset.forEach(function(elem) {
	                    	if (typeof req.t_links !== typeof undefined)
		                    	elem['_links'] = JSON.parse(req.t_links.replace(/:tertulia/g, elem.tr_id));
	                    });
	                    preparedStatement.unprepare();
	                    res.json(recordset);
	                    next();
	                }
	            );
	        });
	    });
	}

	router.post('/', (req, res, next) => {
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.tr_name)
			.input('subject', sql.NVarChar(80), req.body.tr_subject)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('locationName', sql.NVarChar(40), req.body.lo_name)
			.input('locationAddress', sql.NVarChar(80), req.body.lo_address)
			.input('locationZip', sql.NVarChar(40), req.body.lo_zip)
			.input('locationCity', sql.NVarChar(40), req.body.lo_city)
			.input('locationCountry', sql.NVarChar(40), req.body.lo_country)
			.input('locationLatitude', sql.NVarChar(12), req.body.lo_latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.lo_longitude)
			.input('isPrivate', sql.Int, req.body.tr_is_private ? 1 : 0)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				console.log(recordsets);
				console.log('len: ' + recordsets.length);
				//console.log('r1: ' + recordsets[0][0][ErrorNumber]);
				//console.log('r2: ' + recordsets[0][ErrorNumber]);
				//console.log('r2: ' + recordsets[0]);
				if (recordsets.length == 0) {
					console.log('sending 201');
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return;
				} else {
					console.log('sending 409');
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return;
				}
				next();
			})
			.catch(function(err) {
				console.log('catch 1');
				console.log(err);
				next();
			});
		})
		.catch(function(err) {
			console.log('catch 2');
			console.log(err);
			next();
		});
	})

    return router;

}
<commit_msg>wip save<commit_after>var express = require('express'),
	bodyParser = require('body-parser'),
    authenticate = require('azure-mobile-apps/src/express/middleware/authenticate'),
    authorize = require('azure-mobile-apps/src/express/middleware/authorize');

var sql = require('mssql');
var util = require('../util');

const queryTertulias = 'SELECT' +
' tr_id         AS id,' +
' tr_name       AS name,' +
' tr_subject    AS subject,' +
' ev_targetdate AS nextEventDate,' +
' lo_name       AS nextEventLocation,' +
' no_count      AS messages,' +
' nv_name       AS role ' +
' FROM Tertulias' +
' INNER JOIN Members ON mb_tertulia = tr_id' +
' INNER JOIN Enumvalues ON mb_role = nv_id' +
' INNER JOIN Users ON mb_user = us_id' +
' LEFT JOIN' +
' (SELECT * FROM' +
' (SELECT RANK() OVER(PARTITION BY ev_tertulia ORDER BY ev_targetdate DESC) AS "rank", * FROM Events' +
'  INNER JOIN Locations on ev_location = lo_id WHERE ev_targetdate > GETDATE()) AS a WHERE a.rank = 1) AS b' +
' ON ev_tertulia = tr_id' +
' LEFT JOIN (SELECT no_tertulia, count(*) AS no_count FROM Notifications WHERE no_id NOT IN' +
' (SELECT no_id FROM Notifications INNER JOIN Readnotifications ON rn_notification = no_id' +
' INNER JOIN Users ON rn_user = us_id WHERE us_sid = @sid)' +
' GROUP BY no_tertulia) AS c ON no_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid';

const queryTertuliasPaged = 'SELECT * FROM (' + queryTertulias + ') AS tbl' +
' ORDER BY nextEventDate DESC, name' +
' OFFSET ((@pageNr - 1) * @pageSize) ROWS FETCH NEXT @pageSize ROWS ONLY';

const queryTertuliaX = 'SELECT DISTINCT' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description,' + // Schedule
	' tr_is_private, ' +
	' _Member.nv_name AS nv_name' + // Role
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN Members    ON mb_tertulia = tr_id' +
' INNER JOIN Users      ON mb_user = us_id' +
' INNER JOIN EnumValues AS _Member ON mb_role = _Member.nv_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid' +
' AND tr_id = @tertulia';

const queryPublicTertulias = 'SELECT ' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description' + // Schedule
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND tr_is_private = 0';

const queryPublicTertuliaX = 'SELECT' +
	' tr_id, tr_name, tr_subject, ' + // Tertulia
	' lo_name, lo_address, lo_zip, lo_city, lo_country, lo_latitude, lo_longitude, ' + // Location
	' sc_type, _Schedule.nv_name AS schedule, _Schedule.nv_description AS description' + // Schedule
' FROM Tertulias' +
' INNER JOIN Locations  ON tr_location = lo_id' +
' INNER JOIN Schedules  ON tr_schedule = sc_id' +
' INNER JOIN EnumValues AS _Schedule ON sc_type = _Schedule.nv_id' +
' WHERE tr_is_cancelled = 0 AND tr_is_private = 0' +
' AND tr_id = @tertulia';

const queryScheduleMonthlyW = 'SELECT mw_id, mw_dow, mw_weeknr, mw_is_fromstart, mw_skip' +
' FROM MonthlyW' +
' INNER JOIN Schedules  ON mw_schedule = sc_id' +
' INNER JOIN Tertulias  ON sc_tertulia = tr_id' +
' WHERE tr_is_cancelled = 0 AND us_sid = @sid' +
' AND tr_id = @tertulia';

module.exports = function (configuration) {
    var router = express.Router();

	var completeError = function(err, res) {
	    if (err) {
	        console.error(err);
	        if (res) res.sendStatus(500);
	    }
	};

    router.get('/', (req, res, next) => {
		req.queryTertuliasPaged = queryTertulias;
	    req.paramsT = { 'sid': sql.NVarChar, 'pageNr': sql.Int, 'pageSize': sql.Int }; // String -> sql.NVarChar; Number -> sql.Int; Boolean -> sql.Bit; Date -> sql.DateTime; Buffer -> sql.VarBinary; sql.Table -> sql.TVP
	    req.paramsV = { 'sid': req.azureMobile.user.id, 'pageNr': 1, 'pageSize': 3 };
	    req.t_links = '{ "details": { "href": "tertulias/:tertulia" } }';
	    goGetTertulias(req, res, next);
	});

    router.get('/public', (req, res, next) => {
		req.selectedQuery = queryPublicTertulias;
	    req.t_links = '{ "details": { "href": "tertulias/public/:tertulia" } }';
	    goGet(req, res, next);
	});

    router.get('/:tertulia', (req, res, next) => {
		req.selectedQuery = queryTertuliaX;
	    req.paramsT = { sid: sql.NVarChar, tertulia: sql.NVarChar };
	    req.paramsV = { sid: req.azureMobile.user.id, tertulia: req.params.tertulia };
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/:tertulia" }, ' +
	    	'"edit":      { "href": "tertulias/:tertulia/edit" }, ' +
	    	'"location":  { "href": "tertulias/:tertulia/location" }, ' +
	    	'"schedule":  { "href": "tertulias/:tertulia/schedule" }, ' +
	    	'"members":   { "href": "tertulias/:tertulia/members" }, ' +
	    	'"messages":  { "href": "tertulias/:tertulia/messages" }, ' +
	    	'"events":    { "href": "tertulias/:tertulia/events" }, ' +
	    	'"nextevent": { "href": "tertulias/:tertulia/nextevent" } ' +
    	'}';
	    goGet(req, res, next);
	});

    router.get('/public/:tertulia', (req, res, next) => {
		req.selectedQuery = queryPublicTertuliaX;
	    req.paramsT = {};
	    req.paramsV = {};
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/public/:tertulia" }, ' +
	    	'"subscribe": { "href": "tertulias/public/:tertulia/subscribe" }, ' +
	    	'"location":  { "href": "tertulias/public/:tertulia/location" }, ' +
	    	'"schedule":  { "href": "tertulias/public/:tertulia/schedule" }, ' +
	    	'"events":    { "href": "tertulias/public/:tertulia/events" }, ' +
	    	'"nextevent": { "href": "tertulias/public/:tertulia/nextevent" } ' +
    	'}';
	    goGet(req, res, next);
	});

    router.get('/public/:tertulia/subscribe', (req, res, next) => {
		req.selectedQuery = queryTertuliaX;
	    req.paramsT = {};
	    req.paramsV = {};
	    req.t_links = '{ ' +
	    	'"self":      { "href": "tertulias/public/:tertulia/subscribe" } ' +
    	'}';
	    goGet(req, res, next);
	});

	var goGetTertulias = function(req, res, next) {
		var selectedQuery = req.selectedQuery;
	    var paramsT = req.paramsT;
	    var paramsV = req.paramsV;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsT) preparedStatement.input(key, paramsT[key]);
	        preparedStatement.prepare(selectedQuery, function(err) {
	            if (err) { completeError(err, res); return; }
	            preparedStatement.execute(paramsV,
	                function(err, recordset, affected) {
	                    if (err) { completeError(err, res); return; }
	                    res.type('application/json');
	                    recordset.forEach(function(elem) {
	                    	if (typeof req.t_links !== typeof undefined)
		                    	elem['_links'] = JSON.parse(req.t_links.replace(/:tertulia/g, elem.tr_id));
	                    });
	                    preparedStatement.unprepare();
	                    console.log(recordset);
	                    res.json(recordset);
	                    next();
	                }
	            );
	        });
	    });
	}

	var goGet = function(req, res, next) {
		var selectedQuery = req.selectedQuery;
	    var paramsT = req.paramsT;
	    var paramsV = req.paramsV;

		var connection = new sql.Connection(util.sqlConfiguration);
	    connection.connect(function(err) {
	        var preparedStatement = new sql.PreparedStatement(connection);
	        for (var key in paramsT) preparedStatement.input(key, paramsT[key]);
	        preparedStatement.prepare(selectedQuery, function(err) {
	            if (err) { completeError(err, res); return; }
	            preparedStatement.execute(paramsV,
	                function(err, recordset, affected) {
	                    if (err) { completeError(err, res); return; }
	                    res.type('application/json');
	                    recordset.forEach(function(elem) {
	                    	if (typeof req.t_links !== typeof undefined)
		                    	elem['_links'] = JSON.parse(req.t_links.replace(/:tertulia/g, elem.tr_id));
	                    });
	                    preparedStatement.unprepare();
	                    res.json(recordset);
	                    next();
	                }
	            );
	        });
	    });
	}

	router.post('/', (req, res, next) => {
	    sql.connect(util.sqlConfiguration)
	    .then(function() {
			new sql.Request()
			.input('name', sql.NVarChar(40), req.body.tr_name)
			.input('subject', sql.NVarChar(80), req.body.tr_subject)
			.input('userSid', sql.NVarChar(40), req.azureMobile.user.id)
			.input('weekDay', sql.NVarChar(20), 'Tuesday')
			.input('weekNr', sql.Int, 1)
			.input('fromStart', sql.BIT, 1)
			.input('skip', sql.Int, 0)
			.input('locationName', sql.NVarChar(40), req.body.lo_name)
			.input('locationAddress', sql.NVarChar(80), req.body.lo_address)
			.input('locationZip', sql.NVarChar(40), req.body.lo_zip)
			.input('locationCity', sql.NVarChar(40), req.body.lo_city)
			.input('locationCountry', sql.NVarChar(40), req.body.lo_country)
			.input('locationLatitude', sql.NVarChar(12), req.body.lo_latitude)
			.input('locationLongitude', sql.NVarChar(12), req.body.lo_longitude)
			.input('isPrivate', sql.Int, req.body.tr_is_private ? 1 : 0)
			.execute('sp_insertTertulia_MonthlyW_sid')
			.then((recordsets) => {
				console.log(recordsets);
				console.log('len: ' + recordsets.length);
				//console.log('r1: ' + recordsets[0][0][ErrorNumber]);
				//console.log('r2: ' + recordsets[0][ErrorNumber]);
				//console.log('r2: ' + recordsets[0]);
				if (recordsets.length == 0) {
					console.log('sending 201');
					res.status(201)	// 201: Created
						.type('application/json')
						.json( { result: 'Ok' } );
					return;
				} else {
					console.log('sending 409');
					res.status(409)	// 409: Conflict, 422: Unprocessable Entity (WebDAV; RFC 4918)
						.type('application/json')
						.json( { result: 'Duplicate' } );
					return;
				}
				next();
			})
			.catch(function(err) {
				console.log('catch 1');
				console.log(err);
				next();
			});
		})
		.catch(function(err) {
			console.log('catch 2');
			console.log(err);
			next();
		});
	})

    return router;

}
<|endoftext|>


<commit_before>var enums = require('../constants/enums'),
    questionPath = require('../constants/question-path'),
    twilio = require('twilio'),
    responseStore = require('../db/response-store');

// Main question loop
exports.question = function(req, res) {
    var currentIndex = req.params.index;
    var input = req.body.RecordingUrl || req.body.Digits;
    var twiml = new twilio.TwimlResponse();
    var previousNode;
    if (currentIndex) {
      previousNode = questionPath[currentIndex];
    }

    // helper to append a new "Say" verb with alice voice
    function say(text) {
        twiml.say(text, {
            voice: 'alice'
        });
    }

    // respond with the current TwiML content
    function respond() {
        // TODO we need a fallback verb here in case nothing gets collected.
        res.type('text/xml');
        res.send(twiml.toString());
    }

    var args = {
        id: req.body.CallSid,
        phone: req.body.From,
        node: previousNode,
        input: input,
        index: currentIndex
    };
    responseStore.handleCall(args, function(err, index, responseId) {
        if (err) {
            say('Terribly sorry, but an error has occurred. Goodbye.');
            return respond();
        }

        // If question is null, we're done!
        if (index === null) {
            say('Thank you. Goodbye!');
            return respond();
        }

        var nextIndex;

        if (index === 0) { // first hit
            // Add a greeting if this is the first question
            say('Hello.');
        }

        var node = questionPath[index];
        var question = node.question;
        var action = '/api/questions/' + index;

        say(question.text);

        if (question.type === enums.questionTypes.INPUT) {
            say('Enter the number using the number keys on your telephone. Press pound to finish.');
            twiml.gather({
                action: action,
                timeout: question.timeout
            });
        } else if (question.type === enums.questionTypes.SWITCH) {
            for (var button in node.values) {
                say('Press ' + button + ' for ' + node.values[button].name + '.');
            }
            twiml.gather({
                action: action,
                timeout: question.timeout,
                numDigits: 1
            });
        } else { // TEXT
            say('Please record your response after the beep. Press any key to finish.');
            twiml.record({
                action: action,
                // TODO uncomment when ready for transcription
                //transcribe: true,
                //transcribeCallback: '/voice/' + surveyres._id +
                //    '/transcribe/' + responseId,
                maxLength: question.maxLength
            });
        }

        // render TwiML res
        respond();
    });
};

// TODO update
// Transcripton callback - called by Twilio with transcript of recording
// Will update survey res outside the interview call flow
exports.transcription = function(req, res) {
    var resId = req.params.resId;
    var questionIndex = req.params.questionIndex;
    var transcript = req.body.TranscriptionText;

    Surveyres.findById(resId, function(err, surveyres) {
        if (err || !surveyres ||
            !surveyres.ress[questionIndex])
            return res.status(500).end();

        // Update appropriate answer field
        surveyres.ress[questionIndex].answer = transcript;
        surveyres.markModified('ress');
        surveyres.save(function(err, doc) {
            return res.status(err ? 500 : 200).end();
        });
    });
};
<commit_msg>added license ref and pause between accepting input and talking<commit_after>/*
The MIT License (MIT)

Copyright (c) 2015 Twilio Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var enums = require('../constants/enums'),
    questionPath = require('../constants/question-path'),
    twilio = require('twilio'),
    responseStore = require('../db/response-store');

// Main question loop
exports.question = function(req, res) {
    var currentIndex = req.params.index;
    var input = req.body.RecordingUrl || req.body.Digits;
    var twiml = new twilio.TwimlResponse();
    var previousNode;
    if (currentIndex) {
        previousNode = questionPath[currentIndex];
    }

    // helper to append a new "Say" verb with alice voice
    function say(text) {
        twiml.say(text, {
            voice: 'alice'
        });
    }

    // respond with the current TwiML content
    function respond() {
        // TODO we need a fallback verb here in case nothing gets collected.
        res.type('text/xml');
        res.send(twiml.toString());
    }

    var args = {
        id: req.body.CallSid,
        phone: req.body.From,
        node: previousNode,
        input: input,
        index: currentIndex
    };
    responseStore.handleCall(args, function(err, index, responseId) {
        if (err) {
            say('Terribly sorry, but an error has occurred. Goodbye.');
            return respond();
        }

        // If question is null, we're done!
        if (index === null) {
            say('Thank you. Goodbye!');
            return respond();
        }

        twiml.pause({
          length: 3
        });

        var nextIndex;

        if (index === 0) { // first hit
            // Add a greeting if this is the first question
            say('Hello.');
        }

        var node = questionPath[index];
        var question = node.question;
        var action = '/api/questions/' + index;

        say(question.text);

        if (question.type === enums.questionTypes.INPUT) {
            say('Enter the number using the number keys on your telephone. Press pound to finish.');
            twiml.gather({
                action: action,
                timeout: question.timeout
            });
        } else if (question.type === enums.questionTypes.SWITCH) {
            for (var button in node.values) {
                say('Press ' + button + ' for ' + node.values[button].name + '.');
            }
            twiml.gather({
                action: action,
                timeout: question.timeout,
                numDigits: 1
            });
        } else { // TEXT
            say('Please record your response after the beep. Press any key to finish.');
            twiml.record({
                action: action,
                // TODO uncomment when ready for transcription
                //transcribe: true,
                //transcribeCallback: '/voice/' + surveyres._id +
                //    '/transcribe/' + responseId,
                maxLength: question.maxLength
            });
        }

        // render TwiML res
        respond();
    });
};

// TODO update
// Transcripton callback - called by Twilio with transcript of recording
// Will update survey res outside the interview call flow
exports.transcription = function(req, res) {
    var resId = req.params.resId;
    var questionIndex = req.params.questionIndex;
    var transcript = req.body.TranscriptionText;

    Surveyres.findById(resId, function(err, surveyres) {
        if (err || !surveyres ||
            !surveyres.ress[questionIndex])
            return res.status(500).end();

        // Update appropriate answer field
        surveyres.ress[questionIndex].answer = transcript;
        surveyres.markModified('ress');
        surveyres.save(function(err, doc) {
            return res.status(err ? 500 : 200).end();
        });
    });
};
<|endoftext|>


<commit_before>var assert = require('chai').assert;
var BaseHash = require('../lib/BaseHash');

describe('BaseHash', function () {
  it('Should properly export', function () {
    assert.isFunction(BaseHash);
    assert.isFunction(BaseHash.prototype.getConfig);
    assert.isFunction(BaseHash.prototype.setConfig);
    assert.isFunction(BaseHash.prototype.hash);
    assert.isFunction(BaseHash.prototype.hashSync);
    assert.isFunction(BaseHash.prototype.compare);
    assert.isFunction(BaseHash.prototype.compareSync);

    assert.throws(function () {
      BaseCipher.prototype.hash();
    }, Error);

    assert.throws(function () {
      BaseCipher.prototype.hashSync();
    }, Error);

    assert.throws(function () {
      BaseCipher.prototype.compare();
    }, Error);

    assert.throws(function () {
      BaseCipher.prototype.compareSync();
    }, Error);
  });

  it('Should properly make objects configurable', function () {
    var hash = new BaseHash();

    assert.notOk(hash.getConfig('foo'));
    assert.instanceOf(hash.setConfig('foo', 'bar'), BaseHash);
    assert.equal(hash.getConfig('foo'), 'bar');
  });

  it('Should properly create hash with pre-defined config', function () {
    var hash = new BaseHash({
      foo: 'bar'
    });

    assert.equal(hash.getConfig('foo'), 'bar');
    assert.notOk(hash.getConfig('NOT_EXISTS'));
  });
});
<commit_msg>Fix tests<commit_after>var assert = require('chai').assert;
var BaseHash = require('../lib/BaseHash');

describe('BaseHash', function () {
  it('Should properly export', function () {
    assert.isFunction(BaseHash);
    assert.isFunction(BaseHash.prototype.getConfig);
    assert.isFunction(BaseHash.prototype.setConfig);
    assert.isFunction(BaseHash.prototype.hash);
    assert.isFunction(BaseHash.prototype.hashSync);
    assert.isFunction(BaseHash.prototype.compare);
    assert.isFunction(BaseHash.prototype.compareSync);

    assert.throws(function () {
      BaseHash.prototype.hash();
    }, Error);

    assert.throws(function () {
      BaseHash.prototype.hashSync();
    }, Error);

    assert.throws(function () {
      BaseHash.prototype.compare();
    }, Error);

    assert.throws(function () {
      BaseHash.prototype.compareSync();
    }, Error);
  });

  it('Should properly make objects configurable', function () {
    var hash = new BaseHash();

    assert.notOk(hash.getConfig('foo'));
    assert.instanceOf(hash.setConfig('foo', 'bar'), BaseHash);
    assert.equal(hash.getConfig('foo'), 'bar');
  });

  it('Should properly create hash with pre-defined config', function () {
    var hash = new BaseHash({
      foo: 'bar'
    });

    assert.equal(hash.getConfig('foo'), 'bar');
    assert.notOk(hash.getConfig('NOT_EXISTS'));
  });
});
<|endoftext|>


<commit_before>const expect = require("chai").expect;
const nock = require("nock");
const Config = require("../lib/utils/Config");
const Campaign = require("../lib/Campaign");
const fixture = require("./fixtures/campaign");
const options = require("./fixtures/options");

describe("Campaign", function () {
    let campaign;
    let batchURL = "";
    let createdCampaignToken;

    before(function () {
        const cfg = new Config();
        cfg.setUserOptions(options);

        campaign = new Campaign(cfg);

        batchURL = `${cfg.get("api.baseURL")}/${cfg.get("api.version")}/${cfg.get("api.devKey")}`;
    });

    it("should create a campaign", function (done) {
        const payload = fixture.createMinimal.payload;
        const token = fixture.createMinimal.token;
        const status = fixture.createMinimal.statusCode;

        nock(batchURL)
        .post("/campaigns/create", payload)
        .reply(status, token);

        campaign.create(payload)
        .then(function (result) {
            expect(result).to.be.equal(token.campaign_token);
            createdCampaignToken = result;
            done();
        })
        .catch((err) => done(err));
    });

    it("should update an existing campaign", function (done) {
        const payload = fixture.createMinimal.payload;
        const status = fixture.createMinimal.statusCode;

        nock(batchURL)
        .post(`/campaigns/update/${createdCampaignToken}`, payload)
        .reply(status);

        campaign.update(createdCampaignToken, payload)
        .then(() => done())
        .catch((err) => done(err));
    });
});
<commit_msg>test(campaign): init simple test for "stats"<commit_after>const expect = require("chai").expect;
const nock = require("nock");
const Config = require("../lib/utils/Config");
const Campaign = require("../lib/Campaign");
const fixture = require("./fixtures/campaign");
const options = require("./fixtures/options");

describe("Campaign", function () {
    let campaign;
    let batchURL = "";
    let createdCampaignToken;

    before(function () {
        const cfg = new Config();
        cfg.setUserOptions(options);

        campaign = new Campaign(cfg);

        batchURL = `${cfg.get("api.baseURL")}/${cfg.get("api.version")}/${cfg.get("api.devKey")}`;
    });

    it("should create a campaign", function (done) {
        const payload = fixture.createMinimal.payload;
        const token = fixture.createMinimal.token;
        const status = fixture.createMinimal.statusCode;

        nock(batchURL)
        .post("/campaigns/create", payload)
        .reply(status, token);

        campaign.create(payload)
        .then(function (result) {
            expect(result).to.be.equal(token.campaign_token);
            createdCampaignToken = result;
            done();
        })
        .catch((err) => done(err));
    });

    it("should update an existing campaign", function (done) {
        const payload = fixture.createMinimal.payload;
        const status = fixture.createMinimal.statusCode;

        nock(batchURL)
        .post(`/campaigns/update/${createdCampaignToken}`, payload)
        .reply(status);

        campaign.update(createdCampaignToken, payload)
        .then(() => done())
        .catch((err) => done(err));
    });

    it("should fetch stats from an existing campaign", function (done) {
        const replyStats = fixture.createMinimal.replyStats;
        const expectedStats = fixture.createMinimal.stats;
        const status = fixture.createMinimal.statusCode;

        nock(batchURL)
        .get(`/campaigns/stats/${createdCampaignToken}`)
        .reply(status, replyStats);

        campaign.stats(createdCampaignToken)
        .then(function (detail) {
            expect(detail).to.be.deep.equal(expectedStats);
            done();
        })
        .catch((err) => done(err));
    });
});
<|endoftext|>


<commit_before>// Exports a class that can be used to test building with Mimosa. An
// instance of the class can setup a Mimosa project in a temporary
// directory and build it, capturing the output so a test can verify
// it.

var Promise = require('bluebird');
Promise.longStackTraces();

var path = require('path');
var fs = Promise.promisifyAll(require('fs'));
var child_process = Promise.promisifyAll(require('child_process'));
var temp = Promise.promisifyAll(require('temp')).track();
var stripAnsi = require('strip-ansi');

module.exports = MimosaProject;

/**
 * Represents a simple Mimosa project, with a configuration and a
 * directory structure. Can setup and build the project in a temporary
 * directory and capture the build output. Useful when testing build
 * components.
 *
 * To use, create a MimosaProject instance:
 *
 * var project = new MimosaProject()
 *
 * Add files and directories to the project and modify its
 * configuraton:
 *
 * project.files.assets.javascripts.main = '// file contents';
 * project.mimosaConfig.modules.push('jscs');
 *
 * Build the project in a temp directory:
 *
 * project.build().then(function(result) {
 *   // Perform tests on build results
 * });
 *
 * A MimosaProject instance has the following properties:
 * - files, which is an object representing the project's directory
 *   structure. Each property is either a string, which represents a
 *   file where the string is the file contents, or an object, which
 *   represents a directory.
 * - mimosaConfig, which is an object with the project's Mimosa
 *   configuration. If the files object does not contain a
 *   mimosa-config.js file, one will be created from this object.
 *
 * Default mimosaConfig lists one module, 'copy'. Default files object
 * has an assets directory with a javascripts subdirectory.
 */
function MimosaProject() {
  this.mimosaConfig = {
    modules: [ 'copy' ]
  };
  this.files = {
    assets: {
      javascripts: {
      }
    }
  };
  this.promises = {};
};

/**
 * Execute a command in the project's root dir. First arguments is the
 * command to run, the following arguments are program parameters,
 * i. e. exec('ls', '-l') will execute the command "ls -l".
 *
 * Will create the project's root dir (using createRootDir) if it has
 * not been created already. Will not create project files using
 * createProjectFiles.
 *
 * Returns a promise that is rejected with an Error if execution fails
 * and that is resolved with the output (stdout and stderr combined)
 * if successful.
 */
MimosaProject.prototype.exec = function(cmd) {
  var args = Array.prototype.slice.call(arguments, 1);

  return this
    .createRootDir()
    .then(function (rootDir) {
      return exec(cmd, args, rootDir);
    });
};

// Internal function for executing a command
function exec(cmd, args, workingDir) {
  return new Promise(function (resolve, reject) {
    var process = child_process.spawn(cmd, args, { cwd: workingDir });

    var output = '';
    ['stdout', 'stderr'].forEach(function (stream) {
      process[stream].on('data', function (data) {
        output += data;
      });
    });

    process.on('close', function (code) {
      if (code === 0) {
        resolve(output, code);
      } else {
        var msg = 'Command <' + cmd + ' ' + args.join(' ') + '> failed, '
              + 'error code: ' + code + ', output: ' + output
        reject(new Error(msg));
      }
    });
  });
}

/**
 * Creates a temporary directory for the project. The directory will
 * be removed at program exit. Note: the directory is empty, call
 * createProjectFiles to populate it with project files.
 *
 * Returns a promise that is resolved with the path as a string if
 * successful.
 *
 * If called multiple times it will return the same promise, so the
 * directory is only created once.
 */
MimosaProject.prototype.createRootDir = function() {
  if (!this.promises.createRootDir) {
    this.promises.createRootDir =
      temp.mkdirAsync({ prefix: 'mimosa-project-' });
  }

  return this.promises.createRootDir;
};

/**
 * Creates the project's directory structure based on the files and
 * mimosaConfig properties.
 *
 * Will create the project's root dir (using createRootDir) if it has
 * not been created already.
 *
 * Returns a promise that is resolved when files have been created.
 * If called multiple times it will return the same promise, so the
 * directory structure is only created once.
 */
MimosaProject.prototype.createProjectFiles = function() {
  if (!this.promises.createProjectFiles) {
    if (!this.files['mimosa-config.js']) {
      this.files['mimosa-config.js'] = 'exports.config = \n' +
        JSON.stringify(this.mimosaConfig, undefined, 2);
    }

    this.promises.createProjectFiles = this.createRootDir()
      .bind(this)
      .then(function(rootDir) {
        return populateDirectory(rootDir, this.files);
      });
  }

  return this.promises.createProjectFiles;
};

// Internal function that writes the contents of a directory
function populateDirectory(dir, contents) {
  var promise = Promise.resolve();

  for (var file in contents) {
    var filePath = path.join(dir, file);
    var createFunc = createFileOrDir.bind(undefined, filePath, contents[file]);
    promise = promise.then(createFunc);
  }

  return promise;
}

function createFileOrDir(path, contents) {
  if (typeof contents === 'string') {
    return fs.writeFileAsync(path, contents, { encoding: 'utf8' });
  } else {
    return fs.mkdirAsync(path)
      .then(populateDirectory.bind(undefined, path, contents));
  }
}

/**
 * Builds the project using 'mimosa build'.
 *
 * Will create the project's files (using createProjectFiles) if they
 * have not been created already.
 *
 * Returns a promise that is resolved when the build finishes with an
 * object with the following properties:
 * - output, the stdout and stderr in its entirety
 * - logLines, an array with an object for each line with properties:
 *   - raw, the line
 *   - text, the line with ANSI code stripped
 *   - timestamp, the log timestamp as a string
 *   - logLevel, the log level as it appears in the log (Info, Success,
 *     WARN, etc)
 *   - message, the log line message
 * - warnings, the log lines with log level WARN
 *
 * If the buid fails the promise is rejected with an Error.
 */
MimosaProject.prototype.build = function() {
  return this
    .createProjectFiles()
    .bind(this)
    .then(function() {
      return this.exec('mimosa', 'build');
    })
    .then(function (output) {
      return Promise.resolve(new BuildResult(output));
    });
};

// Internal class for representing a build result, with parsed output
function BuildResult(output) {
  this.output = output.toString();
  this.logLines = parseLines(output);
  this.warnings = this.logLines.filter(function (line) {
    return line.logLevel === 'WARN';
  });
}

// Internal function for parsing build output
function parseLines(output) {
  var result = [];
  output.split('\n').forEach(function (line) {
    result.push(parseLine(line));
  });
  return result;
}

// Internal function for parsing a line of build output
function parseLine(line) {
  var result = {
    raw: line,
    text: stripAnsi(line)
  };

  var match = /([0-9:]+) - (\w+) - (.*)/.exec(result.text);
  if (match) {
    result.timestamp = match[1];
    result.logLevel = match[2];
    result.message = match[3];
  }
  return result;
}<commit_msg>Add assets/javascript/vendor to default test project dir structure<commit_after>// Exports a class that can be used to test building with Mimosa. An
// instance of the class can setup a Mimosa project in a temporary
// directory and build it, capturing the output so a test can verify
// it.

var Promise = require('bluebird');
Promise.longStackTraces();

var path = require('path');
var fs = Promise.promisifyAll(require('fs'));
var child_process = Promise.promisifyAll(require('child_process'));
var temp = Promise.promisifyAll(require('temp')).track();
var stripAnsi = require('strip-ansi');

module.exports = MimosaProject;

/**
 * Represents a simple Mimosa project, with a configuration and a
 * directory structure. Can setup and build the project in a temporary
 * directory and capture the build output. Useful when testing build
 * components.
 *
 * To use, create a MimosaProject instance:
 *
 * var project = new MimosaProject()
 *
 * Add files and directories to the project and modify its
 * configuraton:
 *
 * project.files.assets.javascripts.main = '// file contents';
 * project.mimosaConfig.modules.push('jscs');
 *
 * Build the project in a temp directory:
 *
 * project.build().then(function(result) {
 *   // Perform tests on build results
 * });
 *
 * A MimosaProject instance has the following properties:
 * - files, which is an object representing the project's directory
 *   structure. Each property is either a string, which represents a
 *   file where the string is the file contents, or an object, which
 *   represents a directory.
 * - mimosaConfig, which is an object with the project's Mimosa
 *   configuration. If the files object does not contain a
 *   mimosa-config.js file, one will be created from this object.
 *
 * Default mimosaConfig lists one module, 'copy'. Default files object
 * has directories assets, assets/javascript, and
 * assets/javascript/vendor.
 */
function MimosaProject() {
  this.mimosaConfig = {
    modules: [ 'copy' ]
  };
  this.files = {
    assets: {
      javascripts: {
        vendor: { }
      }
    }
  };
  this.promises = {};
};

/**
 * Execute a command in the project's root dir. First arguments is the
 * command to run, the following arguments are program parameters,
 * i. e. exec('ls', '-l') will execute the command "ls -l".
 *
 * Will create the project's root dir (using createRootDir) if it has
 * not been created already. Will not create project files using
 * createProjectFiles.
 *
 * Returns a promise that is rejected with an Error if execution fails
 * and that is resolved with the output (stdout and stderr combined)
 * if successful.
 */
MimosaProject.prototype.exec = function(cmd) {
  var args = Array.prototype.slice.call(arguments, 1);

  return this
    .createRootDir()
    .then(function (rootDir) {
      return exec(cmd, args, rootDir);
    });
};

// Internal function for executing a command
function exec(cmd, args, workingDir) {
  return new Promise(function (resolve, reject) {
    var process = child_process.spawn(cmd, args, { cwd: workingDir });

    var output = '';
    ['stdout', 'stderr'].forEach(function (stream) {
      process[stream].on('data', function (data) {
        output += data;
      });
    });

    process.on('close', function (code) {
      if (code === 0) {
        resolve(output, code);
      } else {
        var msg = 'Command <' + cmd + ' ' + args.join(' ') + '> failed, '
              + 'error code: ' + code + ', output: ' + output
        reject(new Error(msg));
      }
    });
  });
}

/**
 * Creates a temporary directory for the project. The directory will
 * be removed at program exit. Note: the directory is empty, call
 * createProjectFiles to populate it with project files.
 *
 * Returns a promise that is resolved with the path as a string if
 * successful.
 *
 * If called multiple times it will return the same promise, so the
 * directory is only created once.
 */
MimosaProject.prototype.createRootDir = function() {
  if (!this.promises.createRootDir) {
    this.promises.createRootDir =
      temp.mkdirAsync({ prefix: 'mimosa-project-' });
  }

  return this.promises.createRootDir;
};

/**
 * Creates the project's directory structure based on the files and
 * mimosaConfig properties.
 *
 * Will create the project's root dir (using createRootDir) if it has
 * not been created already.
 *
 * Returns a promise that is resolved when files have been created.
 * If called multiple times it will return the same promise, so the
 * directory structure is only created once.
 */
MimosaProject.prototype.createProjectFiles = function() {
  if (!this.promises.createProjectFiles) {
    if (!this.files['mimosa-config.js']) {
      this.files['mimosa-config.js'] = 'exports.config = \n' +
        JSON.stringify(this.mimosaConfig, undefined, 2);
    }

    this.promises.createProjectFiles = this.createRootDir()
      .bind(this)
      .then(function(rootDir) {
        return populateDirectory(rootDir, this.files);
      });
  }

  return this.promises.createProjectFiles;
};

// Internal function that writes the contents of a directory
function populateDirectory(dir, contents) {
  var promise = Promise.resolve();

  for (var file in contents) {
    var filePath = path.join(dir, file);
    var createFunc = createFileOrDir.bind(undefined, filePath, contents[file]);
    promise = promise.then(createFunc);
  }

  return promise;
}

function createFileOrDir(path, contents) {
  if (typeof contents === 'string') {
    return fs.writeFileAsync(path, contents, { encoding: 'utf8' });
  } else {
    return fs.mkdirAsync(path)
      .then(populateDirectory.bind(undefined, path, contents));
  }
}

/**
 * Builds the project using 'mimosa build'.
 *
 * Will create the project's files (using createProjectFiles) if they
 * have not been created already.
 *
 * Returns a promise that is resolved when the build finishes with an
 * object with the following properties:
 * - output, the stdout and stderr in its entirety
 * - logLines, an array with an object for each line with properties:
 *   - raw, the line
 *   - text, the line with ANSI code stripped
 *   - timestamp, the log timestamp as a string
 *   - logLevel, the log level as it appears in the log (Info, Success,
 *     WARN, etc)
 *   - message, the log line message
 * - warnings, the log lines with log level WARN
 *
 * If the buid fails the promise is rejected with an Error.
 */
MimosaProject.prototype.build = function() {
  return this
    .createProjectFiles()
    .bind(this)
    .then(function() {
      return this.exec('mimosa', 'build');
    })
    .then(function (output) {
      return Promise.resolve(new BuildResult(output));
    });
};

// Internal class for representing a build result, with parsed output
function BuildResult(output) {
  this.output = output.toString();
  this.logLines = parseLines(output);
  this.warnings = this.logLines.filter(function (line) {
    return line.logLevel === 'WARN';
  });
}

// Internal function for parsing build output
function parseLines(output) {
  var result = [];
  output.split('\n').forEach(function (line) {
    result.push(parseLine(line));
  });
  return result;
}

// Internal function for parsing a line of build output
function parseLine(line) {
  var result = {
    raw: line,
    text: stripAnsi(line)
  };

  var match = /([0-9:]+) - (\w+) - (.*)/.exec(result.text);
  if (match) {
    result.timestamp = match[1];
    result.logLevel = match[2];
    result.message = match[3];
  }
  return result;
}<|endoftext|>