<commit_before>var expect = require('expect.js');
var async = require('async');

module.exports = function() {
describe('client subscribe', function() {

  it('can call bulk without doing any actions', function() {
    var connection = this.backend.connect();
    connection.startBulk();
    connection.endBulk();
  });

  ['fetch', 'subscribe'].forEach(function(method) {
    it(method + ' gets initial data', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2[method](function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
      });
    });

    it(method + ' twice simultaneously calls back', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        async.parallel([
          function(cb) { doc2.fetch(cb); },
          function(cb) { doc2.fetch(cb); }
        ], function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
      });
    });

    it(method + ' twice in bulk simultaneously calls back', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2.connection.startBulk();
        async.parallel([
          function(cb) { doc2[method](cb); },
          function(cb) { doc2[method](cb); }
        ], function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
        doc2.connection.endBulk();
      });
    });

    it(method + ' bulk on same collection', function(done) {
      var connection = this.backend.connect();
      var connection2 = this.backend.connect();
      async.parallel([
        function(cb) { connection.get('dogs', 'fido').create({age: 3}, cb); },
        function(cb) { connection.get('dogs', 'spot').create({age: 5}, cb); },
        function(cb) { connection.get('cats', 'finn').create({age: 2}, cb); }
      ], function(err) {
        if (err) return done(err);
        var fido = connection2.get('dogs', 'fido');
        var spot = connection2.get('dogs', 'spot');
        var finn = connection2.get('cats', 'finn');
        connection2.startBulk();
        async.parallel([
          function(cb) { fido[method](cb); },
          function(cb) { spot[method](cb); },
          function(cb) { finn[method](cb); }
        ], function(err) {
          if (err) return done(err);
          expect(fido.data).eql({age: 3});
          expect(spot.data).eql({age: 5});
          expect(finn.data).eql({age: 2});
          done();
        });
        connection2.endBulk();
      });
    });

    it(method + ' bulk on same collection from known version', function(done) {
      var connection = this.backend.connect();
      var connection2 = this.backend.connect();
      var fido = connection2.get('dogs', 'fido');
      var spot = connection2.get('dogs', 'spot');
      var finn = connection2.get('cats', 'finn');
      connection2.startBulk();
      async.parallel([
        function(cb) { fido[method](cb); },
        function(cb) { spot[method](cb); },
        function(cb) { finn[method](cb); }
      ], function(err) {
        if (err) return done(err);
        expect(fido.version).equal(0);
        expect(spot.version).equal(0);
        expect(finn.version).equal(0);
        expect(fido.data).equal(undefined);
        expect(spot.data).equal(undefined);
        expect(finn.data).equal(undefined);

        async.parallel([
          function(cb) { connection.get('dogs', 'fido').create({age: 3}, cb); },
          function(cb) { connection.get('dogs', 'spot').create({age: 5}, cb); },
          function(cb) { connection.get('cats', 'finn').create({age: 2}, cb); }
        ], function(err) {
          if (err) return done(err);
          connection2.startBulk();
          async.parallel([
            function(cb) { fido[method](cb); },
            function(cb) { spot[method](cb); },
            function(cb) { finn[method](cb); }
          ], function(err) {
            if (err) return done(err);
            expect(fido.data).eql({age: 3});
            expect(spot.data).eql({age: 5});
            expect(finn.data).eql({age: 2});

            // Test sending a fetch without any new ops being created
            connection2.startBulk();
            async.parallel([
              function(cb) { fido[method](cb); },
              function(cb) { spot[method](cb); },
              function(cb) { finn[method](cb); }
            ], function(err) {
              if (err) return done(err);

              // Create new ops and test if they are received
              async.parallel([
                function(cb) { connection.get('dogs', 'fido').submitOp([{p: ['age'], na: 1}], cb); },
                function(cb) { connection.get('dogs', 'spot').submitOp([{p: ['age'], na: 1}], cb); },
                function(cb) { connection.get('cats', 'finn').submitOp([{p: ['age'], na: 1}], cb); }
              ], function(err) {
                if (err) return done(err);
                connection2.startBulk();
                async.parallel([
                  function(cb) { fido[method](cb); },
                  function(cb) { spot[method](cb); },
                  function(cb) { finn[method](cb); }
                ], function(err) {
                  if (err) return done(err);
                  expect(fido.data).eql({age: 4});
                  expect(spot.data).eql({age: 6});
                  expect(finn.data).eql({age: 3});
                  done();
                });
                connection2.endBulk();
              });
            });
            connection2.endBulk();
          });
          connection2.endBulk();
        });
      });
      connection2.endBulk();
    });

    it(method + ' gets new ops', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2.fetch(function(err) {
          if (err) return done(err);
          doc.submitOp({p: ['age'], na: 1}, function(err) {
            if (err) return done(err);
            doc2.on('op', function(op, context) {
              done();
            });
            doc2[method]();
          });
        });
      });
    });
  });

  it('subscribed client gets create from first client', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc2.subscribe(function(err) {
      if (err) return done(err);
      doc2.on('create', function(context) {
        expect(context).equal(false);
        expect(doc2.version).eql(1);
        expect(doc2.data).eql({age: 3});
        done();
      });
      doc.create({age: 3});
    });
  });

  it('subscribed client gets op from first client', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          expect(doc2.version).eql(2);
          expect(doc2.data).eql({age: 4});
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

  it('unsubscribe stops op updates', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc2.unsubscribe(function(err) {
          if (err) return done(err);
          done();
          doc.submitOp({p: ['age'], na: 1});
        });
      });
    });
  });

  it('doc destroy stops op updates', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc2.destroy(function(err) {
          if (err) return done(err);
          done();
          doc.submitOp({p: ['age'], na: 1});
        });
      });
    });
  });

  it('bulk unsubscribe stops op updates', function(done) {
    var connection = this.backend.connect();
    var connection2 = this.backend.connect();
    var doc = connection.get('dogs', 'fido');
    var fido = connection2.get('dogs', 'fido');
    var spot = connection2.get('dogs', 'spot');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      async.parallel([
        function(cb) { fido.subscribe(cb); },
        function(cb) { spot.subscribe(cb); }
      ], function(err) {
        if (err) return done(err);
        fido.connection.startBulk();
        async.parallel([
          function(cb) { fido.unsubscribe(cb); },
          function(cb) { spot.unsubscribe(cb); }
        ], function(err) {
          if (err) return done(err);
          done();
          fido.on('op', function(op, context) {
            done();
          });
          doc.submitOp({p: ['age'], na: 1});
        });
        fido.connection.endBulk();
      });
    });
  });

  it('calling subscribe, unsubscribe, subscribe sync leaves a doc subscribed', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe();
      doc2.unsubscribe();
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

  it('calling subscribe, unsubscribe, subscribe sync leaves a doc subscribed', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe();
      doc2.unsubscribe();
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

});
};
<commit_msg>test of client disconnect causing doc unsubscribe<commit_after>var expect = require('expect.js');
var async = require('async');

module.exports = function() {
describe('client subscribe', function() {

  it('can call bulk without doing any actions', function() {
    var connection = this.backend.connect();
    connection.startBulk();
    connection.endBulk();
  });

  ['fetch', 'subscribe'].forEach(function(method) {
    it(method + ' gets initial data', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2[method](function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
      });
    });

    it(method + ' twice simultaneously calls back', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        async.parallel([
          function(cb) { doc2.fetch(cb); },
          function(cb) { doc2.fetch(cb); }
        ], function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
      });
    });

    it(method + ' twice in bulk simultaneously calls back', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2.connection.startBulk();
        async.parallel([
          function(cb) { doc2[method](cb); },
          function(cb) { doc2[method](cb); }
        ], function(err) {
          if (err) return done(err);
          expect(doc2.version).eql(1);
          expect(doc2.data).eql({age: 3})
          done();
        });
        doc2.connection.endBulk();
      });
    });

    it(method + ' bulk on same collection', function(done) {
      var connection = this.backend.connect();
      var connection2 = this.backend.connect();
      async.parallel([
        function(cb) { connection.get('dogs', 'fido').create({age: 3}, cb); },
        function(cb) { connection.get('dogs', 'spot').create({age: 5}, cb); },
        function(cb) { connection.get('cats', 'finn').create({age: 2}, cb); }
      ], function(err) {
        if (err) return done(err);
        var fido = connection2.get('dogs', 'fido');
        var spot = connection2.get('dogs', 'spot');
        var finn = connection2.get('cats', 'finn');
        connection2.startBulk();
        async.parallel([
          function(cb) { fido[method](cb); },
          function(cb) { spot[method](cb); },
          function(cb) { finn[method](cb); }
        ], function(err) {
          if (err) return done(err);
          expect(fido.data).eql({age: 3});
          expect(spot.data).eql({age: 5});
          expect(finn.data).eql({age: 2});
          done();
        });
        connection2.endBulk();
      });
    });

    it(method + ' bulk on same collection from known version', function(done) {
      var connection = this.backend.connect();
      var connection2 = this.backend.connect();
      var fido = connection2.get('dogs', 'fido');
      var spot = connection2.get('dogs', 'spot');
      var finn = connection2.get('cats', 'finn');
      connection2.startBulk();
      async.parallel([
        function(cb) { fido[method](cb); },
        function(cb) { spot[method](cb); },
        function(cb) { finn[method](cb); }
      ], function(err) {
        if (err) return done(err);
        expect(fido.version).equal(0);
        expect(spot.version).equal(0);
        expect(finn.version).equal(0);
        expect(fido.data).equal(undefined);
        expect(spot.data).equal(undefined);
        expect(finn.data).equal(undefined);

        async.parallel([
          function(cb) { connection.get('dogs', 'fido').create({age: 3}, cb); },
          function(cb) { connection.get('dogs', 'spot').create({age: 5}, cb); },
          function(cb) { connection.get('cats', 'finn').create({age: 2}, cb); }
        ], function(err) {
          if (err) return done(err);
          connection2.startBulk();
          async.parallel([
            function(cb) { fido[method](cb); },
            function(cb) { spot[method](cb); },
            function(cb) { finn[method](cb); }
          ], function(err) {
            if (err) return done(err);
            expect(fido.data).eql({age: 3});
            expect(spot.data).eql({age: 5});
            expect(finn.data).eql({age: 2});

            // Test sending a fetch without any new ops being created
            connection2.startBulk();
            async.parallel([
              function(cb) { fido[method](cb); },
              function(cb) { spot[method](cb); },
              function(cb) { finn[method](cb); }
            ], function(err) {
              if (err) return done(err);

              // Create new ops and test if they are received
              async.parallel([
                function(cb) { connection.get('dogs', 'fido').submitOp([{p: ['age'], na: 1}], cb); },
                function(cb) { connection.get('dogs', 'spot').submitOp([{p: ['age'], na: 1}], cb); },
                function(cb) { connection.get('cats', 'finn').submitOp([{p: ['age'], na: 1}], cb); }
              ], function(err) {
                if (err) return done(err);
                connection2.startBulk();
                async.parallel([
                  function(cb) { fido[method](cb); },
                  function(cb) { spot[method](cb); },
                  function(cb) { finn[method](cb); }
                ], function(err) {
                  if (err) return done(err);
                  expect(fido.data).eql({age: 4});
                  expect(spot.data).eql({age: 6});
                  expect(finn.data).eql({age: 3});
                  done();
                });
                connection2.endBulk();
              });
            });
            connection2.endBulk();
          });
          connection2.endBulk();
        });
      });
      connection2.endBulk();
    });

    it(method + ' gets new ops', function(done) {
      var doc = this.backend.connect().get('dogs', 'fido');
      var doc2 = this.backend.connect().get('dogs', 'fido');
      doc.create({age: 3}, function(err) {
        if (err) return done(err);
        doc2.fetch(function(err) {
          if (err) return done(err);
          doc.submitOp({p: ['age'], na: 1}, function(err) {
            if (err) return done(err);
            doc2.on('op', function(op, context) {
              done();
            });
            doc2[method]();
          });
        });
      });
    });
  });

  it('subscribed client gets create from first client', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc2.subscribe(function(err) {
      if (err) return done(err);
      doc2.on('create', function(context) {
        expect(context).equal(false);
        expect(doc2.version).eql(1);
        expect(doc2.data).eql({age: 3});
        done();
      });
      doc.create({age: 3});
    });
  });

  it('subscribed client gets op from first client', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          expect(doc2.version).eql(2);
          expect(doc2.data).eql({age: 4});
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

  it('disconnecting stops op updates', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc2.connection.close();
        doc.submitOp({p: ['age'], na: 1});
        done();
      });
    });
  });

  it('unsubscribe stops op updates', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc2.unsubscribe(function(err) {
          if (err) return done(err);
          done();
          doc.submitOp({p: ['age'], na: 1});
        });
      });
    });
  });

  it('doc destroy stops op updates', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc2.destroy(function(err) {
          if (err) return done(err);
          done();
          doc.submitOp({p: ['age'], na: 1});
        });
      });
    });
  });

  it('bulk unsubscribe stops op updates', function(done) {
    var connection = this.backend.connect();
    var connection2 = this.backend.connect();
    var doc = connection.get('dogs', 'fido');
    var fido = connection2.get('dogs', 'fido');
    var spot = connection2.get('dogs', 'spot');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      async.parallel([
        function(cb) { fido.subscribe(cb); },
        function(cb) { spot.subscribe(cb); }
      ], function(err) {
        if (err) return done(err);
        fido.connection.startBulk();
        async.parallel([
          function(cb) { fido.unsubscribe(cb); },
          function(cb) { spot.unsubscribe(cb); }
        ], function(err) {
          if (err) return done(err);
          done();
          fido.on('op', function(op, context) {
            done();
          });
          doc.submitOp({p: ['age'], na: 1});
        });
        fido.connection.endBulk();
      });
    });
  });

  it('calling subscribe, unsubscribe, subscribe sync leaves a doc subscribed', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe();
      doc2.unsubscribe();
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

  it('calling subscribe, unsubscribe, subscribe sync leaves a doc subscribed', function(done) {
    var doc = this.backend.connect().get('dogs', 'fido');
    var doc2 = this.backend.connect().get('dogs', 'fido');
    doc.create({age: 3}, function(err) {
      if (err) return done(err);
      doc2.subscribe();
      doc2.unsubscribe();
      doc2.subscribe(function(err) {
        if (err) return done(err);
        doc2.on('op', function(op, context) {
          done();
        });
        doc.submitOp({p: ['age'], na: 1});
      });
    });
  });

});
};
<|endoftext|>


<commit_before>Components.utils.import("resource://gre/modules/Services.jsm");
Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
Components.utils.import("resource://gre/modules/osfile.jsm");
var EventUtils = Components.utils.import("resource://zotero-unit/EventUtils.jsm");

var ZoteroUnit = Components.classes["@mozilla.org/commandlinehandler/general-startup;1?type=zotero-unit"].
                 getService(Components.interfaces.nsISupports).
                 wrappedJSObject;

var dump = ZoteroUnit.dump;

// Mocha HTML reporter doesn't show deepEqual diffs, so we change this.
chai.config.truncateThreshold = 0

function quit(failed) {
	// Quit with exit status
	if(!failed) {
		OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, "success"), new Uint8Array(0));
	}
	if(!ZoteroUnit.noquit) {
		setTimeout(function () {
			Components.classes['@mozilla.org/toolkit/app-startup;1']
				.getService(Components.interfaces.nsIAppStartup)
				.quit(Components.interfaces.nsIAppStartup.eForceQuit);
		}, 250);
	}
}

if (ZoteroUnit.makeTestData) {
	let dataPath = getTestDataDirectory().path;

	Zotero.Prefs.set("export.citePaperJournalArticleURL", true);

	let dataFiles = [
		{
			name: 'allTypesAndFields',
			func: generateAllTypesAndFieldsData
		},
		{
			name: 'itemJSON',
			func: generateItemJSONData,
			args: [null]
		},
		// {
		// 	name: 'citeProcJSExport',
		// 	func: generateCiteProcJSExportData
		// },
		{
			name: 'translatorExportLegacy',
			func: generateTranslatorExportData,
			args: [true]
		},
		{
			name: 'translatorExport',
			func: generateTranslatorExportData,
			args: [false]
		}
	];
	Zotero.Promise.coroutine(function* () {
		yield Zotero.initializationPromise;
		for (let i=0; i<dataFiles.length; i++) {
			let first = !i;
			let params = dataFiles[i];

			// Make sure to not run next loop if previous fails
			if (!first) dump('\n');
			dump('Generating data for ' + params.name + '...');

			let filePath = OS.Path.join(dataPath, params.name + '.js');
			let exists = yield OS.File.exists(filePath);
			let currentData;
			if (exists) {
				currentData = loadSampleData(params.name);
			}

			let args = params.args || [];
			args.push(currentData);
			let newData = params.func.apply(null, args);
			if (newData instanceof Zotero.Promise) {
				newData = yield newData;
			}
			let str = stableStringify(newData);

			yield OS.File.writeAtomic(OS.Path.join(dataPath, params.name + '.js'), str);
			dump("done.");
		}
	})()
	.catch(function(e) { dump('\n'); dump(Zotero.Utilities.varDump(e)) })
	.finally(function() { quit(false) });
}

function Reporter(runner) {
	var indents = 0, passed = 0, failed = 0, aborted = false;

	function indent() {
		return Array(indents).join('  ');
	}

	runner.on('start', function(){});

	runner.on('suite', function(suite){
		++indents;
		dump("\r"+indent()+suite.title+"\n");
	});

	runner.on('suite end', function(suite){
		--indents;
		if (1 == indents) dump("\n");
	});

	runner.on('pending', function(test){
		dump("\r"+indent()+"pending  -"+test.title+"\n");
	});

	runner.on('pass', function(test){
		passed++;
		var msg = "\r"+indent()+Mocha.reporters.Base.symbols.ok+" "+test.title;
		if ('fast' != test.speed) {
			msg += " ("+Math.round(test.duration)+" ms)";
		}
		dump(msg+"\n");
	});

	runner.on('fail', function(test, err){
		// Remove internal code references
		err.stack = err.stack.replace(/.+(?:zotero-unit\/|\/Task\.jsm|\/bluebird\.js).+\n?/g, "");

		// Strip "From previous event:" block if it's all internals
		if (err.stack.indexOf('From previous event:') != -1) {
			err.stack = err.stack
				// Drop first line, because it contains the error message
				.replace(/^.+\n/, '')
				// Drop "From previous event:" labels for empty blocks
				.replace(/.*From previous event:.*(?:\n(?=\s*From previous event:)|\s*$)/g, '');
		}

		// Make sure there's a blank line after all stack traces
		err.stack = err.stack.replace(/\s*$/, '\n\n');

		failed++;
		let indentStr = indent();
		dump("\r" + indentStr
			// Dark red X for errors
			+ "\033[31;40m" + Mocha.reporters.Base.symbols.err + " [FAIL]\033[0m"
			// Trigger bell if interactive
			+ (Zotero.automatedTest ? "" : "\007")
			+ " " + test.title + "\n"
			+ indentStr + "  " + err.toString() + " at\n"
			+ err.stack.replace(/^/gm, indentStr + "    "));

		if (ZoteroUnit.bail) {
			aborted = true;
			runner.abort();
		}
	});

	runner.on('end', function() {
		dump(passed + "/" + (passed + failed) + " tests passed"
			+ (aborted ? " -- aborting" : "") + "\n");
		quit(failed != 0);
	});
}

// Monkey-patch Mocha to check instanceof Error using compartment-local
// Error object
Mocha.Runner.prototype.fail = function(test, err){
	++this.failures;
	test.state = 'failed';

	if ('string' == typeof err) {
		err = new Error('the string "' + err + '" was thrown, throw an Error :)');
	} else if (!(err instanceof Components.utils.getGlobalForObject(err).Error)) {
		err = new Error('the ' + Mocha.utils.type(err) + ' ' + Mocha.utils.stringify(err) + ' was thrown, throw an Error :)');
	}

	this.emit('fail', test, err);
};

// Setup Mocha
mocha.setup({
	ui: "bdd",
	reporter: Reporter,
	timeout: ZoteroUnit.timeout || 10000,
	grep: ZoteroUnit.grep
});

// Enable Bluebird generator support in Mocha
(function () {
	var Runnable = Mocha.Runnable;
	var run = Runnable.prototype.run;
	Runnable.prototype.run = function (fn) {
		if (this.fn.constructor.name === 'GeneratorFunction') {
			this.fn = Zotero.Promise.coroutine(this.fn);
		} else if (typeof this.fn == 'function' && this.fn.isGenerator()) {
			throw new Error("Attempting to use a legacy generator in Mocha test");
		}
		return run.call(this, fn);
	};
})();

before(function () {
	// Store all prefs set in runtests.sh
	Components.utils.import("resource://zotero/config.js");
	var prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
	ZoteroUnit.customPrefs = {};
	prefBranch.getChildList("", {})
		.filter(key => prefBranch.prefHasUserValue(key))
		.forEach(key => ZoteroUnit.customPrefs[key] = Zotero.Prefs.get(key));
});

/**
 * Clear all prefs, and reset those set in runtests.sh to original values
 */
function resetPrefs() {
	Components.utils.import("resource://zotero/config.js");
	var prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
	prefBranch.getChildList("", {}).forEach(key => {
		var origVal = ZoteroUnit.customPrefs[key];
		if (origVal !== undefined) {
			if (origVal != Zotero.Prefs.get(key)) {
				Zotero.Prefs.set(key, ZoteroUnit.customPrefs[key]);
			}
		}
		else if (prefBranch.prefHasUserValue(key)) {
			Zotero.Prefs.clear(key)
		}
	});
}

afterEach(function () {
	resetPrefs();
});

var assert = chai.assert,
    expect = chai.expect;

// Set up tests to run
var run = ZoteroUnit.runTests;
if(run && ZoteroUnit.tests) {
	function getTestFilename(test) {
		// Allow foo, fooTest, fooTest.js, and tests/fooTest.js
		test = test.replace(/\.js$/, "");
		test = test.replace(/Test$/, "");
		test = test.replace(/^tests[/\\]/, "");
		return test + "Test.js";
	}

	var testDirectory = getTestDataDirectory().parent,
	    testFiles = [];
	if(ZoteroUnit.tests == "all") {
		var enumerator = testDirectory.directoryEntries;
		let startFile = ZoteroUnit.startAt ? getTestFilename(ZoteroUnit.startAt) : false;
		let started = !startFile;
		while(enumerator.hasMoreElements()) {
			var file = enumerator.getNext().QueryInterface(Components.interfaces.nsIFile);
			if(file.leafName.endsWith(".js")) {
				if (started || file.leafName == startFile) {
					testFiles.push(file.leafName);
					started = true;
				}
			}
		}
		if (!started) {
			dump(`Invalid start file ${startFile}\n`);
		}
		testFiles.sort();
	} else {
		var specifiedTests = ZoteroUnit.tests.split(",");
		for (let test of specifiedTests) {
			let fname = getTestFilename(test);
			let file = testDirectory.clone();
			file.append(fname);
			if (!file.exists()) {
				dump("Invalid test file "+test+"\n");
				run = false;
				quit(true);
			}
			testFiles.push(fname);
		}
	}

	for(var fname of testFiles) {
		var el = document.createElement("script");
		el.type = "application/javascript;version=1.8";
		el.src = "resource://zotero-unit-tests/"+fname;
		el.async = false;
		document.body.appendChild(el);
	}
}

if(run) {
	window.onload = function() {
		Zotero.spawn(function* () {
			yield Zotero.Schema.schemaUpdatePromise;

			// Download and cache PDF tools for this platform
			//
			// To reset, delete test/tests/data/pdf/ directory
			var cachePDFTools = Zotero.Promise.coroutine(function* () {
				var path = OS.Path.join(getTestDataDirectory().path, 'pdf');
				yield OS.File.makeDir(path, { ignoreExisting: true });

				var baseURL = Zotero.Fulltext.pdfToolsDownloadBaseURL;
				// Point full-text code to the cache directory, so downloads come from there
				Zotero.Fulltext.pdfToolsDownloadBaseURL = OS.Path.toFileURI(path) + "/";

				// Get latest tools version for the current platform
				yield Zotero.File.download(baseURL + 'latest.json', OS.Path.join(path, 'latest.json'));

				var platform = Zotero.platform.replace(/\s/g, '-');
				var version = yield Zotero.Fulltext.getLatestPDFToolsVersion();

				// Create version directory (e.g., data/pdf/3.04) and download tools to it if
				// they don't exist
				yield OS.File.makeDir(OS.Path.join(path, version), { ignoreExisting: true });

				var fileName = "pdfinfo-" + platform + (Zotero.isWin ? ".exe" : "");
				var execPath = OS.Path.join(path, version, fileName);
				if (!(yield OS.File.exists(execPath))) {
					yield Zotero.File.download(baseURL + version + "/" + fileName, execPath);
				}
				fileName = "pdftotext-" + platform + (Zotero.isWin ? ".exe" : "");;
				execPath = OS.Path.join(path, version, fileName);
				if (!(yield OS.File.exists(execPath))) {
					yield Zotero.File.download(baseURL + version + "/" + fileName, execPath);
				}
			});

			try {
				yield cachePDFTools();
			}
			catch (e) {
				Zotero.logError(e);
			}

			return mocha.run();
		})
	};
}<commit_msg>Convert non-standard octal escape sequences to hex in test runner<commit_after>Components.utils.import("resource://gre/modules/Services.jsm");
Services.scriptloader.loadSubScript("resource://zotero/polyfill.js");
Components.utils.import("resource://gre/modules/osfile.jsm");
var EventUtils = Components.utils.import("resource://zotero-unit/EventUtils.jsm");

var ZoteroUnit = Components.classes["@mozilla.org/commandlinehandler/general-startup;1?type=zotero-unit"].
                 getService(Components.interfaces.nsISupports).
                 wrappedJSObject;

var dump = ZoteroUnit.dump;

// Mocha HTML reporter doesn't show deepEqual diffs, so we change this.
chai.config.truncateThreshold = 0

function quit(failed) {
	// Quit with exit status
	if(!failed) {
		OS.File.writeAtomic(OS.Path.join(OS.Constants.Path.profileDir, "success"), new Uint8Array(0));
	}
	if(!ZoteroUnit.noquit) {
		setTimeout(function () {
			Components.classes['@mozilla.org/toolkit/app-startup;1']
				.getService(Components.interfaces.nsIAppStartup)
				.quit(Components.interfaces.nsIAppStartup.eForceQuit);
		}, 250);
	}
}

if (ZoteroUnit.makeTestData) {
	let dataPath = getTestDataDirectory().path;

	Zotero.Prefs.set("export.citePaperJournalArticleURL", true);

	let dataFiles = [
		{
			name: 'allTypesAndFields',
			func: generateAllTypesAndFieldsData
		},
		{
			name: 'itemJSON',
			func: generateItemJSONData,
			args: [null]
		},
		// {
		// 	name: 'citeProcJSExport',
		// 	func: generateCiteProcJSExportData
		// },
		{
			name: 'translatorExportLegacy',
			func: generateTranslatorExportData,
			args: [true]
		},
		{
			name: 'translatorExport',
			func: generateTranslatorExportData,
			args: [false]
		}
	];
	Zotero.Promise.coroutine(function* () {
		yield Zotero.initializationPromise;
		for (let i=0; i<dataFiles.length; i++) {
			let first = !i;
			let params = dataFiles[i];

			// Make sure to not run next loop if previous fails
			if (!first) dump('\n');
			dump('Generating data for ' + params.name + '...');

			let filePath = OS.Path.join(dataPath, params.name + '.js');
			let exists = yield OS.File.exists(filePath);
			let currentData;
			if (exists) {
				currentData = loadSampleData(params.name);
			}

			let args = params.args || [];
			args.push(currentData);
			let newData = params.func.apply(null, args);
			if (newData instanceof Zotero.Promise) {
				newData = yield newData;
			}
			let str = stableStringify(newData);

			yield OS.File.writeAtomic(OS.Path.join(dataPath, params.name + '.js'), str);
			dump("done.");
		}
	})()
	.catch(function(e) { dump('\n'); dump(Zotero.Utilities.varDump(e)) })
	.finally(function() { quit(false) });
}

function Reporter(runner) {
	var indents = 0, passed = 0, failed = 0, aborted = false;

	function indent() {
		return Array(indents).join('  ');
	}

	runner.on('start', function(){});

	runner.on('suite', function(suite){
		++indents;
		dump("\r"+indent()+suite.title+"\n");
	});

	runner.on('suite end', function(suite){
		--indents;
		if (1 == indents) dump("\n");
	});

	runner.on('pending', function(test){
		dump("\r"+indent()+"pending  -"+test.title+"\n");
	});

	runner.on('pass', function(test){
		passed++;
		var msg = "\r"+indent()+Mocha.reporters.Base.symbols.ok+" "+test.title;
		if ('fast' != test.speed) {
			msg += " ("+Math.round(test.duration)+" ms)";
		}
		dump(msg+"\n");
	});

	runner.on('fail', function(test, err){
		// Remove internal code references
		err.stack = err.stack.replace(/.+(?:zotero-unit\/|\/Task\.jsm|\/bluebird\.js).+\n?/g, "");

		// Strip "From previous event:" block if it's all internals
		if (err.stack.indexOf('From previous event:') != -1) {
			err.stack = err.stack
				// Drop first line, because it contains the error message
				.replace(/^.+\n/, '')
				// Drop "From previous event:" labels for empty blocks
				.replace(/.*From previous event:.*(?:\n(?=\s*From previous event:)|\s*$)/g, '');
		}

		// Make sure there's a blank line after all stack traces
		err.stack = err.stack.replace(/\s*$/, '\n\n');

		failed++;
		let indentStr = indent();
		dump("\r" + indentStr
			// Dark red X for errors
			+ "\x1B[31;40m" + Mocha.reporters.Base.symbols.err + " [FAIL]\x1B[0m"
			// Trigger bell if interactive
			+ (Zotero.automatedTest ? "" : "\x07")
			+ " " + test.title + "\n"
			+ indentStr + "  " + err.toString() + " at\n"
			+ err.stack.replace(/^/gm, indentStr + "    "));

		if (ZoteroUnit.bail) {
			aborted = true;
			runner.abort();
		}
	});

	runner.on('end', function() {
		dump(passed + "/" + (passed + failed) + " tests passed"
			+ (aborted ? " -- aborting" : "") + "\n");
		quit(failed != 0);
	});
}

// Monkey-patch Mocha to check instanceof Error using compartment-local
// Error object
Mocha.Runner.prototype.fail = function(test, err){
	++this.failures;
	test.state = 'failed';

	if ('string' == typeof err) {
		err = new Error('the string "' + err + '" was thrown, throw an Error :)');
	} else if (!(err instanceof Components.utils.getGlobalForObject(err).Error)) {
		err = new Error('the ' + Mocha.utils.type(err) + ' ' + Mocha.utils.stringify(err) + ' was thrown, throw an Error :)');
	}

	this.emit('fail', test, err);
};

// Setup Mocha
mocha.setup({
	ui: "bdd",
	reporter: Reporter,
	timeout: ZoteroUnit.timeout || 10000,
	grep: ZoteroUnit.grep
});

// Enable Bluebird generator support in Mocha
(function () {
	var Runnable = Mocha.Runnable;
	var run = Runnable.prototype.run;
	Runnable.prototype.run = function (fn) {
		if (this.fn.constructor.name === 'GeneratorFunction') {
			this.fn = Zotero.Promise.coroutine(this.fn);
		} else if (typeof this.fn == 'function' && this.fn.isGenerator()) {
			throw new Error("Attempting to use a legacy generator in Mocha test");
		}
		return run.call(this, fn);
	};
})();

before(function () {
	// Store all prefs set in runtests.sh
	Components.utils.import("resource://zotero/config.js");
	var prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
	ZoteroUnit.customPrefs = {};
	prefBranch.getChildList("", {})
		.filter(key => prefBranch.prefHasUserValue(key))
		.forEach(key => ZoteroUnit.customPrefs[key] = Zotero.Prefs.get(key));
});

/**
 * Clear all prefs, and reset those set in runtests.sh to original values
 */
function resetPrefs() {
	Components.utils.import("resource://zotero/config.js");
	var prefBranch = Services.prefs.getBranch(ZOTERO_CONFIG.PREF_BRANCH);
	prefBranch.getChildList("", {}).forEach(key => {
		var origVal = ZoteroUnit.customPrefs[key];
		if (origVal !== undefined) {
			if (origVal != Zotero.Prefs.get(key)) {
				Zotero.Prefs.set(key, ZoteroUnit.customPrefs[key]);
			}
		}
		else if (prefBranch.prefHasUserValue(key)) {
			Zotero.Prefs.clear(key)
		}
	});
}

afterEach(function () {
	resetPrefs();
});

var assert = chai.assert,
    expect = chai.expect;

// Set up tests to run
var run = ZoteroUnit.runTests;
if(run && ZoteroUnit.tests) {
	function getTestFilename(test) {
		// Allow foo, fooTest, fooTest.js, and tests/fooTest.js
		test = test.replace(/\.js$/, "");
		test = test.replace(/Test$/, "");
		test = test.replace(/^tests[/\\]/, "");
		return test + "Test.js";
	}

	var testDirectory = getTestDataDirectory().parent,
	    testFiles = [];
	if(ZoteroUnit.tests == "all") {
		var enumerator = testDirectory.directoryEntries;
		let startFile = ZoteroUnit.startAt ? getTestFilename(ZoteroUnit.startAt) : false;
		let started = !startFile;
		while(enumerator.hasMoreElements()) {
			var file = enumerator.getNext().QueryInterface(Components.interfaces.nsIFile);
			if(file.leafName.endsWith(".js")) {
				if (started || file.leafName == startFile) {
					testFiles.push(file.leafName);
					started = true;
				}
			}
		}
		if (!started) {
			dump(`Invalid start file ${startFile}\n`);
		}
		testFiles.sort();
	} else {
		var specifiedTests = ZoteroUnit.tests.split(",");
		for (let test of specifiedTests) {
			let fname = getTestFilename(test);
			let file = testDirectory.clone();
			file.append(fname);
			if (!file.exists()) {
				dump("Invalid test file "+test+"\n");
				run = false;
				quit(true);
			}
			testFiles.push(fname);
		}
	}

	for(var fname of testFiles) {
		var el = document.createElement("script");
		el.type = "application/javascript;version=1.8";
		el.src = "resource://zotero-unit-tests/"+fname;
		el.async = false;
		document.body.appendChild(el);
	}
}

if(run) {
	window.onload = function() {
		Zotero.spawn(function* () {
			yield Zotero.Schema.schemaUpdatePromise;

			// Download and cache PDF tools for this platform
			//
			// To reset, delete test/tests/data/pdf/ directory
			var cachePDFTools = Zotero.Promise.coroutine(function* () {
				var path = OS.Path.join(getTestDataDirectory().path, 'pdf');
				yield OS.File.makeDir(path, { ignoreExisting: true });

				var baseURL = Zotero.Fulltext.pdfToolsDownloadBaseURL;
				// Point full-text code to the cache directory, so downloads come from there
				Zotero.Fulltext.pdfToolsDownloadBaseURL = OS.Path.toFileURI(path) + "/";

				// Get latest tools version for the current platform
				yield Zotero.File.download(baseURL + 'latest.json', OS.Path.join(path, 'latest.json'));

				var platform = Zotero.platform.replace(/\s/g, '-');
				var version = yield Zotero.Fulltext.getLatestPDFToolsVersion();

				// Create version directory (e.g., data/pdf/3.04) and download tools to it if
				// they don't exist
				yield OS.File.makeDir(OS.Path.join(path, version), { ignoreExisting: true });

				var fileName = "pdfinfo-" + platform + (Zotero.isWin ? ".exe" : "");
				var execPath = OS.Path.join(path, version, fileName);
				if (!(yield OS.File.exists(execPath))) {
					yield Zotero.File.download(baseURL + version + "/" + fileName, execPath);
				}
				fileName = "pdftotext-" + platform + (Zotero.isWin ? ".exe" : "");;
				execPath = OS.Path.join(path, version, fileName);
				if (!(yield OS.File.exists(execPath))) {
					yield Zotero.File.download(baseURL + version + "/" + fileName, execPath);
				}
			});

			try {
				yield cachePDFTools();
			}
			catch (e) {
				Zotero.logError(e);
			}

			return mocha.run();
		})
	};
}<|endoftext|>


<commit_before>var git = require( "../" );
var rimraf = require("rimraf");
var fs = require( "fs" );

// Helper functions
var helper = {
  // Test if obj is a true function
  testFunction: function(test, obj, label) {
    // The object reports itself as a function
    test(typeof obj, "function", label + " reports as a function.");
    // This ensures the repo is actually a derivative of the Function [[Class]]
    test(toString.call(obj), "[object Function]", label + " [[Class]] is of type function.");
  },
  // Test code and handle exception thrown
  testException: function(test, fun, label) {
    try {
      fun();
      test(false, label);
    }
    catch (ex) {
      test(true, label);
    }
  }
};

// Repo
// Ensure the repo method can handle opening repositories with async/sync
// signatures properly.
exports.method = function(test){
  test.expect(5);

  helper.testFunction(test.equals, git.repo, "Repo");

  // Test callback argument existence
  helper.testException(test.ok, function() {
    git.repo("some/path");
  }, "Throw an exception if no callback");

  // Test invalid repository
  git.repo("/etc/hosts", function(err, path) {
    test.equals("The specified repository is invalid", err, "Invalid repository error code");

    // Test valid repository
    git.repo("../.git", function(err, path) {
      test.equals(0, err, "Valid repository error code");

      test.done();
    });
  });
};

// Repo::Init
// Ensure the init method can create repositories at the destination path and
// can create either bare/non-bare.  This should work async/sync and provide
// the proper return values.
exports.init = function(test) {
  test.expect(4);

  helper.testFunction(test.equals, git.repo().init, "Repo::Init");

  // Cleanup, remove test repo directory - if it exists
  rimraf("./test.git", function() {
    // Create bare repo and test for creation
    git.repo().init("./test.git", true, function(err, path, isBare) {
      test.equals(0, err, "Successfully created bare repository");
      // Verify repo exists
      git.repo("./test.git", function(err, path, repo) {
        test.equals(0, err, "Valid repository created");

        // Cleanup, remove test repo directory
        rimraf("./test.git", test.done);
      });
    });
  });
};
<commit_msg>Replaced " with ', updated test<commit_after>var git = require( '../' );
var rimraf = require('rimraf');
var fs = require( 'fs' );

// Helper functions
var helper = {
  // Test if obj is a true function
  testFunction: function(test, obj, label) {
    // The object reports itself as a function
    test(typeof obj, 'function', label + ' reports as a function.');
    // This ensures the repo is actually a derivative of the Function [[Class]]
    test(toString.call(obj), '[object Function]', label + ' [[Class]] is of type function.');
  },
  // Test code and handle exception thrown
  testException: function(test, fun, label) {
    try {
      fun();
      test(false, label);
    }
    catch (ex) {
      test(true, label);
    }
  }
};

// Repo
// Ensure the repo method can handle opening repositories with async/sync
// signatures properly.
exports.method = function(test){
  test.expect(5);

  helper.testFunction(test.equals, git.repo, 'Repo');

  // Test callback argument existence
  helper.testException(test.ok, function() {
    git.repo('some/path');
  }, 'Throw an exception if no callback');

  // Test invalid repository
  git.repo('/etc/hosts', function(err, path) {
    test.equals('The specified repository is invalid', err.message, 'Invalid repository error code');

    // Test valid repository
    git.repo('../.git', function(err, path) {
      test.equals(0, err, 'Valid repository error code');

      test.done();
    });
  });
};

// Repo::Init
// Ensure the init method can create repositories at the destination path and
// can create either bare/non-bare.  This should work async/sync and provide
// the proper return values.
exports.init = function(test) {
  test.expect(4);

  helper.testFunction(test.equals, git.repo().init, 'Repo::Init');

  // Cleanup, remove test repo directory - if it exists
  rimraf('./test.git', function() {
    // Create bare repo and test for creation
    git.repo().init('./test.git', true, function(err, path, isBare) {
      test.equals(0, err, 'Successfully created bare repository');
      // Verify repo exists
      git.repo('./test.git', function(err, path, repo) {
        test.equals(0, err, 'Valid repository created');

        // Cleanup, remove test repo directory
        rimraf('./test.git', test.done);
      });
    });
  });
};
<|endoftext|>


<commit_before>/*
 * config-test.js: Tests for the config module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var vows = require('vows'),
    helpers = require('../helpers'),
    assert = require('assert'),
    eyes = require('eyes'),
    haibu = require('../../lib/haibu');

vows.describe('haibu/config').addBatch({
  "When using the haibu config module": {
    "it should have the correct methods set": function () {
      assert.isFunction(haibu.config.seed);
      assert.isFunction(haibu.config.load);
      assert.isFunction(haibu.config.get);
      assert.isFunction(haibu.config.set);
      assert.isFunction(haibu.config.clear);
      assert.isFunction(haibu.config.reset);
      assert.isFunction(haibu.config.save);
      assert.isFunction(haibu.config.use);
    },
    "the seed() method": {
      topic: function () {
        haibu.config.seed(this.callback.bind(null, null));
      },
      "should respond without an error": function (ign, err) {
        assert.isTrue(typeof err === 'undefined');
      }
    }
  }
}).addBatch({
  "When using the haibu config module": {
    "the load() method": {
      topic: function () {
        haibu.config.load(this.callback.bind(null, null));
      },
      "should respond without an error": function (ign, err) {
        assert.isTrue(typeof err === 'undefined');
      }
    }
  }
}).addBatch({
  "When using the haibu config module": {
    "it should have the correct default config": function () {
      var include = {
        directories: ['apps', 'packages', 'tmp'],
      };

      Object.keys(include).forEach(function (key) {
        var setting = haibu.config.get(key);
        include[key].forEach(function (set) {
          assert.isTrue(typeof setting[set] !== 'undefined');
        });
      })
    }
  }
}).export(module);
<commit_msg>[refactor test] Update test/core/config-test.js<commit_after>/*
 * config-test.js: Tests for the config module.
 *
 * (C) 2010, Nodejitsu Inc.
 *
 */

var vows = require('vows'),
    helpers = require('../helpers'),
    assert = require('assert'),
    eyes = require('eyes'),
    haibu = require('../../lib/haibu');

vows.describe('haibu/config').addBatch({
  "When using the haibu config module": {
    "it should have the correct methods set": function () {
      assert.isFunction(haibu.config.seed);
      assert.isFunction(haibu.config.load);
      assert.isFunction(haibu.config.get);
      assert.isFunction(haibu.config.set);
      assert.isFunction(haibu.config.clear);
      assert.isFunction(haibu.config.reset);
      assert.isFunction(haibu.config.save);
      assert.isFunction(haibu.config.use);
    },
    "the seed() method": {
      topic: function () {
        haibu.config.seed(this.callback.bind(null, null));
      },
      "should respond without an error": function (_, err) {
        assert.isTrue(typeof err === 'undefined');
      }
    }
  }
}).addBatch(
  helpers.requireInit()
).addBatch({
  "When using the haibu config module": {
    "it should have the correct default config": function () {
      var include = {
        directories: ['apps', 'packages', 'tmp'],
      };

      Object.keys(include).forEach(function (key) {
        var setting = haibu.config.get(key);
        include[key].forEach(function (set) {
          assert.isTrue(typeof setting[set] !== 'undefined');
        });
      })
    }
  }
}).export(module);
<|endoftext|>


<commit_before>(function () {

	var app = angular.module('RbsChange');

	app.config(['$routeProvider', function ($routeProvider) {
		$routeProvider

		// User

		. when(
			'/Rbs/User',
			{
				templateUrl : 'Rbs/User/User/list.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/User',
			{
				templateUrl : 'Rbs/User/User/list.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/User/:id',
			{
				templateUrl : 'Rbs/User/User/form.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/Profile',
			{
				templateUrl : 'Rbs/User/Profile/profile.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/User/:id/Applications/',
			{
				templateUrl: 'Rbs/User/User/applications.twig',
				reloadOnSearch: false
			})

		. when(
			'/Rbs/User/Group',
			{
				templateUrl : 'Rbs/User/Group/list.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/Group/:id',
			{
				templateUrl : 'Rbs/User/Group/form.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/User/:id/Permission/',
			{
				templateUrl : 'Rbs/User/User/permission.twig',
				reloadOnSearch : false
			})

		. when(
			'/Rbs/User/Group/:id/Permission/',
			{
				templateUrl : 'Rbs/User/User/permission.twig',
				reloadOnSearch : false
			})
		;
	}]);

	app.config(['$provide', function ($provide) {
		$provide.decorator('RbsChange.UrlManager', ['$delegate', function ($delegate) {

			// User
			$delegate.register('Rbs_User_User', {
				'form'  : '/Rbs/User/User/:id',
				'list'  : '/Rbs/User/User',
				'applications' : '/Rbs/User/User/:id/Applications/',
				'permission' : '/Rbs/User/User/:id/Permission/'
			});
			// Group
			$delegate.register('Rbs_User_Group', {
				'form'  : '/Rbs/User/Group/:id',
				'list'  : '/Rbs/User/Group',
				'permission' : '/Rbs/User/Group/:id/Permission/'
			});

			return $delegate;

		}]);
	}]);

	function ChangeUserUserLoginController($scope, Workspace, OAuthService, NotificationCenter) {
		Workspace.hideMenus();

		$scope.login = function () {
			OAuthService.authenticate($scope.username, $scope.password).then(

				// Success
				function () {
					Workspace.restore();
				},

				// Failure
				function (failure) {
					NotificationCenter.error("Unable to authenticate", failure);
				}
			);
		};
	}

	ChangeUserUserLoginController.$inject = [
		'$scope',
		'RbsChange.Workspace',
		'OAuthService',
		'RbsChange.NotificationCenter'
	];
	app.controller('Rbs_User_User_LoginController', ChangeUserUserLoginController);

})();<commit_msg>Enhanced routes management.<commit_after>(function () {

	"use strict";

	var app = angular.module('RbsChange');

	/**
	 * Routes and URL definitions.
	 */
	app.config(['$provide', function ($provide)
	{
		$provide.decorator('RbsChange.UrlManager', ['$delegate', function ($delegate)
		{
			$delegate.model('Rbs_User_User')
				.route('applications', 'Rbs/User/User/:id/Applications/', 'Rbs/User/User/applications.twig')
				.route('permission'  , 'Rbs/User/User/:id/Permissions/' , 'Rbs/User/User/permission.twig')
			;

			$delegate.model('Rbs_User_Group')
				.route('permission'  , 'Rbs/User/Group/:id/Permissions/', 'Rbs/User/User/permission.twig')
			;

			$delegate.model(null)
				.route('home', 'Rbs/User/', { 'redirectTo': 'Rbs/User/User/'})
			;

			$delegate.routesForModels([
				'Rbs_User_User',
				'Rbs_User_Group'
			]);

			return $delegate;
		}]);
	}]);

	function ChangeUserUserLoginController($scope, Workspace, OAuthService, NotificationCenter) {
		Workspace.hideMenus();

		$scope.login = function () {
			OAuthService.authenticate($scope.username, $scope.password).then(

				// Success
				function () {
					Workspace.restore();
				},

				// Failure
				function (failure) {
					NotificationCenter.error("Unable to authenticate", failure);
				}
			);
		};
	}

	ChangeUserUserLoginController.$inject = [
		'$scope',
		'RbsChange.Workspace',
		'OAuthService',
		'RbsChange.NotificationCenter'
	];
	app.controller('Rbs_User_User_LoginController', ChangeUserUserLoginController);

})();<|endoftext|>


<commit_before>'use strict'

const createTorrent = require('create-torrent')
const ffmpeg = require('fluent-ffmpeg')
const fs = require('fs')
const magnetUtil = require('magnet-uri')
const map = require('lodash/map')
const parallel = require('async/parallel')
const parseTorrent = require('parse-torrent')
const pathUtils = require('path')

const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
const modelUtils = require('./utils')

// ---------------------------------------------------------------------------

module.exports = function (sequelize, DataTypes) {
  // TODO: add indexes on searchable columns
  const Video = sequelize.define('Video',
    {
      id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true
      },
      name: {
        type: DataTypes.STRING
      },
      extname: {
        // TODO: enum?
        type: DataTypes.STRING
      },
      remoteId: {
        type: DataTypes.UUID
      },
      description: {
        type: DataTypes.STRING
      },
      infoHash: {
        type: DataTypes.STRING
      },
      duration: {
        type: DataTypes.INTEGER
      }
    },
    {
      classMethods: {
        associate,

        generateThumbnailFromBase64,
        getDurationFromFile,
        list,
        listForApi,
        listByHostAndRemoteId,
        listOwnedAndPopulateAuthorAndTags,
        listOwnedByAuthor,
        load,
        loadAndPopulateAuthor,
        loadAndPopulateAuthorAndPodAndTags,
        searchAndPopulateAuthorAndPodAndTags
      },
      instanceMethods: {
        generateMagnetUri,
        getVideoFilename,
        getThumbnailName,
        getPreviewName,
        getTorrentName,
        isOwned,
        toFormatedJSON,
        toRemoteJSON
      },
      hooks: {
        beforeCreate,
        afterDestroy
      }
    }
  )

  return Video
}

// TODO: Validation
// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)

function beforeCreate (video, options, next) {
  const tasks = []

  if (video.isOwned()) {
    const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())

    tasks.push(
      // TODO: refractoring
      function (callback) {
        const options = {
          announceList: [
            [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
          ],
          urlList: [
            constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getVideoFilename()
          ]
        }

        createTorrent(videoPath, options, function (err, torrent) {
          if (err) return callback(err)

          fs.writeFile(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), torrent, function (err) {
            if (err) return callback(err)

            const parsedTorrent = parseTorrent(torrent)
            video.infoHash = parsedTorrent.infoHash

            callback(null)
          })
        })
      },
      function (callback) {
        createThumbnail(video, videoPath, callback)
      },
      function (callback) {
        createPreview(video, videoPath, callback)
      }
    )

    return parallel(tasks, next)
  }

  return next()
}

function afterDestroy (video, options, next) {
  const tasks = []

  tasks.push(
    function (callback) {
      removeThumbnail(video, callback)
    }
  )

  if (video.isOwned()) {
    tasks.push(
      function (callback) {
        removeFile(video, callback)
      },
      function (callback) {
        removeTorrent(video, callback)
      },
      function (callback) {
        removePreview(video, callback)
      }
    )
  }

  parallel(tasks, next)
}

// ------------------------------ METHODS ------------------------------

function associate (models) {
  this.belongsTo(models.Author, {
    foreignKey: {
      name: 'authorId',
      allowNull: false
    },
    onDelete: 'cascade'
  })

  this.belongsToMany(models.Tag, {
    foreignKey: 'videoId',
    through: models.VideoTag,
    onDelete: 'cascade'
  })
}

function generateMagnetUri () {
  let baseUrlHttp, baseUrlWs

  if (this.isOwned()) {
    baseUrlHttp = constants.CONFIG.WEBSERVER.URL
    baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
  } else {
    baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
    baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
  }

  const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
  const announce = baseUrlWs + '/tracker/socket'
  const urlList = [ baseUrlHttp + constants.STATIC_PATHS.WEBSEED + this.getVideoFilename() ]

  const magnetHash = {
    xs,
    announce,
    urlList,
    infoHash: this.infoHash,
    name: this.name
  }

  return magnetUtil.encode(magnetHash)
}

function getVideoFilename () {
  if (this.isOwned()) return this.id + this.extname

  return this.remoteId + this.extname
}

function getThumbnailName () {
  // We always have a copy of the thumbnail
  return this.id + '.jpg'
}

function getPreviewName () {
  const extension = '.jpg'

  if (this.isOwned()) return this.id + extension

  return this.remoteId + extension
}

function getTorrentName () {
  const extension = '.torrent'

  if (this.isOwned()) return this.id + extension

  return this.remoteId + extension
}

function isOwned () {
  return this.remoteId === null
}

function toFormatedJSON () {
  let podHost

  if (this.Author.Pod) {
    podHost = this.Author.Pod.host
  } else {
    // It means it's our video
    podHost = constants.CONFIG.WEBSERVER.HOST
  }

  const json = {
    id: this.id,
    name: this.name,
    description: this.description,
    podHost,
    isLocal: this.isOwned(),
    magnetUri: this.generateMagnetUri(),
    author: this.Author.name,
    duration: this.duration,
    tags: map(this.Tags, 'name'),
    thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
    createdAt: this.createdAt
  }

  return json
}

function toRemoteJSON (callback) {
  const self = this

  // Convert thumbnail to base64
  const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
  fs.readFile(thumbnailPath, function (err, thumbnailData) {
    if (err) {
      logger.error('Cannot read the thumbnail of the video')
      return callback(err)
    }

    const remoteVideo = {
      name: self.name,
      description: self.description,
      infoHash: self.infoHash,
      remoteId: self.id,
      author: self.Author.name,
      duration: self.duration,
      thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
      tags: map(self.Tags, 'name'),
      createdAt: self.createdAt,
      extname: self.extname
    }

    return callback(null, remoteVideo)
  })
}

// ------------------------------ STATICS ------------------------------

function generateThumbnailFromBase64 (video, thumbnailData, callback) {
  // Creating the thumbnail for a remote video

  const thumbnailName = video.getThumbnailName()
  const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName
  fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) {
    if (err) return callback(err)

    return callback(null, thumbnailName)
  })
}

function getDurationFromFile (videoPath, callback) {
  ffmpeg.ffprobe(videoPath, function (err, metadata) {
    if (err) return callback(err)

    return callback(null, Math.floor(metadata.format.duration))
  })
}

function list (callback) {
  return this.find().asCallback()
}

function listForApi (start, count, sort, callback) {
  const query = {
    offset: start,
    limit: count,
    distinct: true, // For the count, a video can have many tags
    order: [ modelUtils.getSort(sort) ],
    include: [
      {
        model: this.sequelize.models.Author,
        include: [ { model: this.sequelize.models.Pod, required: false } ]
      },

      this.sequelize.models.Tag
    ]
  }

  return this.findAndCountAll(query).asCallback(function (err, result) {
    if (err) return callback(err)

    return callback(null, result.rows, result.count)
  })
}

function listByHostAndRemoteId (fromHost, remoteId, callback) {
  const query = {
    where: {
      remoteId: remoteId
    },
    include: [
      {
        model: this.sequelize.models.Author,
        include: [
          {
            model: this.sequelize.models.Pod,
            required: true,
            where: {
              host: fromHost
            }
          }
        ]
      }
    ]
  }

  return this.findAll(query).asCallback(callback)
}

function listOwnedAndPopulateAuthorAndTags (callback) {
  // If remoteId is null this is *our* video
  const query = {
    where: {
      remoteId: null
    },
    include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
  }

  return this.findAll(query).asCallback(callback)
}

function listOwnedByAuthor (author, callback) {
  const query = {
    where: {
      remoteId: null
    },
    include: [
      {
        model: this.sequelize.models.Author,
        where: {
          name: author
        }
      }
    ]
  }

  return this.findAll(query).asCallback(callback)
}

function load (id, callback) {
  return this.findById(id).asCallback(callback)
}

function loadAndPopulateAuthor (id, callback) {
  const options = {
    include: [ this.sequelize.models.Author ]
  }

  return this.findById(id, options).asCallback(callback)
}

function loadAndPopulateAuthorAndPodAndTags (id, callback) {
  const options = {
    include: [
      {
        model: this.sequelize.models.Author,
        include: [ { model: this.sequelize.models.Pod, required: false } ]
      },
      this.sequelize.models.Tag
    ]
  }

  return this.findById(id, options).asCallback(callback)
}

function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
  const podInclude = {
    model: this.sequelize.models.Pod,
    required: false
  }

  const authorInclude = {
    model: this.sequelize.models.Author,
    include: [
      podInclude
    ]
  }

  const tagInclude = {
    model: this.sequelize.models.Tag
  }

  const query = {
    where: {},
    offset: start,
    limit: count,
    distinct: true, // For the count, a video can have many tags
    order: [ modelUtils.getSort(sort) ]
  }

  // Make an exact search with the magnet
  if (field === 'magnetUri') {
    const infoHash = magnetUtil.decode(value).infoHash
    query.where.infoHash = infoHash
  } else if (field === 'tags') {
    const escapedValue = this.sequelize.escape('%' + value + '%')
    query.where = {
      id: {
        $in: this.sequelize.literal(
          '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
        )
      }
    }
  } else if (field === 'host') {
    // FIXME: Include our pod? (not stored in the database)
    podInclude.where = {
      host: {
        $like: '%' + value + '%'
      }
    }
    podInclude.required = true
  } else if (field === 'author') {
    authorInclude.where = {
      name: {
        $like: '%' + value + '%'
      }
    }

    // authorInclude.or = true
  } else {
    query.where[field] = {
      $like: '%' + value + '%'
    }
  }

  query.include = [
    authorInclude, tagInclude
  ]

  if (tagInclude.where) {
    // query.include.push([ this.sequelize.models.Tag ])
  }

  return this.findAndCountAll(query).asCallback(function (err, result) {
    if (err) return callback(err)

    return callback(null, result.rows, result.count)
  })
}

// ---------------------------------------------------------------------------

function removeThumbnail (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.getThumbnailName(), callback)
}

function removeFile (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.getVideoFilename(), callback)
}

function removeTorrent (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), callback)
}

function removePreview (video, callback) {
  // Same name than video thumnail
  fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
}

function createPreview (video, videoPath, callback) {
  generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
}

function createThumbnail (video, videoPath, callback) {
  generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), constants.THUMBNAILS_SIZE, callback)
}

function generateImage (video, videoPath, folder, imageName, size, callback) {
  const options = {
    filename: imageName,
    count: 1,
    folder
  }

  if (!callback) {
    callback = size
  } else {
    options.size = size
  }

  ffmpeg(videoPath)
    .on('error', callback)
    .on('end', function () {
      callback(null, imageName)
    })
    .thumbnail(options)
}
<commit_msg>Server: correctly sort tags by name asc<commit_after>'use strict'

const createTorrent = require('create-torrent')
const ffmpeg = require('fluent-ffmpeg')
const fs = require('fs')
const magnetUtil = require('magnet-uri')
const map = require('lodash/map')
const parallel = require('async/parallel')
const parseTorrent = require('parse-torrent')
const pathUtils = require('path')

const constants = require('../initializers/constants')
const logger = require('../helpers/logger')
const modelUtils = require('./utils')

// ---------------------------------------------------------------------------

module.exports = function (sequelize, DataTypes) {
  // TODO: add indexes on searchable columns
  const Video = sequelize.define('Video',
    {
      id: {
        type: DataTypes.UUID,
        defaultValue: DataTypes.UUIDV4,
        primaryKey: true
      },
      name: {
        type: DataTypes.STRING
      },
      extname: {
        // TODO: enum?
        type: DataTypes.STRING
      },
      remoteId: {
        type: DataTypes.UUID
      },
      description: {
        type: DataTypes.STRING
      },
      infoHash: {
        type: DataTypes.STRING
      },
      duration: {
        type: DataTypes.INTEGER
      }
    },
    {
      classMethods: {
        associate,

        generateThumbnailFromBase64,
        getDurationFromFile,
        list,
        listForApi,
        listByHostAndRemoteId,
        listOwnedAndPopulateAuthorAndTags,
        listOwnedByAuthor,
        load,
        loadAndPopulateAuthor,
        loadAndPopulateAuthorAndPodAndTags,
        searchAndPopulateAuthorAndPodAndTags
      },
      instanceMethods: {
        generateMagnetUri,
        getVideoFilename,
        getThumbnailName,
        getPreviewName,
        getTorrentName,
        isOwned,
        toFormatedJSON,
        toRemoteJSON
      },
      hooks: {
        beforeCreate,
        afterDestroy
      }
    }
  )

  return Video
}

// TODO: Validation
// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)

function beforeCreate (video, options, next) {
  const tasks = []

  if (video.isOwned()) {
    const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())

    tasks.push(
      // TODO: refractoring
      function (callback) {
        const options = {
          announceList: [
            [ constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT + '/tracker/socket' ]
          ],
          urlList: [
            constants.CONFIG.WEBSERVER.URL + constants.STATIC_PATHS.WEBSEED + video.getVideoFilename()
          ]
        }

        createTorrent(videoPath, options, function (err, torrent) {
          if (err) return callback(err)

          fs.writeFile(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), torrent, function (err) {
            if (err) return callback(err)

            const parsedTorrent = parseTorrent(torrent)
            video.infoHash = parsedTorrent.infoHash

            callback(null)
          })
        })
      },
      function (callback) {
        createThumbnail(video, videoPath, callback)
      },
      function (callback) {
        createPreview(video, videoPath, callback)
      }
    )

    return parallel(tasks, next)
  }

  return next()
}

function afterDestroy (video, options, next) {
  const tasks = []

  tasks.push(
    function (callback) {
      removeThumbnail(video, callback)
    }
  )

  if (video.isOwned()) {
    tasks.push(
      function (callback) {
        removeFile(video, callback)
      },
      function (callback) {
        removeTorrent(video, callback)
      },
      function (callback) {
        removePreview(video, callback)
      }
    )
  }

  parallel(tasks, next)
}

// ------------------------------ METHODS ------------------------------

function associate (models) {
  this.belongsTo(models.Author, {
    foreignKey: {
      name: 'authorId',
      allowNull: false
    },
    onDelete: 'cascade'
  })

  this.belongsToMany(models.Tag, {
    foreignKey: 'videoId',
    through: models.VideoTag,
    onDelete: 'cascade'
  })
}

function generateMagnetUri () {
  let baseUrlHttp, baseUrlWs

  if (this.isOwned()) {
    baseUrlHttp = constants.CONFIG.WEBSERVER.URL
    baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
  } else {
    baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
    baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
  }

  const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
  const announce = baseUrlWs + '/tracker/socket'
  const urlList = [ baseUrlHttp + constants.STATIC_PATHS.WEBSEED + this.getVideoFilename() ]

  const magnetHash = {
    xs,
    announce,
    urlList,
    infoHash: this.infoHash,
    name: this.name
  }

  return magnetUtil.encode(magnetHash)
}

function getVideoFilename () {
  if (this.isOwned()) return this.id + this.extname

  return this.remoteId + this.extname
}

function getThumbnailName () {
  // We always have a copy of the thumbnail
  return this.id + '.jpg'
}

function getPreviewName () {
  const extension = '.jpg'

  if (this.isOwned()) return this.id + extension

  return this.remoteId + extension
}

function getTorrentName () {
  const extension = '.torrent'

  if (this.isOwned()) return this.id + extension

  return this.remoteId + extension
}

function isOwned () {
  return this.remoteId === null
}

function toFormatedJSON () {
  let podHost

  if (this.Author.Pod) {
    podHost = this.Author.Pod.host
  } else {
    // It means it's our video
    podHost = constants.CONFIG.WEBSERVER.HOST
  }

  const json = {
    id: this.id,
    name: this.name,
    description: this.description,
    podHost,
    isLocal: this.isOwned(),
    magnetUri: this.generateMagnetUri(),
    author: this.Author.name,
    duration: this.duration,
    tags: map(this.Tags, 'name'),
    thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
    createdAt: this.createdAt
  }

  return json
}

function toRemoteJSON (callback) {
  const self = this

  // Convert thumbnail to base64
  const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName())
  fs.readFile(thumbnailPath, function (err, thumbnailData) {
    if (err) {
      logger.error('Cannot read the thumbnail of the video')
      return callback(err)
    }

    const remoteVideo = {
      name: self.name,
      description: self.description,
      infoHash: self.infoHash,
      remoteId: self.id,
      author: self.Author.name,
      duration: self.duration,
      thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
      tags: map(self.Tags, 'name'),
      createdAt: self.createdAt,
      extname: self.extname
    }

    return callback(null, remoteVideo)
  })
}

// ------------------------------ STATICS ------------------------------

function generateThumbnailFromBase64 (video, thumbnailData, callback) {
  // Creating the thumbnail for a remote video

  const thumbnailName = video.getThumbnailName()
  const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName
  fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) {
    if (err) return callback(err)

    return callback(null, thumbnailName)
  })
}

function getDurationFromFile (videoPath, callback) {
  ffmpeg.ffprobe(videoPath, function (err, metadata) {
    if (err) return callback(err)

    return callback(null, Math.floor(metadata.format.duration))
  })
}

function list (callback) {
  return this.find().asCallback()
}

function listForApi (start, count, sort, callback) {
  const query = {
    offset: start,
    limit: count,
    distinct: true, // For the count, a video can have many tags
    order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ],
    include: [
      {
        model: this.sequelize.models.Author,
        include: [ { model: this.sequelize.models.Pod, required: false } ]
      },

      this.sequelize.models.Tag
    ]
  }

  return this.findAndCountAll(query).asCallback(function (err, result) {
    if (err) return callback(err)

    return callback(null, result.rows, result.count)
  })
}

function listByHostAndRemoteId (fromHost, remoteId, callback) {
  const query = {
    where: {
      remoteId: remoteId
    },
    include: [
      {
        model: this.sequelize.models.Author,
        include: [
          {
            model: this.sequelize.models.Pod,
            required: true,
            where: {
              host: fromHost
            }
          }
        ]
      }
    ]
  }

  return this.findAll(query).asCallback(callback)
}

function listOwnedAndPopulateAuthorAndTags (callback) {
  // If remoteId is null this is *our* video
  const query = {
    where: {
      remoteId: null
    },
    include: [ this.sequelize.models.Author, this.sequelize.models.Tag ]
  }

  return this.findAll(query).asCallback(callback)
}

function listOwnedByAuthor (author, callback) {
  const query = {
    where: {
      remoteId: null
    },
    include: [
      {
        model: this.sequelize.models.Author,
        where: {
          name: author
        }
      }
    ]
  }

  return this.findAll(query).asCallback(callback)
}

function load (id, callback) {
  return this.findById(id).asCallback(callback)
}

function loadAndPopulateAuthor (id, callback) {
  const options = {
    include: [ this.sequelize.models.Author ]
  }

  return this.findById(id, options).asCallback(callback)
}

function loadAndPopulateAuthorAndPodAndTags (id, callback) {
  const options = {
    include: [
      {
        model: this.sequelize.models.Author,
        include: [ { model: this.sequelize.models.Pod, required: false } ]
      },
      this.sequelize.models.Tag
    ]
  }

  return this.findById(id, options).asCallback(callback)
}

function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) {
  const podInclude = {
    model: this.sequelize.models.Pod,
    required: false
  }

  const authorInclude = {
    model: this.sequelize.models.Author,
    include: [
      podInclude
    ]
  }

  const tagInclude = {
    model: this.sequelize.models.Tag
  }

  const query = {
    where: {},
    offset: start,
    limit: count,
    distinct: true, // For the count, a video can have many tags
    order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ]
  }

  // Make an exact search with the magnet
  if (field === 'magnetUri') {
    const infoHash = magnetUtil.decode(value).infoHash
    query.where.infoHash = infoHash
  } else if (field === 'tags') {
    const escapedValue = this.sequelize.escape('%' + value + '%')
    query.where = {
      id: {
        $in: this.sequelize.literal(
          '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')'
        )
      }
    }
  } else if (field === 'host') {
    // FIXME: Include our pod? (not stored in the database)
    podInclude.where = {
      host: {
        $like: '%' + value + '%'
      }
    }
    podInclude.required = true
  } else if (field === 'author') {
    authorInclude.where = {
      name: {
        $like: '%' + value + '%'
      }
    }

    // authorInclude.or = true
  } else {
    query.where[field] = {
      $like: '%' + value + '%'
    }
  }

  query.include = [
    authorInclude, tagInclude
  ]

  if (tagInclude.where) {
    // query.include.push([ this.sequelize.models.Tag ])
  }

  return this.findAndCountAll(query).asCallback(function (err, result) {
    if (err) return callback(err)

    return callback(null, result.rows, result.count)
  })
}

// ---------------------------------------------------------------------------

function removeThumbnail (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.THUMBNAILS_DIR + video.getThumbnailName(), callback)
}

function removeFile (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.VIDEOS_DIR + video.getVideoFilename(), callback)
}

function removeTorrent (video, callback) {
  fs.unlink(constants.CONFIG.STORAGE.TORRENTS_DIR + video.getTorrentName(), callback)
}

function removePreview (video, callback) {
  // Same name than video thumnail
  fs.unlink(constants.CONFIG.STORAGE.PREVIEWS_DIR + video.getPreviewName(), callback)
}

function createPreview (video, videoPath, callback) {
  generateImage(video, videoPath, constants.CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName(), callback)
}

function createThumbnail (video, videoPath, callback) {
  generateImage(video, videoPath, constants.CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName(), constants.THUMBNAILS_SIZE, callback)
}

function generateImage (video, videoPath, folder, imageName, size, callback) {
  const options = {
    filename: imageName,
    count: 1,
    folder
  }

  if (!callback) {
    callback = size
  } else {
    options.size = size
  }

  ffmpeg(videoPath)
    .on('error', callback)
    .on('end', function () {
      callback(null, imageName)
    })
    .thumbnail(options)
}
<|endoftext|>


<commit_before>const passport = require('passport');
const SlackStrategy = require('passport-slack').Strategy;
const config = require('../config').slack;

module.exports = (User) => {
    passport.serializeUser((user, done) => {
        done(null, user._id);
    });

    // Get user by ID
    passport.deserializeUser((id, done) => {
        User.findById(id)
        .exec()
        .then(user => {
            done(null, user);
        })
        .catch(done);
    });

    passport.use(new SlackStrategy(config,
        (req, token, refreshToken, profile, done) => {
            const email = profile.user.email;

            // Find user
            User.findOne({ slackId: profile.slackId })
            .exec()
            .then(user => {
                if (!user) {
                    console.log(`Creating user with email ${email}...`);
                    user = new User({
                        slackId: profile.id,
                        email,
                        name: {
                            full: profile.user.name,
                            first: profile.user.name.split(' ')[0],
                            last: profile.user.name.split(' ')[1],
                        },
                        profileImageName: profile.user.image_192,
                        verified: false,
                        admin: false
                    });

                    user.save();
                    sendEmail(user.email, 'Welcome to Kids Tales', 'newUser', { firstName: user.name.first });
                }
                console.log(`Logging in ${email}...`)
                return done(null, user);
            })
            .catch(err => {
                console.log(`Failed to login user with email ${email}.`)
                return done(err, false, { message: 'There was an error logging you in, please try again later.' });
            });
        }
    ));

    return passport;
};
<commit_msg>Restrict slack auth to team<commit_after>const passport = require('passport');
const SlackStrategy = require('passport-slack').Strategy;
const config = require('../config').slack;

module.exports = (User) => {
    passport.serializeUser((user, done) => {
        done(null, user._id);
    });

    // Get user by ID
    passport.deserializeUser((id, done) => {
        User.findById(id)
        .exec()
        .then(user => {
            done(null, user);
        })
        .catch(done);
    });

    passport.use(new SlackStrategy(config,
        (req, token, refreshToken, profile, done) => {
            console.log(profile);

            // Make sure user is in Kids Tales Slack team
            if (profile.team.id !== config.teamID) return done(null, false, { message: 'You must be on the Kids Tales Slack team!' });

            const email = profile.user.email;

            // Find user
            User.findOne({ slackId: profile.slackId })
            .exec()
            .then(user => {
                if (!user) {
                    console.log(`Creating user with email ${email}...`);
                    user = new User({
                        slackId: profile.id,
                        email,
                        name: {
                            full: profile.user.name,
                            first: profile.user.name.split(' ')[0],
                            last: profile.user.name.split(' ')[1],
                        },
                        profileImageName: profile.user.image_192,
                        verified: false,
                        admin: false
                    });

                    user.save();
                    sendEmail(user.email, 'Welcome to Kids Tales', 'newUser', { firstName: user.name.first });
                }
                console.log(`Logging in ${email}...`)
                return done(null, user);
            })
            .catch(err => {
                console.log(`Failed to login user with email ${email}.`)
                return done(err, false, { message: 'There was an error logging you in, please try again later.' });
            });
        }
    ));

    return passport;
};
<|endoftext|>


<commit_before>//
// Copyright (c) Autodesk, Inc. All rights reserved
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// by Cyrille Fauvel - Autodesk Developer Network (ADN)
//
var express =require ('express') ;
var mqtt =require ('mqtt') ;
var moment =require ('moment') ;
var sendMail =require ('./sendMail') ;
var config =require ('./config') ;

var astro =require (__dirname + '/../www/data/mqtt.json') ;
var url =(process.env.NODE_ENV === 'production' ? 'wss' : 'ws') + '://' + astro.mqtt.host + ':' + astro.mqtt.port ;

var client =mqtt.connect (url) ;
//var astroBeginTime =moment ().subtract (7, 'days') ;
var astroLastTime =moment () ;
var astroInterval =resetAstroInterval () ;

client.on ('connect', function () {
	client.subscribe (astro.topic + '/#') ;
}) ;

client.on ('error', function (err) {
	console.log (err) ;
	client.end () ;

	sendMail ({
		'from': config.MJ_ACCOUNT,
		'replyTo': config.MJ_ACCOUNT,
		'to': config.mailTo,
		'subject': 'pier9.autodesk.io - MQTT error',
		'html': err
	}) ;
	clearInterval (astroInterval) ;
	astroInterval =null ;

}) ;

client.on ('message', function (topic, message) { // message is Buffer
	//console.log (message.toString ()) ;
	astroLastTime =moment () ;
	bMsgSent =false ;
}) ;

function resetAstroInterval () {
	astroLastTime =moment () ;
	if ( astroInterval )
		clearInterval (astroInterval) ;
	astroInterval =setInterval (astroIntervalElapsed, moment.duration (1, 'hours').asMilliseconds ()) ;
	//astroInterval =setInterval (astroIntervalElapsed, 2000) ;
	return (astroInterval) ;
}

var bMsgSent =false ;
function astroIntervalElapsed () {
	var diff =moment ().diff (moment (astroLastTime)) ;
	var minutes =moment.duration (diff).asMinutes () ;
	if ( minutes > 30 && bMsgSent === false ) {
		bMsgSent =true ;
		sendMail ({
			'from': config.MJ_ACCOUNT,
			'replyTo': config.MJ_ACCOUNT,
			'to': config.mailTo,
			'subject': 'pier9.autodesk.io - MQTT no message for the last ' + minutes + ' minutes',
			'html': 'last message was received at ' + moment (astroLastTime).calendar ()
		}) ;
	}
}

module.exports =client ;
<commit_msg>mqtt monitor<commit_after>//
// Copyright (c) Autodesk, Inc. All rights reserved
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE.  AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// by Cyrille Fauvel - Autodesk Developer Network (ADN)
//
var express =require ('express') ;
var mqtt =require ('mqtt') ;
var moment =require ('moment') ;
var sendMail =require ('./sendMail') ;
var config =require ('./config') ;

var astro =require (__dirname + '/../www/data/mqtt.json') ;
//var url =(process.env.NODE_ENV === 'production' ? 'wss' : 'ws') + '://' + astro.mqtt.host + ':' + astro.mqtt.port ;
var url ='ws://' + astro.mqtt.host + ':' + astro.mqtt.port ;

var client =mqtt.connect (url) ;
//var astroBeginTime =moment ().subtract (7, 'days') ;
var astroLastTime =moment () ;
var astroInterval =resetAstroInterval () ;

client.on ('connect', function () {
	client.subscribe (astro.topic + '/#') ;
}) ;

client.on ('error', function (err) {
	console.log (err) ;
	client.end () ;

	sendMail ({
		'from': config.MJ_ACCOUNT,
		'replyTo': config.MJ_ACCOUNT,
		'to': config.mailTo,
		'subject': 'pier9.autodesk.io - MQTT error',
		'html': err
	}) ;
	clearInterval (astroInterval) ;
	astroInterval =null ;

}) ;

client.on ('message', function (topic, message) { // message is Buffer
	//console.log (message.toString ()) ;
	astroLastTime =moment () ;
	bMsgSent =false ;
}) ;

function resetAstroInterval () {
	astroLastTime =moment () ;
	if ( astroInterval )
		clearInterval (astroInterval) ;
	astroInterval =setInterval (astroIntervalElapsed, moment.duration (1, 'hours').asMilliseconds ()) ;
	//astroInterval =setInterval (astroIntervalElapsed, 2000) ;
	return (astroInterval) ;
}

var bMsgSent =false ;
function astroIntervalElapsed () {
	var diff =moment ().diff (moment (astroLastTime)) ;
	var minutes =moment.duration (diff).asMinutes () ;
	if ( minutes > 30 && bMsgSent === false ) {
		bMsgSent =true ;
		sendMail ({
			'from': config.MJ_ACCOUNT,
			'replyTo': config.MJ_ACCOUNT,
			'to': config.mailTo,
			'subject': 'pier9.autodesk.io - MQTT no message for the last ' + minutes + ' minutes',
			'html': 'last message was received at ' + moment (astroLastTime).calendar ()
		}) ;
	}
}

module.exports =client ;
<|endoftext|>