<commit_before>import { expect } from 'chai';
import app from '../server/index';
import request from 'supertest-as-promised';
import Promise from 'bluebird';
import sinon from 'sinon';
import models from '../server/models';
import emailLib from '../server/lib/email';
import webhookBodyPayload from './mocks/mailgun.webhook.payload';
import webhookBodyApprove from './mocks/mailgun.webhook.approve';
import * as utils from '../test/utils';
import crypto from 'crypto';
import config from 'config';
import nock from 'nock';
import initNock from './email.routes.test.nock.js';

const generateToken = (email, slug, template) => {
  const uid = `${email}.${slug}.${template}.${config.keys.opencollective.secret}`;
  return crypto.createHash('md5').update(uid).digest("hex");
}

const {
  Collective
} = models;

const usersData = [
  {
    firstName: 'Xavier',
    lastName: 'Damman',
    email: 'xdamman+test@gmail.com',
    role: 'ADMIN',
    image: 'https://pbs.twimg.com/profile_images/3075727251/5c825534ad62223ae6a539f6a5076d3c.jpeg'
  },
  {
    firstName: 'Aseem',
    lastName: 'Sood',
    email: 'asood123+test@gmail.com',
    role: 'ADMIN'
  },
  {
    firstName: 'Pia',
    lastName: 'Mancini',
    email: 'pia+test@opencollective.com',
    role: 'BACKER'
  },
  {
    firstName: 'github',
    lastName: '',
    email: 'github+test@opencollective.com',
    role: 'BACKER'
  }
];

const collectiveData = {
  slug: 'testcollective',
  name: 'Test Collective',
  settings: {}
};

let collective, users = [];

describe("email.routes.test", () => {

  let sandbox;

  before(() => utils.resetTestDB());

  before(initNock);

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });

  afterEach(() => {
    sandbox.restore();
  });

  after(() => {
    nock.cleanAll();
  });

  before('create collective and members', (done) => {

    Collective.create(collectiveData)
      .tap(g => collective = g )
      .then(() => Promise.map(usersData, models.User.createUserWithCollective))
      .tap(users => {
        return Promise.map(users, (user, index) => {
          return collective.addUserWithRole(user, usersData[index].role);
        });
      })
      .tap(usersRows => {
        users = usersRows;
        return Promise.map(usersRows, (user, index) => {
          const lists = usersData[index].lists || [];
          return Promise.map(lists, (list) => models.Notification.create({
              channel: 'email',
              UserId: user.id,
              CollectiveId: collective.id,
              type: list
            })
          );
        })
      })
      .then(() => done())
      .catch(console.error);
  });

  it("forwards emails sent to info@:slug.opencollective.com", () => {

    const spy = sandbox.spy(emailLib, 'sendMessage');

    return request(app)
      .post('/webhooks/mailgun')
      .send(Object.assign({}, webhookBodyPayload, { recipient: 'info@testcollective.opencollective.com' }))
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[4][0]).to.equal('info@testcollective.opencollective.com');
        expect(spy.args[4][1]).to.equal(webhookBodyPayload.subject);
        expect(usersData.map(u => u.email).indexOf(spy.args[4][3].bcc) !== -1).to.be.true;
      });
  });

  it("forwards the email for approval to the core members", () => {
    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .post('/webhooks/mailgun')
      .send(webhookBodyPayload)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[0][1]).to.equal('admins@testcollective.opencollective.com');
        const emailSentTo = [spy.args[0][3].bcc,spy.args[1][3].bcc];
        expect(emailSentTo.indexOf(usersData[0].email) !== -1).to.be.true;
        expect(emailSentTo.indexOf(usersData[1].email) !== -1).to.be.true;
      });
  });

  it("skip the email if already processed", () => {
    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .post('/webhooks/mailgun')
      .send(webhookBodyApprove)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.called).to.be.false;
      });
  });

  it("rejects emails sent to unknown mailing list", () => {

    const unknownMailingListWebhook = Object.assign({}, webhookBodyPayload, { recipient: 'unknown@testcollective.opencollective.com' });

    return request(app)
      .post('/webhooks/mailgun')
      .send(unknownMailingListWebhook)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(res.body.error.message).to.equal('There is no user subscribed to unknown@testcollective.opencollective.com');
      });
  });

  it("approves the email", () => {

    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .get(`/services/email/approve?messageId=eyJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
      .then(() => {
        expect(spy.callCount).to.equal(2);
        expect(spy.args[0][1]).to.equal('admins@testcollective.opencollective.com');
        expect(spy.args[0][2].subject).to.equal('test collective admins');
        expect([spy.args[0][3].bcc, spy.args[1][3].bcc]).to.contain(usersData[0].email);
        expect(spy.args[0][3].from).to.equal('testcollective collective <hello@testcollective.opencollective.com>');
      });
  });

  it("return 404 if message not found", () => {
    const messageId = 'eyJwIjpmYWxzZSwiayI6IjY5MTdlYTZlLWVhNzctNGQzOC04OGUxLWMzMTQwMzdmNGRhNiIsInMiOiIwMjNjMzgwYWFlIiwiYyI6InNhaWFkIn0=';
    return request(app)
      .get(`/services/email/approve?messageId=${messageId}&approver=xdamman%40gmail.com&mailserver=so`)
      .then((res) => {
        expect(res.statusCode).to.equal(404);
        expect(res.body.error.message).to.contain(messageId);
      });
  });

  describe("send email to event", () => {

    let spy;
    const slug = `event1-ev111`;
    const subject = "email reminder for event 1";

    before(async () => {

      const event = await models.Collective.create({
        type: "EVENT",
        name: "event 1",
        ParentCollectiveId: collective.id,
        slug
      });

      await models.Member.create({
        CreatedByUserId: users[0].id,
        MemberCollectiveId: users[0].CollectiveId,
        CollectiveId: event.id,
        role: 'FOLLOWER'
      });

      await models.Member.create({
        CreatedByUserId: users[1].id,
        MemberCollectiveId: users[1].CollectiveId,
        CollectiveId: event.id,
        role: 'ATTENDEE'
      });

    });

    it("send please approve email when sending email to eventSlug@parentCollectiveSlug.opencollective.com", async () => {
      spy = sandbox.spy(emailLib, 'sendMessage');
      return request(app)
      .post('/webhooks/mailgun')
      .send(Object.assign({}, webhookBodyPayload, { recipient: `${slug}@${collective.slug}.opencollective.com`, subject }))
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[0][0]).to.equal(`admins@${collective.slug}.opencollective.com`);
        expect(spy.args[0][1]).to.equal(`Please approve: ${subject}`);
        expect(usersData.map(u => u.email).indexOf(spy.args[0][3].bcc) !== -1).to.be.true;
      });
    });

    it("approves an email sent to eventSlug@parentCollectiveSlug.opencollective.com", (done) => {

      spy = sandbox.spy(emailLib, 'send');
      request(app)
        .get(`/services/email/approve?messageId=abJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
        .then(() => {
          expect([users[0].email, users[1].email].indexOf(spy.args[0][3].bcc) !== -1).to.be.true;
          expect([users[0].email, users[1].email].indexOf(spy.args[1][3].bcc) !== -1).to.be.true;
          done();
        });

    });
  });

  describe("unsubscribe", () => {

    const template = 'mailinglist.admins';

    const generateUnsubscribeUrl = (email) => {
      const token = generateToken(email, collectiveData.slug, template);
      return `/services/email/unsubscribe/${encodeURIComponent(email)}/${collectiveData.slug}/${template}/${token}`;
    }

    it("returns an error if invalid token", () => {
      return request(app)
        .get(`/services/email/unsubscribe/${encodeURIComponent(usersData[0].email)}/${collectiveData.slug}/${template}/xxxxxxxxxx`)
        .then((res) => {
          expect(res.statusCode).to.equal(400);
          expect(res.body.error.message).to.equal('Invalid token');
        });
    });

    it("sends the unsubscribe link in the footer of the email", () => {

    const spy = sandbox.stub(emailLib, 'sendMessage');

    return request(app)
      .get(`/services/email/approve?messageId=eyJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
      .then(() => {
        for (const i in spy.args) {
          const emailBody = spy.args[i][2];
          expect(emailBody).to.contain(generateUnsubscribeUrl(spy.args[i][3].bcc));
          expect(emailBody).to.contain("To unsubscribe from the admins mailing list");
        }
      });
    });

    it("unsubscribes", () => {
      const where = {
        UserId: users[0].id,
        CollectiveId: collective.id,
        type: 'mailinglist.admins',
        active: true
      };

      return request(app)
        .get(generateUnsubscribeUrl(users[0].email))
        .then(() => models.Notification.count({ where }))
        .then(count => expect(count).to.equal(0))
    });
  });
});
<commit_msg>fix email route tests<commit_after>import { expect } from 'chai';
import app from '../server/index';
import request from 'supertest-as-promised';
import Promise from 'bluebird';
import sinon from 'sinon';
import models from '../server/models';
import emailLib from '../server/lib/email';
import webhookBodyPayload from './mocks/mailgun.webhook.payload';
import webhookBodyApprove from './mocks/mailgun.webhook.approve';
import * as utils from '../test/utils';
import crypto from 'crypto';
import config from 'config';
import nock from 'nock';
import initNock from './email.routes.test.nock.js';

const generateToken = (email, slug, template) => {
  const uid = `${email}.${slug}.${template}.${config.keys.opencollective.secret}`;
  return crypto.createHash('md5').update(uid).digest("hex");
}

const {
  Collective
} = models;

const usersData = [
  {
    firstName: 'Xavier',
    lastName: 'Damman',
    email: 'xdamman+test@gmail.com',
    role: 'ADMIN',
    image: 'https://pbs.twimg.com/profile_images/3075727251/5c825534ad62223ae6a539f6a5076d3c.jpeg'
  },
  {
    firstName: 'Aseem',
    lastName: 'Sood',
    email: 'asood123+test@gmail.com',
    role: 'ADMIN'
  },
  {
    firstName: 'Pia',
    lastName: 'Mancini',
    email: 'pia+test@opencollective.com',
    role: 'BACKER'
  },
  {
    firstName: 'github',
    lastName: '',
    email: 'github+test@opencollective.com',
    role: 'BACKER'
  }
];

const collectiveData = {
  slug: 'testcollective',
  name: 'Test Collective',
  settings: {}
};

let collective, users = [];

describe("email.routes.test", () => {

  let sandbox;

  before(() => utils.resetTestDB());

  before(initNock);

  beforeEach(() => {
    sandbox = sinon.sandbox.create();
  });

  afterEach(() => {
    sandbox.restore();
  });

  after(() => {
    nock.cleanAll();
  });

  before('create collective and members', (done) => {

    Collective.create(collectiveData)
      .tap(g => collective = g )
      .then(() => Promise.map(usersData, models.User.createUserWithCollective))
      .tap(users => {
        return Promise.map(users, (user, index) => {
          return collective.addUserWithRole(user, usersData[index].role);
        });
      })
      .tap(usersRows => {
        users = usersRows;
        return Promise.map(usersRows, (user, index) => {
          const lists = usersData[index].lists || [];
          return Promise.map(lists, (list) => models.Notification.create({
              channel: 'email',
              UserId: user.id,
              CollectiveId: collective.id,
              type: list
            })
          );
        })
      })
      .then(() => done())
      .catch(console.error);
  });

  it("forwards emails sent to info@:slug.opencollective.com", () => {

    const spy = sandbox.spy(emailLib, 'sendMessage');

    return request(app)
      .post('/webhooks/mailgun')
      .send(Object.assign({}, webhookBodyPayload, { recipient: 'info@testcollective.opencollective.com' }))
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[3][0]).to.equal('info@testcollective.opencollective.com');
        expect(spy.args[3][1]).to.equal(webhookBodyPayload.subject);
        expect(usersData.map(u => u.email).indexOf(spy.args[3][3].bcc) !== -1).to.be.true;
      });
  });

  it("forwards the email for approval to the core members", () => {
    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .post('/webhooks/mailgun')
      .send(webhookBodyPayload)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[0][1]).to.equal('admins@testcollective.opencollective.com');
        const emailSentTo = [spy.args[0][3].bcc,spy.args[1][3].bcc];
        expect(emailSentTo.indexOf(usersData[0].email) !== -1).to.be.true;
        expect(emailSentTo.indexOf(usersData[1].email) !== -1).to.be.true;
      });
  });

  it("skip the email if already processed", () => {
    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .post('/webhooks/mailgun')
      .send(webhookBodyApprove)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.called).to.be.false;
      });
  });

  it("rejects emails sent to unknown mailing list", () => {

    const unknownMailingListWebhook = Object.assign({}, webhookBodyPayload, { recipient: 'unknown@testcollective.opencollective.com' });

    return request(app)
      .post('/webhooks/mailgun')
      .send(unknownMailingListWebhook)
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(res.body.error.message).to.equal('There is no user subscribed to unknown@testcollective.opencollective.com');
      });
  });

  it("approves the email", () => {

    const spy = sandbox.spy(emailLib, 'send');

    return request(app)
      .get(`/services/email/approve?messageId=eyJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
      .then(() => {
        expect(spy.callCount).to.equal(2);
        expect(spy.args[0][1]).to.equal('admins@testcollective.opencollective.com');
        expect(spy.args[0][2].subject).to.equal('test collective admins');
        expect([spy.args[0][3].bcc, spy.args[1][3].bcc]).to.contain(usersData[0].email);
        expect(spy.args[0][3].from).to.equal('testcollective collective <hello@testcollective.opencollective.com>');
      });
  });

  it("return 404 if message not found", () => {
    const messageId = 'eyJwIjpmYWxzZSwiayI6IjY5MTdlYTZlLWVhNzctNGQzOC04OGUxLWMzMTQwMzdmNGRhNiIsInMiOiIwMjNjMzgwYWFlIiwiYyI6InNhaWFkIn0=';
    return request(app)
      .get(`/services/email/approve?messageId=${messageId}&approver=xdamman%40gmail.com&mailserver=so`)
      .then((res) => {
        expect(res.statusCode).to.equal(404);
        expect(res.body.error.message).to.contain(messageId);
      });
  });

  describe("send email to event", () => {

    let spy;
    const slug = `event1-ev111`;
    const subject = "email reminder for event 1";

    before(async () => {

      const event = await models.Collective.create({
        type: "EVENT",
        name: "event 1",
        ParentCollectiveId: collective.id,
        slug
      });

      await models.Member.create({
        CreatedByUserId: users[0].id,
        MemberCollectiveId: users[0].CollectiveId,
        CollectiveId: event.id,
        role: 'FOLLOWER'
      });

      await models.Member.create({
        CreatedByUserId: users[1].id,
        MemberCollectiveId: users[1].CollectiveId,
        CollectiveId: event.id,
        role: 'ATTENDEE'
      });

    });

    it("send please approve email when sending email to eventSlug@parentCollectiveSlug.opencollective.com", async () => {
      spy = sandbox.spy(emailLib, 'sendMessage');
      return request(app)
      .post('/webhooks/mailgun')
      .send(Object.assign({}, webhookBodyPayload, { recipient: `${slug}@${collective.slug}.opencollective.com`, subject }))
      .then((res) => {
        expect(res.statusCode).to.equal(200);
        expect(spy.args[0][0]).to.equal(`admins@${collective.slug}.opencollective.com`);
        expect(spy.args[0][1]).to.equal(`Please approve: ${subject}`);
        expect(usersData.map(u => u.email).indexOf(spy.args[0][3].bcc) !== -1).to.be.true;
      });
    });

    it("approves an email sent to eventSlug@parentCollectiveSlug.opencollective.com", (done) => {

      spy = sandbox.spy(emailLib, 'send');
      request(app)
        .get(`/services/email/approve?messageId=abJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
        .then(() => {
          expect([users[0].email, users[1].email].indexOf(spy.args[0][3].bcc) !== -1).to.be.true;
          expect([users[0].email, users[1].email].indexOf(spy.args[1][3].bcc) !== -1).to.be.true;
          done();
        });

    });
  });

  describe("unsubscribe", () => {

    const template = 'mailinglist.admins';

    const generateUnsubscribeUrl = (email) => {
      const token = generateToken(email, collectiveData.slug, template);
      return `/services/email/unsubscribe/${encodeURIComponent(email)}/${collectiveData.slug}/${template}/${token}`;
    }

    it("returns an error if invalid token", () => {
      return request(app)
        .get(`/services/email/unsubscribe/${encodeURIComponent(usersData[0].email)}/${collectiveData.slug}/${template}/xxxxxxxxxx`)
        .then((res) => {
          expect(res.statusCode).to.equal(400);
          expect(res.body.error.message).to.equal('Invalid token');
        });
    });

    it("sends the unsubscribe link in the footer of the email", () => {

    const spy = sandbox.stub(emailLib, 'sendMessage');

    return request(app)
      .get(`/services/email/approve?messageId=eyJwIjpmYWxzZSwiayI6Ijc3NjFlZTBjLTc1NGQtNGIwZi05ZDlkLWU1NTgxODJkMTlkOSIsInMiOiI2NDhjZDg1ZTE1IiwiYyI6InNhb3JkIn0=&approver=${encodeURIComponent(usersData[1].email)}`)
      .then(() => {
        for (const i in spy.args) {
          const emailBody = spy.args[i][2];
          expect(emailBody).to.contain(generateUnsubscribeUrl(spy.args[i][3].bcc));
          expect(emailBody).to.contain("To unsubscribe from the admins mailing list");
        }
      });
    });

    it("unsubscribes", () => {
      const where = {
        UserId: users[0].id,
        CollectiveId: collective.id,
        type: 'mailinglist.admins',
        active: true
      };

      return request(app)
        .get(generateUnsubscribeUrl(users[0].email))
        .then(() => models.Notification.count({ where }))
        .then(count => expect(count).to.equal(0))
    });
  });
});
<|endoftext|>


<commit_before>routes.define(function($routeProvider){
    $routeProvider
        .when('/thread/:threadId', {
            action: 'viewThread'
        })
});

function ActualitesController($scope, template, route, model){

    this.initialize = function(){

        route({
            viewThread: function(param){
            }
        });

        // Model
        $scope.template = template;
        $scope.me = model.me;
        $scope.threads = model.threads;
        $scope.threadFilters = [
            {label: "public", value: ACTUALITES_CONFIGURATION.threadFilters.PUBLIC},
            {label: "all", value: ACTUALITES_CONFIGURATION.threadFilters.ALL}
        ];

        $scope.threadFilter = ACTUALITES_CONFIGURATION.threadFilters.PUBLIC;

        // Variables
        $scope.infos = model.infos;
        $scope.currentInfo = new Info();
        $scope.newComment = new Comment();
        $scope.display = {
			emptyThread: false,
			showCommentsPanel: false,
			showComments: false,
			show3: true
		};

        $scope.appPrefix = 'actualites';
        $scope.currentThread = {};

        // View initialization
		template.open('threadsView', 'threads-view');
        template.open('comments', 'info-comments');
        template.open('infoEdit', 'info-edit');
        template.open('infoView', 'info-view');
		template.open('filters', 'filters');
		template.open('main', 'infos-list');
    };

    $scope.hasCurrentThread = function(){
        return (($scope.currentThread instanceof Thread) && ($scope.currentThread.type !== ACTUALITES_CONFIGURATION.threadTypes.latest));
    };

    $scope.loadNextThreads = function(){
        _.first(model.threads.mixed.rest($scope.loadedThreadsNumber), $scope.loadThreadsIncrement).forEach(function(thread){
            thread.open();
            thread.on('change', function(){
                $scope.$apply('threads');
            })
        });

        $scope.loadedThreadsNumber += $scope.loadThreadsIncrement;
    };

    $scope.isInfoPublished = function(info) {
        return info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED;
    };

    $scope.isInfoVisible = function(info) {
        // Selected Filters
        if (! $scope.display['show' + info.status]) {
           return false;
        }
        // Selected Thread
        if ($scope.thread && $scope.thread !== info.thread) {
           return false;
        }

        // For Published Infos, enforce publication and expiration dates if the user has not 'contrib' permission
        if (info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED && (info.myRights.contrib === undefined)) {
            if (info.publicationDate !== undefined && info.publicationDate !== null) {
                if (moment().unix() < moment(info.publicationDate).unix()) {
                    return false;
                }
            }
            if (info.expirationDate !== undefined && info.expirationDate !== null) {
                if (moment().unix() > moment(info.expirationDate).unix()) {
                    return false;
                }
            }
        }
        return true;
    };

    /* Info Edition */
    $scope.infoExists = function(info) {
        return (info._id !== undefined);
    };

    $scope.isInfoEditable = function(info) {
        return info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    $scope.createInfo = function(info){
		$scope.currentInfo = new Info();
		template.open('main', 'info-create');
    };

	$scope.editInfo = function(info){
		$scope.currentInfo = info;
	};

    $scope.saveInfo = function(){
    	template.open('main', 'infos-list');
		$scope.currentInfo.save();
		$scope.currentInfo = undefined;
    };

    $scope.cancelEditInfo = function(){
		template.open('main', 'infos-list');
        $scope.currentInfo = undefined;
    };

    /* Info Publication */
    $scope.isInfoPublishable = function(info) {
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.PENDING;
    };

	$scope.openThread = function(thread){
		$scope.thread = thread;
	};

	$scope.closeThread = function(){
		$scope.thread = undefined;
	};

    $scope.isInfoUnpublishable = function(info) {
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED;
    };

    /* Info Submit */
    $scope.isInfoSubmitable = function(info){
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    $scope.isInfoSubmitted = function(info){
        return info && info.status === ACTUALITES_CONFIGURATION.infoStatus.PENDING;
    };

    $scope.getState = function(info){
    	if(info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED){
    		if(info.hasPublicationDate && (moment().unix() < moment(info.publicationDate).unix())){
    			// label (A venir)
    			return "actualites.edition.status.4" ;
    		}
    		if(info.expirationDate && (moment().unix() > moment(info.expirationDate).unix())){
    			// label (Expiree)
    			return "actualites.edition.status.5" ;
    		}
    		if(info.owner.userId !== model.me.userId){
    			return "actualites.edition.status.empty";
    		}
    	}
    	return "actualites.edition.status." + info.status;
    };

    /* Info Delete */
    $scope.isInfoDeletable = function(info) {
        return info && info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    /* Comments */
    $scope.hasInfoComments = function(info){
        return (info.comments !== undefined && info.comments.length > 0);
    };

    $scope.getInfoCommentsStatus = function(info, showComments){
        if (showComments) {
            return 'close';
        }
        if ($scope.hasInfoComments(info)) {
            return 'many';
        }
        return 'none';
    };

    $scope.postInfoComment = function(info){
        if ((! _.isString($scope.newComment.comment)) || ($scope.newComment.comment.trim() === "")) {
            return;
        }

        info.comment($scope.newComment.comment);
		$scope.newComment = new Comment();

    };

    // Threads
    $scope.threadsView = function(){
		template.open('main', 'threads-view');
	};

    $scope.hasCurrentThread = function(){
        return (($scope.currentThread instanceof Thread) && ($scope.currentThread.type !== ACTUALITES_CONFIGURATION.threadTypes.latest));
    };

	$scope.newThreadView = function(){
		$scope.currentThread = new Thread();
		template.open('main', 'thread-edit')
	};

	$scope.editSelectedThread = function(){
		$scope.currentThread = model.threads.selection()[0];
		model.threads.deselectAll();
		template.open('main', 'thread-edit');
	};

	$scope.switchAllThreads = function(){
		if($scope.display.selectAllThreads){
			model.threads.selectAll();
		}
		else{
			model.threads.deselectAll();
		}
	}

    $scope.saveThread = function(){
       	$scope.currentThread.save();
        template.open('main', 'threads-view');
		$scope.currentThread = undefined;
    };

    $scope.cancelEditThread = function(){
        $scope.currentThread = undefined;
		template.open('main', 'threads-view');
    };

    /* Util */
    $scope.formatDate = function(date){
    	var momentDate;
		if (date instanceof Object) {
			momentDate = moment(date.$date);
		} else {
			momentDate = moment(date);
		}
		return moment(momentDate, ACTUALITES_CONFIGURATION.momentFormat).lang('fr').format('dddd DD MMM YYYY');
    };

    $scope.checkThreadsRightsFilter = function(category){
    	return category.myRights.submit !== undefined;
	};

	$scope.checkThreadsContribRight = function(){
		var right = false;
		$scope.threads.forEach(function(item){
			if(item.myRights.submit){
				right = true;
			}
		});
		return right;
	};

	$scope.checkThreadsAdminRight = function(){
		return model.me.workflow.actualites.admin;
	}

    this.initialize();
}<commit_msg>#1465 all filters enabled by default<commit_after>routes.define(function($routeProvider){
    $routeProvider
        .when('/thread/:threadId', {
            action: 'viewThread'
        })
});

function ActualitesController($scope, template, route, model){

    this.initialize = function(){

        route({
            viewThread: function(param){
            }
        });

        // Model
        $scope.template = template;
        $scope.me = model.me;
        $scope.threads = model.threads;
        $scope.threadFilters = [
            {label: "public", value: ACTUALITES_CONFIGURATION.threadFilters.PUBLIC},
            {label: "all", value: ACTUALITES_CONFIGURATION.threadFilters.ALL}
        ];

        $scope.threadFilter = ACTUALITES_CONFIGURATION.threadFilters.PUBLIC;

        // Variables
        $scope.infos = model.infos;
        $scope.currentInfo = new Info();
        $scope.newComment = new Comment();
        $scope.display = {
			emptyThread: false,
			showCommentsPanel: false,
			showComments: false,
			show1: true,
			show2: true,
			show3: true
		};

        $scope.appPrefix = 'actualites';
        $scope.currentThread = {};

        // View initialization
		template.open('threadsView', 'threads-view');
        template.open('comments', 'info-comments');
        template.open('infoEdit', 'info-edit');
        template.open('infoView', 'info-view');
		template.open('filters', 'filters');
		template.open('main', 'infos-list');
    };

    $scope.hasCurrentThread = function(){
        return (($scope.currentThread instanceof Thread) && ($scope.currentThread.type !== ACTUALITES_CONFIGURATION.threadTypes.latest));
    };

    $scope.loadNextThreads = function(){
        _.first(model.threads.mixed.rest($scope.loadedThreadsNumber), $scope.loadThreadsIncrement).forEach(function(thread){
            thread.open();
            thread.on('change', function(){
                $scope.$apply('threads');
            })
        });

        $scope.loadedThreadsNumber += $scope.loadThreadsIncrement;
    };

    $scope.isInfoPublished = function(info) {
        return info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED;
    };

    $scope.isInfoVisible = function(info) {
        // Selected Filters
        if (! $scope.display['show' + info.status]) {
           return false;
        }
        // Selected Thread
        if ($scope.thread && $scope.thread !== info.thread) {
           return false;
        }

        // For Published Infos, enforce publication and expiration dates if the user has not 'contrib' permission
        if (info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED && (info.myRights.contrib === undefined)) {
            if (info.publicationDate !== undefined && info.publicationDate !== null) {
                if (moment().unix() < moment(info.publicationDate).unix()) {
                    return false;
                }
            }
            if (info.expirationDate !== undefined && info.expirationDate !== null) {
                if (moment().unix() > moment(info.expirationDate).unix()) {
                    return false;
                }
            }
        }
        return true;
    };

    /* Info Edition */
    $scope.infoExists = function(info) {
        return (info._id !== undefined);
    };

    $scope.isInfoEditable = function(info) {
        return info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    $scope.createInfo = function(info){
		$scope.currentInfo = new Info();
		template.open('main', 'info-create');
    };

	$scope.editInfo = function(info){
		$scope.currentInfo = info;
	};

    $scope.saveInfo = function(){
    	template.open('main', 'infos-list');
		$scope.currentInfo.save();
		$scope.currentInfo = undefined;
    };

    $scope.cancelEditInfo = function(){
		template.open('main', 'infos-list');
        $scope.currentInfo = undefined;
    };

    /* Info Publication */
    $scope.isInfoPublishable = function(info) {
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.PENDING;
    };

	$scope.openThread = function(thread){
		$scope.thread = thread;
	};

	$scope.closeThread = function(){
		$scope.thread = undefined;
	};

    $scope.isInfoUnpublishable = function(info) {
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED;
    };

    /* Info Submit */
    $scope.isInfoSubmitable = function(info){
        return info && info._id && info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    $scope.isInfoSubmitted = function(info){
        return info && info.status === ACTUALITES_CONFIGURATION.infoStatus.PENDING;
    };

    $scope.getState = function(info){
    	if(info.status === ACTUALITES_CONFIGURATION.infoStatus.PUBLISHED){
    		if(info.hasPublicationDate && (moment().unix() < moment(info.publicationDate).unix())){
    			// label (A venir)
    			return "actualites.edition.status.4" ;
    		}
    		if(info.expirationDate && (moment().unix() > moment(info.expirationDate).unix())){
    			// label (Expiree)
    			return "actualites.edition.status.5" ;
    		}
    		if(info.owner.userId !== model.me.userId){
    			return "actualites.edition.status.empty";
    		}
    	}
    	return "actualites.edition.status." + info.status;
    };

    /* Info Delete */
    $scope.isInfoDeletable = function(info) {
        return info && info.status === ACTUALITES_CONFIGURATION.infoStatus.DRAFT;
    };

    /* Comments */
    $scope.hasInfoComments = function(info){
        return (info.comments !== undefined && info.comments.length > 0);
    };

    $scope.getInfoCommentsStatus = function(info, showComments){
        if (showComments) {
            return 'close';
        }
        if ($scope.hasInfoComments(info)) {
            return 'many';
        }
        return 'none';
    };

    $scope.postInfoComment = function(info){
        if ((! _.isString($scope.newComment.comment)) || ($scope.newComment.comment.trim() === "")) {
            return;
        }

        info.comment($scope.newComment.comment);
		$scope.newComment = new Comment();

    };

    // Threads
    $scope.threadsView = function(){
		template.open('main', 'threads-view');
	};

    $scope.hasCurrentThread = function(){
        return (($scope.currentThread instanceof Thread) && ($scope.currentThread.type !== ACTUALITES_CONFIGURATION.threadTypes.latest));
    };

	$scope.newThreadView = function(){
		$scope.currentThread = new Thread();
		template.open('main', 'thread-edit')
	};

	$scope.editSelectedThread = function(){
		$scope.currentThread = model.threads.selection()[0];
		model.threads.deselectAll();
		template.open('main', 'thread-edit');
	};

	$scope.switchAllThreads = function(){
		if($scope.display.selectAllThreads){
			model.threads.selectAll();
		}
		else{
			model.threads.deselectAll();
		}
	}

    $scope.saveThread = function(){
       	$scope.currentThread.save();
        template.open('main', 'threads-view');
		$scope.currentThread = undefined;
    };

    $scope.cancelEditThread = function(){
        $scope.currentThread = undefined;
		template.open('main', 'threads-view');
    };

    /* Util */
    $scope.formatDate = function(date){
    	var momentDate;
		if (date instanceof Object) {
			momentDate = moment(date.$date);
		} else {
			momentDate = moment(date);
		}
		return moment(momentDate, ACTUALITES_CONFIGURATION.momentFormat).lang('fr').format('dddd DD MMM YYYY');
    };

    $scope.checkThreadsRightsFilter = function(category){
    	return category.myRights.submit !== undefined;
	};

	$scope.checkThreadsContribRight = function(){
		var right = false;
		$scope.threads.forEach(function(item){
			if(item.myRights.submit){
				right = true;
			}
		});
		return right;
	};

	$scope.checkThreadsAdminRight = function(){
		return model.me.workflow.actualites.admin;
	}

    this.initialize();
}<|endoftext|>


<commit_before>var assert = require("assert");
var fs = require("fs");

var testFile = fs.join(java.lang.System.getProperty("java.io.tmpdir"), "opentest.txt");

exports.setUp = function() {
    fs.touch(testFile);
};

exports.testOpen = function() {
    assert.isTrue(fs.exists(testFile));

    try {
        fs.open(testFile, "rw");
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }

    try {
        fs.open(testFile, {
            read: true,
            write: true
        });
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }
};

exports.testOpenRaw = function() {
    assert.isTrue(fs.exists(testFile));

    try {
        fs.openRaw(testFile, {
            read: true,
            write: true
        });
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }
};

exports.tearDown = function() {
    try {
        fs.remove(testFile);
    } catch (e) {
        // do nothing
    }
};

if (require.main == module.id) {
    require('system').exit(require("test").run(module.id));
}<commit_msg>Improves the fs.open() tests<commit_after>var assert = require("assert");
var fs = require("fs");
var {ByteArray} = require("binary");

var testFile = fs.join(java.lang.System.getProperty("java.io.tmpdir"), "opentest.txt");

exports.setUp = function() {
    fs.touch(testFile);
};

exports.testOpen = function() {
    assert.isTrue(fs.exists(testFile));

    try {
        fs.open(testFile, "rw");
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }

    try {
        fs.open(testFile, {
            read: true,
            write: true
        });
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }
};

exports.testOpenRaw = function() {
    assert.isTrue(fs.exists(testFile));

    try {
        fs.openRaw(testFile, {
            read: true,
            write: true
        });
    } catch (err) {
        assert.strictEqual(err.message, "Cannot open a file for reading and writing at the same time");
    }
};

exports.testOpenAndWrite = function() {
    assert.isTrue(fs.exists(testFile));
    var stream = fs.open(testFile, "wb");

    var ba = new ByteArray([0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);

    try {
        stream.write(ba);
        stream.flush();
    } finally {
        stream.close();
        assert.isTrue(stream.closed());
    }

    stream = fs.open(testFile, "rb");
    try {
        var buff = new ByteArray(10);
        assert.equal(stream.readInto(buff), 10);

        // Check the content
        for (var i = 0; i < buff.length; i++) {
            assert.equal(buff.charCodeAt(i), ba.charCodeAt(i));
        }
    } finally {
        stream.close();
        assert.isTrue(stream.closed());
    }
};

exports.testOpenAndAppend = function() {
    var ba = new ByteArray([0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);

    assert.isTrue(fs.exists(testFile));
    var stream = fs.open(testFile, "ab");

    try {
        stream.write(ba);
        stream.flush();
    } finally {
        stream.close();
        assert.isTrue(stream.closed());
    }

    stream = fs.open(testFile, "ab");

    try {
        stream.write(ba);
        stream.flush();
    } finally {
        stream.close();
        assert.isTrue(stream.closed());
    }

    stream = fs.open(testFile, "rb");
    try {
        var buff = new ByteArray(20);
        assert.equal(stream.readInto(buff), 20);

        // Check the content
        for (var i = 0; i < 10; i++) {
            assert.equal(buff.charCodeAt(i), ba.charCodeAt(i));
        }
        for (var u = 10; u < 20; u++) {
            assert.equal(buff.charCodeAt(i), ba.charCodeAt(i - 10));
        }
    } finally {
        stream.close();
        assert.isTrue(stream.closed());
    }
};

exports.tearDown = function() {
    try {
        fs.remove(testFile);
    } catch (e) {
        // do nothing
    }
};

if (require.main == module.id) {
    require('system').exit(require("test").run(module.id));
}<|endoftext|>


<commit_before>/*eslint-env node, mocha */
/*global $database */
import request from 'superagent'
import mkdirp from 'mkdirp'

import { getSingleton } from '../../app/app'
import { load as configLoader } from '../../config/config'
import * as funcTestHelper from './functional_test_helper'

const config = configLoader()

describe("GroupsController", function() {
  let app

  beforeEach(async () => {
    app = await getSingleton()
    await $database.flushdbAsync()
  })

  describe("#create()", function() {
    var context = {}

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    it('should reject unauthenticated users', function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(401)
            done()
          })
    })

    it('should create a group', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
              authToken: context.authToken })
          .end(function(err, res) {
            res.body.should.not.be.empty
            res.body.should.have.property('groups')
            res.body.groups.should.have.property('username')
            res.body.groups.should.have.property('screenName')
            res.body.groups.username.should.eql(userName)
            res.body.groups.screenName.should.eql(screenName)
            done()
          })
    })

    it('should create a private group', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName, isPrivate: '1'},
            authToken: context.authToken })
          .end(function(err, res) {
            res.body.should.not.be.empty
            res.body.should.have.property('groups')
            res.body.groups.isPrivate.should.eql('1')
            done()
          })
    })

    it('should not create a group if a user with that name already exists', function(done) {
      var userName = 'Luna';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            done()
          })
    })

    it('should not create a group with slash in its name', function(done) {
      var userName = 'Lu/na';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            err.response.error.should.have.property('text')
            JSON.parse(err.response.error.text).err.should.eql('Invalid username')
            done()
          })
    })

    it('should not create a group with an empty username', function(done) {
      var userName = '';
      var screenName = '';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            err.response.error.should.have.property('text')
            JSON.parse(err.response.error.text).err.should.eql('Invalid username')
            done()
          })
    })

    it('should add the creating user as the administrator', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            // TODO[yole] check that the user is an administrator
            done()
          })
    })

    it('should subscribe the creating user', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            var newGroupId = res.body.groups.id
            request
                .get(app.config.host + '/v1/users/Luna/subscriptions')
                .query({ authToken: context.authToken })
                .end(function(err, res) {
                  res.status.should.not.eql(404)
                  res.status.should.not.eql(422)
                  res.body.should.not.be.empty
                  res.body.should.have.property('subscribers')
                  res.body.should.have.property('subscriptions')
                  var subIds = res.body.subscriptions.map(function(sub) { return sub.user })
                  subIds.should.contain(newGroupId)
                  var users = res.body.subscribers
                  users.length.should.eql(1)
                  users[0].type.should.eql("group")
                  done()
                })
          })
    })
  })

  describe('#admin', function() {
    var adminContext = {}
      , nonAdminContext = {}

    beforeEach(funcTestHelper.createUserCtx(adminContext, 'Luna', 'password'))
    beforeEach(funcTestHelper.createUserCtx(nonAdminContext, 'yole', 'wordpass'))

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
            authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })

    })

    it('should reject unauthenticated users', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(403)
            done()
          })
    })

    it('should reject nonexisting group', function(done) {
      request
          .post(app.config.host + '/v1/groups/foobar/subscribers/yole/admin')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(404)
            done()
          })
    })
    it('should allow an administrator to add another administrator', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .send({authToken: adminContext.authToken })
          .end(function(err, res) {
            res.status.should.eql(200)
            done()
          })
    })
  })

  describe('#update', function() {
    var context = {}
      , group

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    beforeEach(function(done) {
      request
        .post(app.config.host + '/v1/groups')
        .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
                authToken: context.authToken
              })
        .end(function(err, res) {
          group = res.body.groups
          done()
        })
    })

    it('should update group settings', function(done) {
      var screenName = 'mokum-dev'
      var description = 'Mokum Developers'

      request
        .post(app.config.host + '/v1/users/' + group.id)
        .send({ authToken: context.authToken,
                user: { screenName: screenName, description: description },
                '_method': 'put' })
        .end(function(err, res) {
          res.should.not.be.empty
          res.body.should.not.be.empty
          res.body.should.have.property('groups')
          res.body.groups.should.have.property('id')
          res.body.groups.should.have.property('screenName')
          res.body.groups.screenName.should.eql(screenName)
          res.body.groups.should.have.property('description')
          res.body.groups.description.should.eql(description)
          done()
        })
    })
  })

  describe('#unadmin', function() {
    var adminContext = {}
      , nonAdminContext = {}

    beforeEach(funcTestHelper.createUserCtx(adminContext, 'Luna', 'password'))
    beforeEach(funcTestHelper.createUserCtx(nonAdminContext, 'yole', 'wordpass'))

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
            authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })

    })

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .send({ authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })
    })

    it('should allow an administrator to remove another administrator', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/unadmin')
          .send({ authToken: adminContext.authToken })
          .end(function(err, res) {
            res.status.should.eql(200)
            done()
          })
    })
  })

  describe('#updateProfilePicture', function() {
    var context = {}

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    beforeEach(function(done){
      mkdirp.sync(config.profilePictures.storage.rootDir + config.profilePictures.path)
      done()
    })

    beforeEach(function(done) {
      request
        .post(app.config.host + '/v1/groups')
        .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
          authToken: context.authToken })
        .end(function(err, res) {
          done()
        })
    })

    it('should update the profile picture', function(done) {
      request
        .post(app.config.host + '/v1/groups/pepyatka-dev/updateProfilePicture')
        .set('X-Authentication-Token', context.authToken)
        .attach('file', 'test/fixtures/default-userpic-75.gif')
        .end(function(err, res) {
          res.status.should.eql(200)
          res.body.should.not.be.empty
          request
            .get(app.config.host + '/v1/users/pepyatka-dev')
            .query({ authToken: context.authToken })
            .end(function(err, res) {
              res.should.not.be.empty
              res.body.users.profilePictureLargeUrl.should.not.be.empty
              done()
            })
        })
    })
  })
})
<commit_msg>[group-descriptions] Add functional test (2 of 2)<commit_after>/*eslint-env node, mocha */
/*global $database */
import request from 'superagent'
import mkdirp from 'mkdirp'

import { getSingleton } from '../../app/app'
import { load as configLoader } from '../../config/config'
import * as funcTestHelper from './functional_test_helper'

const config = configLoader()

describe("GroupsController", function() {
  let app

  beforeEach(async () => {
    app = await getSingleton()
    await $database.flushdbAsync()
  })

  describe("#create()", function() {
    var context = {}

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    it('should reject unauthenticated users', function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(401)
            done()
          })
    })

    it('should create a group', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
              authToken: context.authToken })
          .end(function(err, res) {
            res.body.should.not.be.empty
            res.body.should.have.property('groups')
            res.body.groups.should.have.property('username')
            res.body.groups.should.have.property('screenName')
            res.body.groups.username.should.eql(userName)
            res.body.groups.screenName.should.eql(screenName)
            done()
          })
    })

    it('should create a private group', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName, isPrivate: '1'},
            authToken: context.authToken })
          .end(function(err, res) {
            res.body.should.not.be.empty
            res.body.should.have.property('groups')
            res.body.groups.isPrivate.should.eql('1')
            done()
          })
    })

    it('should not create a group if a user with that name already exists', function(done) {
      var userName = 'Luna';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            done()
          })
    })

    it('should not create a group with slash in its name', function(done) {
      var userName = 'Lu/na';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            err.response.error.should.have.property('text')
            JSON.parse(err.response.error.text).err.should.eql('Invalid username')
            done()
          })
    })

    it('should not create a group with an empty username', function(done) {
      var userName = '';
      var screenName = '';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(422)
            err.response.error.should.have.property('text')
            JSON.parse(err.response.error.text).err.should.eql('Invalid username')
            done()
          })
    })

    it('should add the creating user as the administrator', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            // TODO[yole] check that the user is an administrator
            done()
          })
    })

    it('should subscribe the creating user', function(done) {
      var userName = 'pepyatka-dev';
      var screenName = 'Pepyatka Developers';
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: userName, screenName: screenName},
            authToken: context.authToken })
          .end(function(err, res) {
            var newGroupId = res.body.groups.id
            request
                .get(app.config.host + '/v1/users/Luna/subscriptions')
                .query({ authToken: context.authToken })
                .end(function(err, res) {
                  res.status.should.not.eql(404)
                  res.status.should.not.eql(422)
                  res.body.should.not.be.empty
                  res.body.should.have.property('subscribers')
                  res.body.should.have.property('subscriptions')
                  var subIds = res.body.subscriptions.map(function(sub) { return sub.user })
                  subIds.should.contain(newGroupId)
                  var users = res.body.subscribers
                  users.length.should.eql(1)
                  users[0].type.should.eql("group")
                  done()
                })
          })
    })
  })

  describe('#admin', function() {
    var adminContext = {}
      , nonAdminContext = {}

    beforeEach(funcTestHelper.createUserCtx(adminContext, 'Luna', 'password'))
    beforeEach(funcTestHelper.createUserCtx(nonAdminContext, 'yole', 'wordpass'))

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
            authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })

    })

    it('should reject unauthenticated users', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(403)
            done()
          })
    })

    it('should reject nonexisting group', function(done) {
      request
          .post(app.config.host + '/v1/groups/foobar/subscribers/yole/admin')
          .end(function(err, res) {
            err.should.not.be.empty
            err.status.should.eql(404)
            done()
          })
    })
    it('should allow an administrator to add another administrator', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .send({authToken: adminContext.authToken })
          .end(function(err, res) {
            res.status.should.eql(200)
            done()
          })
    })
  })

  describe('#update', function() {
    var context = {}
      , group

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    beforeEach(function(done) {
      request
        .post(app.config.host + '/v1/groups')
        .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
                authToken: context.authToken
              })
        .end(function(err, res) {
          group = res.body.groups
          done()
        })
    })

    it('should update group settings', function(done) {
      var screenName = 'mokum-dev'
      var description = 'Mokum Developers'

      request
        .post(app.config.host + '/v1/users/' + group.id)
        .send({ authToken: context.authToken,
                user: { screenName: screenName, description: description },
                '_method': 'put' })
        .end(function(err, res) {
          res.should.not.be.empty
          res.body.should.not.be.empty
          res.body.should.have.property('groups')
          res.body.groups.should.have.property('id')
          res.body.groups.should.have.property('screenName')
          res.body.groups.screenName.should.eql(screenName)
          res.body.groups.should.have.property('description')
          res.body.groups.description.should.eql(description)
          done()
        })
    })

    it("should not reset description if it's not provided", async () => {
      var oldScreenName = group.screenName
      var newScreenName = 'vanilla-dev'
      var newDescription = 'Vanilla Developer(s)'

      // First, check screenName and description (should be the old ones)
      {
        const response = await funcTestHelper.getUserAsync({}, group.username)
        response.status.should.equal(200)

        const data = await response.json()
        data.should.have.property('users')
        data.users.should.have.property('screenName')
        data.users.screenName.should.eql(oldScreenName) // old screenName
        data.users.should.not.have.property('description') // no description property (since it's empty)
      }

      // Second, only update description (screenName shouldn't change)
      {
        const userContext = {
          user: group,
          authToken: context.authToken
        }
        await funcTestHelper.updateUserAsync(userContext, {
          description: newDescription
        })

        const response = await funcTestHelper.getUserAsync({}, group.username)
        response.status.should.equal(200)

        const data = await response.json()
        data.should.have.property('users')
        data.users.should.have.property('screenName')
        data.users.screenName.should.eql(oldScreenName) // old screenName
        data.users.should.have.property('description')
        data.users.description.should.eql(newDescription) // new description
      }

      // Third, only update screenName (description shouldn't change)
      {
        const userContext = {
          user: group,
          authToken: context.authToken
        }
        await funcTestHelper.updateUserAsync(userContext, {
          screenName: newScreenName
        })

        const response = await funcTestHelper.getUserAsync({}, group.username)
        response.status.should.equal(200)

        const data = await response.json()
        data.should.have.property('users')
        data.users.should.have.property('screenName')
        data.users.screenName.should.eql(newScreenName) // new screenName
        data.users.should.have.property('description')
        data.users.description.should.eql(newDescription) // new description
      }
    })
  })

  describe('#unadmin', function() {
    var adminContext = {}
      , nonAdminContext = {}

    beforeEach(funcTestHelper.createUserCtx(adminContext, 'Luna', 'password'))
    beforeEach(funcTestHelper.createUserCtx(nonAdminContext, 'yole', 'wordpass'))

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups')
          .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
            authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })

    })

    beforeEach(function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/admin')
          .send({ authToken: adminContext.authToken })
          .end(function(err, res) {
            done()
          })
    })

    it('should allow an administrator to remove another administrator', function(done) {
      request
          .post(app.config.host + '/v1/groups/pepyatka-dev/subscribers/yole/unadmin')
          .send({ authToken: adminContext.authToken })
          .end(function(err, res) {
            res.status.should.eql(200)
            done()
          })
    })
  })

  describe('#updateProfilePicture', function() {
    var context = {}

    beforeEach(funcTestHelper.createUserCtx(context, 'Luna', 'password'))

    beforeEach(function(done){
      mkdirp.sync(config.profilePictures.storage.rootDir + config.profilePictures.path)
      done()
    })

    beforeEach(function(done) {
      request
        .post(app.config.host + '/v1/groups')
        .send({ group: {username: 'pepyatka-dev', screenName: 'Pepyatka Developers'},
          authToken: context.authToken })
        .end(function(err, res) {
          done()
        })
    })

    it('should update the profile picture', function(done) {
      request
        .post(app.config.host + '/v1/groups/pepyatka-dev/updateProfilePicture')
        .set('X-Authentication-Token', context.authToken)
        .attach('file', 'test/fixtures/default-userpic-75.gif')
        .end(function(err, res) {
          res.status.should.eql(200)
          res.body.should.not.be.empty
          request
            .get(app.config.host + '/v1/users/pepyatka-dev')
            .query({ authToken: context.authToken })
            .end(function(err, res) {
              res.should.not.be.empty
              res.body.users.profilePictureLargeUrl.should.not.be.empty
              done()
            })
        })
    })
  })
})
<|endoftext|>


<commit_before>// This is basically a port of webdriver-test.py
// https://github.com/hugs/appium/blob/master/sample-code/webdriver-test.py
/*global it:true */
"use strict";

var assert = require("assert")
  , describeWd = require("../helpers/driverblock.js").describe;

describeWd('load calc app', function(h) {
  var values = [];
  var populate = function(driver, cb) {
    driver.elementsByTagName('textField', function(err, elems) {
      var next = function(num) {
        if (num >= elems.length) {
          return cb(elems);
        }
        var val = Math.round(Math.random()*10);
        values.push(val);
        var elem = elems[num++];
        elem.sendKeys(val, function() {
          next(num);
        });
      };
      next(0);
    });
  };

  return it('should fill two fields with numbers', function(done) {
    var driver = h.driver;
    populate(driver, function(elems) {
      driver.elementsByTagName('button', function(err, buttons) {
        buttons[0].click(function() {
          driver.elementsByTagName('staticText', function(err, elems) {
            elems[0].text(function(err, text) {
              var sum = values[0] + values[1];
              assert.equal(parseInt(text, 10), sum);
              done();
            });
          });
        });
      });
    });
  });
});

<commit_msg>Minor improvement.<commit_after>// This is basically a port of webdriver-test.py
// https://github.com/hugs/appium/blob/master/sample-code/webdriver-test.py
/*global it:true */
"use strict";

var assert = require("assert")
  , describeWd = require("../helpers/driverblock.js").describe;

describeWd('calc app', function(h) {
  var values = [];
  var populate = function(driver, cb) {
    driver.elementsByTagName('textField', function(err, elems) {
      var next = function(num) {
        if (num >= elems.length) {
          return cb(elems);
        }
        var val = Math.round(Math.random()*10);
        values.push(val);
        var elem = elems[num++];
        elem.sendKeys(val, function() {
          next(num);
        });
      };
      next(0);
    });
  };

  return it('should fill two fields with numbers', function(done) {
    var driver = h.driver;
    populate(driver, function(elems) {
      driver.elementsByTagName('button', function(err, buttons) {
        buttons[0].click(function() {
          driver.elementsByTagName('staticText', function(err, elems) {
            elems[0].text(function(err, text) {
              var sum = values[0] + values[1];
              assert.equal(parseInt(text, 10), sum);
              done();
            });
          });
        });
      });
    });
  });
});

<|endoftext|>


<commit_before>let assert = require("chai").assert;
let util = require("../lib/utility.js");
let moment = require("moment");

describe("Slack ID置換のテスト", () => {
  beforeEach( () => {
    util.users['U0XXYYZZ0'] =  {name: "alice"};
    util.users['U0XXYYZZ1'] =  {name: "bob"};
    util.keywords = ["hideack", "nice"];
  });

  it("Slack message中の1ユーザに対するメンションのユーザIDを置換できること", () => {
    assert.equal(util.replaceSlackId("<@U0XXYYZZ0> Hello."), "@alice Hello.", "Slack IDに置換できている");
  });

  it("Slack message中の2ユーザに対するメンションのユーザIDを置換できること", () => {
    assert.equal(util.replaceSlackId("<@U0XXYYZZ0> <@U0XXYYZZ1> Hello."), "@alice @bob Hello.", "Slack IDに置換できている");
  });

  it("Slack message中にメンションが無い場合はそのまま出力されること", () => {
    assert.equal(util.replaceSlackId("Hello."), "Hello.", "渡した文字列がそのまま変える");
  });
});

describe("Slack messageのテキストパーサーのテスト", () => {
  let ghBotMessage = {"text":"","bot_id":"B5VPUMFGD","attachments":[{"fallback":"[foo/bar] Pull request submitted: .....","pretext":"[foo/bar] Pull request submitted by hideack","title":"#180 foobar","id":1,"title_link":"https://github.com/foo/bar/pull/180","color":"6CC644","mrkdwn_in":["text","pretext"]}],"type":"message","subtype":"bot_message","team":"T03GLB4LV","channel":"C03Q6TDDE","event_ts":"1498197665.915869","ts":"1498197665.915869","level":"error","message":"","timestamp":"2017-06-23T06:01:05.397Z"};

  let twitterBotMessage = {"bot_id":"XXE0S2FXX","attachments":[{"fallback":"<https://twitter.com/hideack@hideack>: Test message","ts":1495864801,"author_name":"hideack","author_link":"https://twitter.com/hideack/status/868346031640989696","author_icon":"https://pbs.twimg.com/profile_images/hideack/hideack_normal.png","author_subname":"@hideack","pretext":"<https://twitter.com/hideack/status/868346031640989696>","text":"Test message","service_name":"twitter","service_url":"https://twitter.com/","from_url":"https://twitter.com/hideack/status/868346031640989696","image_url":"https://pbs.twimg.com/media/DAv6DVjUIAAVlrY.jpg","image_width":1200,"image_height":800,"image_bytes":273940,"id":1,"footer":"Twitter","footer_icon":"https://a.slack-edge.com/6e067/img/services/twitter_pixel_snapped_32.png"}],"type":"message","subtype":"bot_message","team":"XX3GLB4XX","channel":"XXNQBEEXX","event_ts":"1495864803.324462","ts":"1495864803.324462","level":"error","message":"","timestamp":"2017-05-27T06:00:04.121Z"};

  let message = {type: 'message', channel: 'XXEGEXXS0', user: 'XX3NKUBXX', text: 'normal message', ts: '1495868089.515753',source_team: 'XXYGLB4ZZ', team: 'ZZ3GLB4XX' };

  let noTextMessage = {type: 'message', channel: 'XXEGEXXS0', user: 'XX3NKUBXX', ts: '1495868089.515753',source_team: 'XXYGLB4ZZ', team: 'ZZ3GLB4XX' };

  it("通常のメッセージの場合はtextプロパティ参照", () => {
    assert.equal(util.parseText(message), "normal message", "メッセージ中のtextが抽出できている");
  });

  it("bot_messageでtextが無く、attachmentsプロパティの中にtextがある場合、その部分をテキストとして抽出できること", () => {
    assert.equal(util.parseText(twitterBotMessage), "Test message", "botメッセージ中のattachmentsからtextが抽出できている");
  });

  it("bot_messageでtextが無く、attachmentsプロパティの中にtextも無いがpretextがある場合、その部分をテキストとして抽出できること", () => {
    assert.equal(util.parseText(ghBotMessage), "[foo/bar] Pull request submitted by hideack", "botメッセージ中のattachmentsからpretextが抽出できている");
  });

  it("messageでtextが無く、attachmentsプロパティの中にtext, pretextも無い場合、空文字列が返ること", () => {
    assert.equal(util.parseText(noTextMessage), "");
  });
});

describe("テキスト装飾のテスト", () => {
  it("装飾文字列が含まれない場合はそのままテキストが返ってくること", () => {
    assert.equal(util.decolateText("Hello slack"), "Hello slack", "そのまま文字列が返ってくる");
  });

  it("協調表記された文字が太文字になること", () => {
    let expectedText = "Hello \u001b[1mworld\u001b[22m";

    assert.equal(util.decolateText("Hello *world*"), expectedText, "worldという単語が太文字になっている");
  });

  it("引用表記された文字が > になり、以降イタリック表記されること", () => {
    let expectedText = ">\u001b[3m Hey\u001b[23m";

    assert.equal(util.decolateText("> Hey"), expectedText);
  });

  it("キーワード指定された文字が赤太文字となること", () => {
    let expectedText = "Hello \u001b[31m\u001b[1mhideack\u001b[22m\u001b[39m. Have a \u001b[31m\u001b[1mnice\u001b[22m\u001b[39m day";
    assert.equal(util.decolateText("Hello hideack. Have a nice day"), expectedText, "hideackとniceという単語が赤太文字になっている");
  });
});

describe("設定ファイル読み込みのテスト", () => {
  it("設定ファイルがない場合はキーワードは更新されない", () => {
    util.parseSettingFile('./test/null.yaml');
    assert.deepEqual(util.keywords, ["hideack", "nice"], "キーワードがなにも入っていない");
  });

  it("設定ファイルを読み込み強調キーワードが更新されていること", () => {
    util.parseSettingFile('./test/settings_sample.yaml');
    assert.deepEqual(util.keywords, ["abc", "xyz"], "キーワードが設定ファイルに基いて更新されている");
    assert.equal(util.token, "xoxp-xxxx-xxxx-xxxx-xxxx", "Slackのトークン文字列が更新されている");
  });
});

describe("メッセージバッファのテスト", () => {
  it("3件分のメッセージバッファを行うと3件分のバッファが作成されること", () => {
    for (let i=0; i<3; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];
    assert.equal(messageBuffer.length, 3, "3件分のバッファーが存在する");
  });

  it("21件分のメッセージバッファを行うと20件分のバッファが作成されること", () => {
    for (let i=0; i<21; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];
    assert.equal(messageBuffer.length, 20, "20件分のバッファーが存在する");
  });

  it("21件分のメッセージバッファを行うと先頭のメッセージが2件目から21件目ののメッセージでバッファされること", () => {
    for (let i=0; i<21; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];

    let count = 1;
    messageBuffer.forEach((data) => {
      assert.equal(data.lines[0], `${count}`, `${count + 1}件目のデータが一致すること`);
      count++;
    });
  });
});

<commit_msg>hook用の設定定義の読み取りテスト追加<commit_after>let assert = require("chai").assert;
let util = require("../lib/utility.js");
let moment = require("moment");

describe("Slack ID置換のテスト", () => {
  beforeEach( () => {
    util.users['U0XXYYZZ0'] =  {name: "alice"};
    util.users['U0XXYYZZ1'] =  {name: "bob"};
    util.keywords = ["hideack", "nice"];
  });

  it("Slack message中の1ユーザに対するメンションのユーザIDを置換できること", () => {
    assert.equal(util.replaceSlackId("<@U0XXYYZZ0> Hello."), "@alice Hello.", "Slack IDに置換できている");
  });

  it("Slack message中の2ユーザに対するメンションのユーザIDを置換できること", () => {
    assert.equal(util.replaceSlackId("<@U0XXYYZZ0> <@U0XXYYZZ1> Hello."), "@alice @bob Hello.", "Slack IDに置換できている");
  });

  it("Slack message中にメンションが無い場合はそのまま出力されること", () => {
    assert.equal(util.replaceSlackId("Hello."), "Hello.", "渡した文字列がそのまま変える");
  });
});

describe("Slack messageのテキストパーサーのテスト", () => {
  let ghBotMessage = {"text":"","bot_id":"B5VPUMFGD","attachments":[{"fallback":"[foo/bar] Pull request submitted: .....","pretext":"[foo/bar] Pull request submitted by hideack","title":"#180 foobar","id":1,"title_link":"https://github.com/foo/bar/pull/180","color":"6CC644","mrkdwn_in":["text","pretext"]}],"type":"message","subtype":"bot_message","team":"T03GLB4LV","channel":"C03Q6TDDE","event_ts":"1498197665.915869","ts":"1498197665.915869","level":"error","message":"","timestamp":"2017-06-23T06:01:05.397Z"};

  let twitterBotMessage = {"bot_id":"XXE0S2FXX","attachments":[{"fallback":"<https://twitter.com/hideack@hideack>: Test message","ts":1495864801,"author_name":"hideack","author_link":"https://twitter.com/hideack/status/868346031640989696","author_icon":"https://pbs.twimg.com/profile_images/hideack/hideack_normal.png","author_subname":"@hideack","pretext":"<https://twitter.com/hideack/status/868346031640989696>","text":"Test message","service_name":"twitter","service_url":"https://twitter.com/","from_url":"https://twitter.com/hideack/status/868346031640989696","image_url":"https://pbs.twimg.com/media/DAv6DVjUIAAVlrY.jpg","image_width":1200,"image_height":800,"image_bytes":273940,"id":1,"footer":"Twitter","footer_icon":"https://a.slack-edge.com/6e067/img/services/twitter_pixel_snapped_32.png"}],"type":"message","subtype":"bot_message","team":"XX3GLB4XX","channel":"XXNQBEEXX","event_ts":"1495864803.324462","ts":"1495864803.324462","level":"error","message":"","timestamp":"2017-05-27T06:00:04.121Z"};

  let message = {type: 'message', channel: 'XXEGEXXS0', user: 'XX3NKUBXX', text: 'normal message', ts: '1495868089.515753',source_team: 'XXYGLB4ZZ', team: 'ZZ3GLB4XX' };

  let noTextMessage = {type: 'message', channel: 'XXEGEXXS0', user: 'XX3NKUBXX', ts: '1495868089.515753',source_team: 'XXYGLB4ZZ', team: 'ZZ3GLB4XX' };

  it("通常のメッセージの場合はtextプロパティ参照", () => {
    assert.equal(util.parseText(message), "normal message", "メッセージ中のtextが抽出できている");
  });

  it("bot_messageでtextが無く、attachmentsプロパティの中にtextがある場合、その部分をテキストとして抽出できること", () => {
    assert.equal(util.parseText(twitterBotMessage), "Test message", "botメッセージ中のattachmentsからtextが抽出できている");
  });

  it("bot_messageでtextが無く、attachmentsプロパティの中にtextも無いがpretextがある場合、その部分をテキストとして抽出できること", () => {
    assert.equal(util.parseText(ghBotMessage), "[foo/bar] Pull request submitted by hideack", "botメッセージ中のattachmentsからpretextが抽出できている");
  });

  it("messageでtextが無く、attachmentsプロパティの中にtext, pretextも無い場合、空文字列が返ること", () => {
    assert.equal(util.parseText(noTextMessage), "");
  });
});

describe("テキスト装飾のテスト", () => {
  it("装飾文字列が含まれない場合はそのままテキストが返ってくること", () => {
    assert.equal(util.decolateText("Hello slack"), "Hello slack", "そのまま文字列が返ってくる");
  });

  it("協調表記された文字が太文字になること", () => {
    let expectedText = "Hello \u001b[1mworld\u001b[22m";

    assert.equal(util.decolateText("Hello *world*"), expectedText, "worldという単語が太文字になっている");
  });

  it("引用表記された文字が > になり、以降イタリック表記されること", () => {
    let expectedText = ">\u001b[3m Hey\u001b[23m";

    assert.equal(util.decolateText("> Hey"), expectedText);
  });

  it("キーワード指定された文字が赤太文字となること", () => {
    let expectedText = "Hello \u001b[31m\u001b[1mhideack\u001b[22m\u001b[39m. Have a \u001b[31m\u001b[1mnice\u001b[22m\u001b[39m day";
    assert.equal(util.decolateText("Hello hideack. Have a nice day"), expectedText, "hideackとniceという単語が赤太文字になっている");
  });
});

describe("設定ファイル読み込みのテスト", () => {
  it("設定ファイルがない場合はキーワードは更新されない", () => {
    util.parseSettingFile('./test/null.yaml');
    assert.deepEqual(util.keywords, ["hideack", "nice"], "キーワードがなにも入っていない");
  });

  it("設定ファイルを読み込み強調キーワードが更新されていること", () => {
    util.parseSettingFile('./test/settings_sample.yaml');
    assert.deepEqual(util.keywords, ["abc", "xyz"], "キーワードが設定ファイルに基いて更新されている");
    assert.equal(util.token, "xoxp-xxxx-xxxx-xxxx-xxxx", "Slackのトークン文字列が更新されている");
  });

  it("hook用コマンドが読み取れること", () => {
    util.parseSettingFile('./test/settings_sample.yaml');
    assert.equal(util.hook, "curl -X http://example.com/ -d {hoge:foo}", "hook用コマンドがYAMLから読み取れている");
  });

});

describe("メッセージバッファのテスト", () => {
  it("3件分のメッセージバッファを行うと3件分のバッファが作成されること", () => {
    for (let i=0; i<3; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];
    assert.equal(messageBuffer.length, 3, "3件分のバッファーが存在する");
  });

  it("21件分のメッセージバッファを行うと20件分のバッファが作成されること", () => {
    for (let i=0; i<21; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];
    assert.equal(messageBuffer.length, 20, "20件分のバッファーが存在する");
  });

  it("21件分のメッセージバッファを行うと先頭のメッセージが2件目から21件目ののメッセージでバッファされること", () => {
    for (let i=0; i<21; i++) {
      util.addMessageBuffer({bufferKey: "#test", lines: [`${i}`], times:moment()});
    }

    messageBuffer = util.buffer["#test"];

    let count = 1;
    messageBuffer.forEach((data) => {
      assert.equal(data.lines[0], `${count}`, `${count + 1}件目のデータが一致すること`);
      count++;
    });
  });
});

<|endoftext|>


<commit_before>/*global eyeballApp, $*/

eyeballApp.factory('popover',function(){
    return {
        show : function(e,obj) {
            var el = $(obj);
            el.popup('hide');
            el.popup({on : false});
            el.popup('setting','content',$('#popoverContent').html());
            el.popup('setting','variation','small');
            el.popup('setting','position','left center');
            el.popup('show');
        },
        hide : function(e,obj) {
            $(obj).popup('hide');
        }
    };
});<commit_msg>fix popover<commit_after>/*global eyeballApp, $*/

eyeballApp.factory('popover',function(){
    return {
        show : function(e,obj) {
            var el = $(obj);
            if(!el.popup('is visible')) {
                el.popup('hide');
            }
            el.popup({on : false});
            el.popup('setting','content',$('#popoverContent').html());
            el.popup('setting','variation','small');
            el.popup('setting','position','left center');
            el.popup('show');
        },
        hide : function(e,obj) {
            $(obj).popup('hide');
        }
    };
});<|endoftext|>


<commit_before>/*
 * Study calendar functions, etc., that require scriptaculous
 */

////// STUDY CALENDAR JS STYLES

SC.slideAndHide = function(element, options) {
    var e = $(element);
    if (e.style.display != "none")
    {
        new Effect.Parallel(
            [
                new Effect.BlindUp(e, {sync:true}),
                new Effect.Fade(e, {sync:true})
            ], $H(options).merge({
                duration: 1.0
            }).toObject()
        );
    }
}

SC.slideAndShow = function(element, options) {
    var e = $(element);
    if (e.style.display == "none")
    {
        new Effect.Parallel(
            [
                new Effect.BlindDown(e, {sync:true}),
                new Effect.Appear(e, {sync:true})
            ], $H(options).merge({
                duration: 1.0
            }).toObject()
        );
    }
}

SC.highlight = function(element, options) {
    var e = $(element)
    new Effect.Highlight(element, Object.extend({
        restorecolor: "#ffffff"
    }, options));
}

// creates an indicator image tag just like the one created by tags:activityIndicator
SC.activityIndicator = function(baseHref, id) {
    var attrs = {
        className: 'indicator',
        src: baseHref + 'images/indicator.white.gif',
        alt: 'activity indicator'
    }
    if (id) attrs.id = id
    return Builder.node('img', attrs)
}

////// CONTROLS

SC.InPlaceEditor = Class.create()

Object.extend(Object.extend(SC.InPlaceEditor.prototype, Ajax.InPlaceEditor.prototype), {
    initialize: function(element, url, options) {
        Ajax.InPlaceEditor.prototype.initialize.call(this,
            element, url,
            Object.extend({
                highlight: true, okText: 'OK', cancelText: 'Cancel',
                // replace standard callbacks to implement highlight option
                onEnterHover: function(ipe) {
                    if (ipe.options.highlight) {
                        ipe.element.style.backgroundColor = ipe.options.highlightColor;
                    }
                    if (ipe._effect)
                        ipe._effect.cancel();
                },
                onLeaveHover: function(ipe) {
                    if (ipe.options.highlight) {
                        ipe._effect = new Effect.Highlight(ipe.element, {
                            startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
                            restorecolor: ipe._originalBackground, keepBackgroundImage: true
                        });
                    }
                }
            }, options));
        // remove handlers from element, if requested
        if (options.externalControlOnly) {
            Event.stopObserving(this.element, 'click', this.onclickListener);
            Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
            Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
        }
    }
})

SC.inPlaceEditors = { }
SC.inPlaceEdit = function(element, url, options) {
    var e = $(element)
    SC.inPlaceEditors[e.id] = new SC.InPlaceEditor(element, url,
        Object.extend({
            evalScripts: true
        }, options)
    );
}

<commit_msg>adding right parameters to fix naming issue<commit_after>/*
 * Study calendar functions, etc., that require scriptaculous
 */

////// STUDY CALENDAR JS STYLES

SC.slideAndHide = function(element, options) {
    var e = $(element);
    if (e.style.display != "none")
    {
        new Effect.Parallel(
            [
                new Effect.BlindUp(e, {sync:true}),
                new Effect.Fade(e, {sync:true})
            ], $H(options).merge({
                duration: 1.0
            }).toObject()
        );
    }
}

SC.slideAndShow = function(element, options) {
    var e = $(element);
    if (e.style.display == "none")
    {
        new Effect.Parallel(
            [
                new Effect.BlindDown(e, {sync:true}),
                new Effect.Appear(e, {sync:true})
            ], $H(options).merge({
                duration: 1.0
            }).toObject()
        );
    }
}

SC.highlight = function(element, options) {
    var e = $(element)
    new Effect.Highlight(element, Object.extend({
        restorecolor: "#ffffff"
    }, options));
}

// creates an indicator image tag just like the one created by tags:activityIndicator
SC.activityIndicator = function(baseHref, id) {
    var attrs = {
        className: 'indicator',
        src: baseHref + 'images/indicator.white.gif',
        alt: 'activity indicator'
    }
    if (id) attrs.id = id
    return Builder.node('img', attrs)
}

////// CONTROLS

SC.InPlaceEditor = Class.create()

Object.extend(Object.extend(SC.InPlaceEditor.prototype, Ajax.InPlaceEditor.prototype), {
    initialize: function(element, url, options) {
        Ajax.InPlaceEditor.prototype.initialize.call(this,
            element, url,
            Object.extend({
                highlight: true, okText: 'OK', cancelText: 'Cancel', htmlResponse: false, ajaxOptions: {method: 'post'},
                // replace standard callbacks to implement highlight option
                onEnterHover: function(ipe) {
                    if (ipe.options.highlight) {
                        ipe.element.style.backgroundColor = ipe.options.highlightColor;
                    }
                    if (ipe._effect)
                        ipe._effect.cancel();
                },
                onLeaveHover: function(ipe) {
                    if (ipe.options.highlight) {
                        ipe._effect = new Effect.Highlight(ipe.element, {
                            startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
                            restorecolor: ipe._originalBackground, keepBackgroundImage: true
                        });
                    }
                }
            }, options));
        // remove handlers from element, if requested
        if (options.externalControlOnly) {
            Event.stopObserving(this.element, 'click', this.onclickListener);
            Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
            Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
        }
    }
})

SC.inPlaceEditors = { }
SC.inPlaceEdit = function(element, url, options) {
    var e = $(element)
    SC.inPlaceEditors[e.id] = new SC.InPlaceEditor(element, url,
        Object.extend({
            evalScripts: true
        }, options)
    );
}

<|endoftext|>


<commit_before>/**
 * Created with JetBrains WebStorm.
 * User: ty
 * Date: 14-10-5
 * Time: 下午5:34
 * To change this template use File | Settings | File Templates.
 */
var config={
    baseUrl:"",
    qiNiu:{
    	upTokenUrl:"qi-niu/up-token",
        uploadDomain:"http://qiniu-plupload.qiniudn.com/",
        bucketDomain:"http://7xqx6h.com1.z0.glb.clouddn.com/",
        swfUrl:"resources/js/lib/Moxie.swf"
    },
    ajaxUrls:{
        courseDesignGetAll:"list",
        courseDesignDelete:"/:id",
        courseWareGetAll:"data/json/courseWare.json",
        courseWareDelete:"/:id",
        courseVideoGetAll:"data/json/courseVideo.json",
        courseVideoDelete:"/:id"
    },
    dataTable:{
        langUrl:"resources/lang/de_DE.txt"
    },
    perLoadCounts:{
        table:10
    },
    uploader:{
        url:"#",
        swfUrl:"resources/js/plupload/plupload.flash.swf",
        sizes:{
            all:"5120m",
            img:"2m"
        },
        filters:{
            all:"*",
            zip:"zip,rar",
            img:"jpg,JPG,jpeg,JPEG,png,PNG"
        }
    },
    validErrors:{
        required:"请输入此字段！",
        email:"请输入正确的邮箱格式！",
        emailExist:"邮箱已经存在！",
        uploadImg:"请上传图片！",
        number:"请输入数字",
        maxLength:"此字段最多输入${max}个字！",
        minLength:"此字段最少输入${min}个字！",
        rangLength:"此字段只能输入${min}-${max}个字！",
        rang:"此字段只能输入${min}-${max}之间的整数！",
        pwdNotEqual:"两次输入的密码不一样！"
    },
    messages:{
        successTitle:"成功提示",
        errorTitle:"错误提示",
        optSuccess:"操作成功！",
        noData:"没有数据",
        progress:"处理中...",
        uploaded:"上传完成！",
        confirmDelete:"确定删除吗？",
        confirm:"确定执行此操作吗？",
        noSelected:"没有选中任何记录！",
        notFound:"资源丢失！",
        loadDataError:"请求数据失败！",
        networkError:"网络异常，请稍后重试！",
        systemError:"系统错误，请稍后重试或者联系mail@lotusprize.com！",
        optSuccRedirect:"操作成功,3秒后跳转到管理页！",
        timeout:"登录超时，3秒后自动跳到登陆页！",
        optError:"服务器端异常，请稍后重试！",
        uploadSizeError:"最大文件大小${value}！",
        uploadExtensionError:"只允许上传${value}！",
        uploadIOError:"上传出错，请稍后重试！",
        imageSizeError:"图片大小不符合！"
    }
};
$(document).ready(function(){

    $("input[type='text'],input[type='email']").blur(function(){
        $(this).val($(this).val().trim());
    });

    //火狐里面阻止form提交
    $("input[type='text'],input[type='password']").keydown(function(e){
        if(e.keyCode==13){
            return false;
        }
    });

});
<commit_msg>修改了路径<commit_after>/**
 * Created with JetBrains WebStorm.
 * User: ty
 * Date: 14-10-5
 * Time: 下午5:34
 * To change this template use File | Settings | File Templates.
 */
var config={
    baseUrl:"",
    qiNiu:{
    	upTokenUrl:"qi-niu/up-token",
        uploadDomain:"http://qiniu-plupload.qiniudn.com/",
        bucketDomain:"http://7xqx6h.com1.z0.glb.clouddn.com/",
        swfUrl:basePath+"/resources/js/lib/Moxie.swf"
    },
    ajaxUrls:{
        courseDesignGetAll:"list",
        courseDesignDelete:"/:id",
        courseWareGetAll:"data/json/courseWare.json",
        courseWareDelete:"/:id",
        courseVideoGetAll:"data/json/courseVideo.json",
        courseVideoDelete:"/:id"
    },
    dataTable:{
        langUrl:basePath+"/resources/lang/de_DE.txt"
    },
    perLoadCounts:{
        table:10
    },
    uploader:{
        url:"#",
        swfUrl:basePath+"/resources/js/plupload/plupload.flash.swf",
        sizes:{
            all:"5120m",
            img:"2m"
        },
        filters:{
            all:"*",
            zip:"zip,rar",
            img:"jpg,JPG,jpeg,JPEG,png,PNG"
        }
    },
    validErrors:{
        required:"请输入此字段！",
        email:"请输入正确的邮箱格式！",
        emailExist:"邮箱已经存在！",
        uploadImg:"请上传图片！",
        number:"请输入数字",
        maxLength:"此字段最多输入${max}个字！",
        minLength:"此字段最少输入${min}个字！",
        rangLength:"此字段只能输入${min}-${max}个字！",
        rang:"此字段只能输入${min}-${max}之间的整数！",
        pwdNotEqual:"两次输入的密码不一样！"
    },
    messages:{
        successTitle:"成功提示",
        errorTitle:"错误提示",
        optSuccess:"操作成功！",
        noData:"没有数据",
        progress:"处理中...",
        uploaded:"上传完成！",
        confirmDelete:"确定删除吗？",
        confirm:"确定执行此操作吗？",
        noSelected:"没有选中任何记录！",
        notFound:"资源丢失！",
        loadDataError:"请求数据失败！",
        networkError:"网络异常，请稍后重试！",
        systemError:"系统错误，请稍后重试或者联系mail@lotusprize.com！",
        optSuccRedirect:"操作成功,3秒后跳转到管理页！",
        timeout:"登录超时，3秒后自动跳到登陆页！",
        optError:"服务器端异常，请稍后重试！",
        uploadSizeError:"最大文件大小${value}！",
        uploadExtensionError:"只允许上传${value}！",
        uploadIOError:"上传出错，请稍后重试！",
        imageSizeError:"图片大小不符合！"
    }
};
$(document).ready(function(){

    $("input[type='text'],input[type='email']").blur(function(){
        $(this).val($(this).val().trim());
    });

    //火狐里面阻止form提交
    $("input[type='text'],input[type='password']").keydown(function(e){
        if(e.keyCode==13){
            return false;
        }
    });

});
<|endoftext|>