<commit_before>/* eslint-disable max-nested-callbacks,no-unused-expressions,handle-callback-err */
'use strict';

var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var rimraf = require('rimraf');
var moment = require('moment');
var winston = require('winston');
var MemoryStream = require('./memory-stream');
var randomString = require('./random-string');
var DailyRotateFile = require('../daily-rotate-file');

function sendLogItem(transport, level, message, meta, cb) { // eslint-disable-line max-params
    transport.on('logged', function () {
        if (cb) {
            cb(null, true);
        }
    });

    const info = { level: level, message: message };
    transport.log(winston.format.json().transform(info));
}

describe('winston/transports/daily-rotate-file', function () {
    beforeEach(function () {
        this.stream = new MemoryStream();
        this.transport = new DailyRotateFile({
            stream: this.stream
        });
    });

    it('should have the proper methods defined', function () {
        var transport = new DailyRotateFile({stream: new MemoryStream()});
        expect(transport).to.be.instanceOf(DailyRotateFile);
        expect(transport).to.respondTo('log');
        expect(transport).to.respondTo('query');
    });

    it('should not allow invalid characters in the filename', function () {
        expect(function () {
            // eslint-disable-next-line no-new
            new DailyRotateFile({
                filename: 'test\0log.log'
            });
        }).to.throw();
    });

    it('should not allow invalid characters in the dirname', function () {
        expect(function () {
            // eslint-disable-next-line no-new
            new DailyRotateFile({
                dirname: 'C:\\application<logs>',
                filename: 'test_%DATE%.log'
            });
        }).to.throw();
    });

    it('should write to the stream', function (done) {
        var self = this;
        sendLogItem(this.transport, 'info', 'this message should write to the stream', {}, function (err, logged) {
            expect(err).to.be.null;
            expect(logged).to.be.true;
            var logEntry = JSON.parse(self.stream.toString());
            expect(logEntry.level).to.equal('info');
            expect(logEntry.message).to.equal('this message should write to the stream');
            done();
        });
    });

    describe('when passed metadata', function () {
        var circular = {};
        circular.metadata = circular;

        var params = {
            no: {},
            object: {metadata: true},
            primitive: 'metadata',
            circular: circular
        };

        Object.keys(params).forEach(function (param) {
            it('should accept log messages with ' + param + ' metadata', function (done) {
                sendLogItem(this.transport, 'info', 'test log message', params[param], function (err, logged) {
                    expect(err).to.be.null;
                    expect(logged).to.be.true;
                    // TODO parse the metadata value to make sure its set properly
                    done();
                });
            });
        });
    });

    describe('when using a filename or dirname', function () {
        var logDir = path.join(__dirname, 'logs');
        var now = moment().utc().format('YYYY-MM-DD-HH');
        var filename = path.join(logDir, 'application-' + now + '.testlog');
        var options = {
            json: true,
            dirname: logDir,
            filename: 'application-%DATE%',
            datePattern: 'YYYY-MM-DD-HH',
            utc: true,
            extension: '.testlog'
        };

        beforeEach(function (done) {
            var self = this;
            rimraf(logDir, function () {
                self.transport = new DailyRotateFile(options);
                done();
            });
        });

        it('should write to the file', function (done) {
            this.transport.on('finish', function () {
                var logEntries = fs.readFileSync(filename).toString().split('\n').slice(0, -1);
                expect(logEntries.length).to.equal(1);

                var logEntry = JSON.parse(logEntries[0]);
                expect(logEntry.level).to.equal('info');
                expect(logEntry.message).to.equal('this message should write to the file');
                done();
            });

            sendLogItem(this.transport, 'info', 'this message should write to the file', {}, function (err, logged) {
                expect(err).to.be.null;
                expect(logged).to.be.true;
            });

            this.transport.close();
        });

        it('should not allow the stream to be set', function () {
            var opts = Object.assign({}, options);
            opts.stream = new MemoryStream();
            expect(function () {
                var transport = new DailyRotateFile(opts);
                expect(transport).to.not.be.null;
            }).to.throw();
        });

        it('should raise the new event for a new log file', function (done) {
            this.transport.on('new', function (newFile) {
                expect(newFile).to.equal(filename);
                done();
            });

            sendLogItem(this.transport, 'info', 'this message should write to the file');
            this.transport.close();
        });

        it('should raise the logRemoved event when pruning old log files', function (done) {
            var opts = Object.assign({}, options);
            opts.maxSize = '1k';
            opts.maxFiles = 1;

            this.transport = new DailyRotateFile(opts);

            this.transport.on('logRemoved', function (removedFilename) {
                expect(removedFilename).to.equal(filename);
                done();
            });

            sendLogItem(this.transport, 'info', randomString(1056));
            sendLogItem(this.transport, 'info', randomString(1056));
            this.transport.close();
        });

        describe('when setting zippedArchive', function () {
            it('should archive the log after rotating', function (done) {
                var opts = Object.assign({}, options);
                opts.zippedArchive = true;
                opts.maxSize = '1k';

                this.transport = new DailyRotateFile(opts);

                this.transport.on('finish', function () {
                    fs.readdir(logDir, function (err, files) {
                        expect(files.filter(function (file) {
                            return path.extname(file) === '.gz';
                        }).length).to.equal(1);
                        done();
                    });
                });
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                this.transport.close();
            });
        });

        describe('query', function () {
            it('should call callback when no files are present', function () {
                this.transport.query(function (err, results) {
                    expect(results).to.not.be.null;
                    expect(results.length).to.equal(0);
                });
            });

            it('should raise error when calling with stream', function () {
                expect(function () {
                    var transport = new DailyRotateFile({stream: new MemoryStream()});
                    transport.query(null);
                }).to.throw();
            });

            it('should raise error when calling with json set to false', function () {
                expect(function () {
                    var opts = Object.assign({}, options);
                    opts.json = false;
                    var transport = new DailyRotateFile(opts);
                    transport.query(null);
                }).to.throw();
            });

            it('should return log entries that match the query', function (done) {
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));

                var self = this;
                this.transport.on('finish', function () {
                    self.transport.query(function (err, results) {
                        expect(results).to.not.be.null;
                        expect(results.length).to.equal(4);
                        done();
                    });
                });

                this.transport.close();
            });

            it('should search within archived files', function (done) {
                var opts = Object.assign({}, options);
                opts.zippedArchive = true;
                opts.maxSize = '1k';

                this.transport = new DailyRotateFile(opts);

                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));

                var self = this;

                self.transport.on('archive', function () {
                    self.transport.query(function (err, results) {
                        expect(results).to.not.be.null;
                        expect(results.length).to.equal(2);
                        done();
                    });
                });
            });
        });
    });
});
<commit_msg>remove unnecessary unit tests<commit_after>/* eslint-disable max-nested-callbacks,no-unused-expressions,handle-callback-err */
'use strict';

var fs = require('fs');
var path = require('path');
var expect = require('chai').expect;
var rimraf = require('rimraf');
var moment = require('moment');
var winston = require('winston');
var MemoryStream = require('./memory-stream');
var randomString = require('./random-string');
var DailyRotateFile = require('../daily-rotate-file');

function sendLogItem(transport, level, message, meta, cb) { // eslint-disable-line max-params
    transport.on('logged', function () {
        if (cb) {
            cb(null, true);
        }
    });

    const info = { level: level, message: message };
    transport.log(winston.format.json().transform(info));
}

describe('winston/transports/daily-rotate-file', function () {
    beforeEach(function () {
        this.stream = new MemoryStream();
        this.transport = new DailyRotateFile({
            stream: this.stream
        });
    });

    it('should have the proper methods defined', function () {
        var transport = new DailyRotateFile({stream: new MemoryStream()});
        expect(transport).to.be.instanceOf(DailyRotateFile);
        expect(transport).to.respondTo('log');
        expect(transport).to.respondTo('query');
    });

    it('should not allow invalid characters in the filename', function () {
        expect(function () {
            // eslint-disable-next-line no-new
            new DailyRotateFile({
                filename: 'test\0log.log'
            });
        }).to.throw();
    });

    it('should not allow invalid characters in the dirname', function () {
        expect(function () {
            // eslint-disable-next-line no-new
            new DailyRotateFile({
                dirname: 'C:\\application<logs>',
                filename: 'test_%DATE%.log'
            });
        }).to.throw();
    });

    it('should write to the stream', function (done) {
        var self = this;
        sendLogItem(this.transport, 'info', 'this message should write to the stream', {}, function (err, logged) {
            expect(err).to.be.null;
            expect(logged).to.be.true;
            var logEntry = JSON.parse(self.stream.toString());
            expect(logEntry.level).to.equal('info');
            expect(logEntry.message).to.equal('this message should write to the stream');
            done();
        });
    });

    describe('when using a filename or dirname', function () {
        var logDir = path.join(__dirname, 'logs');
        var now = moment().utc().format('YYYY-MM-DD-HH');
        var filename = path.join(logDir, 'application-' + now + '.testlog');
        var options = {
            json: true,
            dirname: logDir,
            filename: 'application-%DATE%',
            datePattern: 'YYYY-MM-DD-HH',
            utc: true,
            extension: '.testlog'
        };

        beforeEach(function (done) {
            var self = this;
            rimraf(logDir, function () {
                self.transport = new DailyRotateFile(options);
                done();
            });
        });

        it('should write to the file', function (done) {
            this.transport.on('finish', function () {
                var logEntries = fs.readFileSync(filename).toString().split('\n').slice(0, -1);
                expect(logEntries.length).to.equal(1);

                var logEntry = JSON.parse(logEntries[0]);
                expect(logEntry.level).to.equal('info');
                expect(logEntry.message).to.equal('this message should write to the file');
                done();
            });

            sendLogItem(this.transport, 'info', 'this message should write to the file', {}, function (err, logged) {
                expect(err).to.be.null;
                expect(logged).to.be.true;
            });

            this.transport.close();
        });

        it('should not allow the stream to be set', function () {
            var opts = Object.assign({}, options);
            opts.stream = new MemoryStream();
            expect(function () {
                var transport = new DailyRotateFile(opts);
                expect(transport).to.not.be.null;
            }).to.throw();
        });

        it('should raise the new event for a new log file', function (done) {
            this.transport.on('new', function (newFile) {
                expect(newFile).to.equal(filename);
                done();
            });

            sendLogItem(this.transport, 'info', 'this message should write to the file');
            this.transport.close();
        });

        it('should raise the logRemoved event when pruning old log files', function (done) {
            var opts = Object.assign({}, options);
            opts.maxSize = '1k';
            opts.maxFiles = 1;

            this.transport = new DailyRotateFile(opts);

            this.transport.on('logRemoved', function (removedFilename) {
                expect(removedFilename).to.equal(filename);
                done();
            });

            sendLogItem(this.transport, 'info', randomString(1056));
            sendLogItem(this.transport, 'info', randomString(1056));
            this.transport.close();
        });

        describe('when setting zippedArchive', function () {
            it('should archive the log after rotating', function (done) {
                var opts = Object.assign({}, options);
                opts.zippedArchive = true;
                opts.maxSize = '1k';

                this.transport = new DailyRotateFile(opts);

                this.transport.on('finish', function () {
                    fs.readdir(logDir, function (err, files) {
                        expect(files.filter(function (file) {
                            return path.extname(file) === '.gz';
                        }).length).to.equal(1);
                        done();
                    });
                });
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                this.transport.close();
            });
        });

        describe('query', function () {
            it('should call callback when no files are present', function () {
                this.transport.query(function (err, results) {
                    expect(results).to.not.be.null;
                    expect(results.length).to.equal(0);
                });
            });

            it('should raise error when calling with stream', function () {
                expect(function () {
                    var transport = new DailyRotateFile({stream: new MemoryStream()});
                    transport.query(null);
                }).to.throw();
            });

            it('should raise error when calling with json set to false', function () {
                expect(function () {
                    var opts = Object.assign({}, options);
                    opts.json = false;
                    var transport = new DailyRotateFile(opts);
                    transport.query(null);
                }).to.throw();
            });

            it('should return log entries that match the query', function (done) {
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));

                var self = this;
                this.transport.on('finish', function () {
                    self.transport.query(function (err, results) {
                        expect(results).to.not.be.null;
                        expect(results.length).to.equal(4);
                        done();
                    });
                });

                this.transport.close();
            });

            it('should search within archived files', function (done) {
                var opts = Object.assign({}, options);
                opts.zippedArchive = true;
                opts.maxSize = '1k';

                this.transport = new DailyRotateFile(opts);

                sendLogItem(this.transport, 'info', randomString(1056));
                sendLogItem(this.transport, 'info', randomString(1056));

                var self = this;

                self.transport.on('archive', function () {
                    self.transport.query(function (err, results) {
                        expect(results).to.not.be.null;
                        expect(results.length).to.equal(2);
                        done();
                    });
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var sinonChai = require('sinon-chai');

var datastored = require('../..');
var Model = require('../../lib/model').Model;

chai.should();
chai.use(sinonChai);

describe('Model', function() {

  var options = {
    table: 'models',
    schema: {
      id: {
        primary: true,
        type: 'string'
      }
    }
  };

  beforeEach(function() {
    this.orm = datastored.createOrm({
      redisClient: true,
      cassandraClient: true
    });
    this.redis = this.orm.datastores.redis;
    this.cassandra = this.orm.datastores.cassandra;
  });

  it('should set `methods` and `staticMethods`', function() {
    var ModelClass = this.orm.createModel('Model', _.extend({}, options, {
      methods: {
        getInstanceThis: function() {return this;}
      },
      staticMethods: {
        getStaticThis: function() {return this;}
      }
    }));

    // Test static methods.
    ModelClass.getStaticThis().should.deep.equal(ModelClass);

    // Test instance methods.
    var model = ModelClass.create({});
    model.getInstanceThis().should.deep.equal(model);
  });

  describe('constructor', function() {

    it('should fail when any required option is not defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.omit(options, ['table']));
      }).should.throw('attribute `table` is not defined');

      (function() {
        orm.createModel('Model', _.omit(options, ['schema']));
      }).should.throw('attribute `schema` is not defined');
    });

    it('should fail if a primary key attribute is not defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.extend({}, options, {
          schema: {}
        }));
      }).should.throw('a primary key attribute is required');
    });

    it('should fail if multiple primary key attributes are ' +
      'defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', {
          table: 'models',
          schema: {
            id1: {primary: true},
            id2: {primary: true}
          }
        });
      }).should.throw('only one primary key attribute can be defined per ' +
        'model');
    });

    it('should fail if an attribute is defined without a type', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.extend({}, options, {
          schema: {invalidAttribute: {}}
        }));
      }).should.throw('a primary key attribute is required');
    });
  });

  describe('#Model.create()', function() {

    beforeEach(function() {
      sinon.spy(Model.prototype, 'set');
    });

    afterEach(function() {
      Model.prototype.set.restore();
    });

    it('should initially set the attributes', function() {
      var model = this.orm.createModel('Model', options).create({id: 'foo'});
      model.set.should.have.been.calledWith({id: 'foo'});
    });
  });

  describe('#Model.get()', function() {

    it('should use the primary key attribute', function() {
      var model = this.orm.createModel('Model', options).get('foo');
      model.get('id').should.equal('foo');
    });
  });

  describe('#Model.find()', function() {

    beforeEach(function() {
      this.model = this.orm.createModel('Model', _.merge({}, options, {
        schema: {
          indexed: {
            index: true,
            type: 'string'
          },
          cachedAndIndexed: {
            index: true,
            cache: true,
            type: 'string'
          },
          name: {
            type: 'string'
          }
        }
      }));

      sinon.stub(this.model.prototype, 'transform', function(attributes) {
        _.object(_.map(attributes, function(value, name) {
          return [name, 'transformed'];
        }))
      });
    });

    afterEach(function() {
      this.model.transform.restore();
    });

    it('should only allow indexed attributes in the query', function() {
      var model = this.model;

      (function() {
        model.find({name: 'foo'});
      }).should.throw('attribute `name` is not indexed');

      (function() {
        model.find({id: 'foo'});
      }).should.throw('attribute `id` is not indexed');
    });

    it.only('should transform the query by default', function() {
      sinon.stub(this.model, 'fetchFromDatastores', function(onDatastore) {
        var datastore = sinon.mock({
          find: function(query) {
            query.should.deep.equal({indexed: 'transformed'});
            done();
          }
        });
        onDatastore(datastore);
      });
      this.model.find({indexed: 'foo'});
    });

    it('should not transform the query if requested', function(done) {
      sinon.stub(this.model, 'fetchFromDatastores', function(onDatastore) {
        var datastore = sinon.mock({
          find: function(query) {
            query.should.deep.equal({indexed: 'foo'});
            done();
          }
        });
        onDatastore(datastore);
      });
      this.model.find({indexed: 'foo'}, false);
    });

    it('should use the datastores correctly', function() {
      // Should pass whether or not to use the cache.

      sinon.stub(this.model, 'fetchFromDatastores', function(cb) {
        //
      });

      function noop(pk, options, cb) {cb();}

      beforeEach(function() {
        sinon.stub(this.redis, 'destroy', noop);
        sinon.stub(this.cassandra, 'destroy', noop);
      });

      this.model.fetchFromDatastores.restore();

      sinon.stub(this.model, 'fetchFromDatastores', function(cb) {
        cb('error');
      });

      this.model.find({indexed: 'foo'}, function(err) {
        err.should.equal('error');
        done();
      });

      this.model.fetchFromDatastores.restore();
    });

    it('should callback with `null` if no model was found', function() {

    });

    xit('should find a single model through the datastore', function() {
      // if index attribute is cached: (check redis for the index)
      // if fail, check cassandra
      // if fatal err (return fatal err)
      // else if none returned, return null

      // this.redis.find
      this.model.find({indexed: 'foo'}, function(err, model) {
        // check input transform chain
        model.get('id').should.equal();
      });

      // Check errors.
      this.model.find({indexed: 'foo'}, function(err, model) {
        err.should.equal('error');
      });

      this.model.find({indexed: 'foo'}, false, function(err, model) {
        model.get('id').should.equal();
      });

      // similar to fetch
      // redis first if ()
      // [model options, attribute map] (orm options are already given at init)
      //   should give -> id or null
      // cassandra
      // Check that the pk is set. when datastore returns id, and calls back null
      // if no model was found.
      //
      // datastore access needs different strategies. Each strategy needs to be
      // tested.
    });
  });

  describe('#transform()', function() {

    it('should transform with chains in the right order', function(done) {
      var model = this.orm.createModel('Model', _.extend({}, options, {
        transforms: [{
          input: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          output: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          fetch: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          save: function(attributes, model, cb) {
            attributes.foo += '1';
            cb(null, attributes);
          }
        }, {
          input: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          output: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          fetch: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          save: function(attributes, model, cb) {
            attributes.foo += '2';
            cb(null, attributes);
          }
        }]
      })).create();

      model.transform({foo: '0'}, 'input').foo.should.equal('012');
      model.transform({foo: '0'}, 'output').foo.should.equal('021');
      model.transform({foo: '0'}, 'fetch').foo.should.equal('012');
      model.transform({foo: '0'}, 'save', function(err, attributes) {
        attributes.foo.should.equal('021');
        done();
      });
    });
  });

  describe('#set()', function() {

    beforeEach(function() {
      sinon.stub(Model.prototype, 'transform');
    });

    afterEach(function() {
      Model.prototype.transform.restore();
    });

    it('should accept a single attribute', function() {
      var model = this.orm.createModel('Model', options).create();
      model.set('id', 'foo', false);
      model.get('id').should.equal('foo');
    });

    it('should accept multiple attributes', function() {
      var modelOptions = _.merge({}, options, {schema: {
        foo: {type: 'string'}
      }});
      var model = this.orm.createModel('Model', modelOptions).create();

      model.transform.returns({id: 'transformed', foo: 'transformed'});
      model.set({id: 'foo', foo: 'bar'});
      model.get(['id', 'foo']).should.deep.equal({
        id: 'transformed', foo: 'transformed'
      });
      model.transform.should.have.been.calledWith({
        id: 'foo', foo: 'bar'
      }, 'input');

      model.transform.reset();
      model.set({id: 'foo', foo: 'bar'}, false);
      model.get(['id', 'foo']).should.deep.equal({id: 'foo', foo: 'bar'});
      model.transform.should.not.have.been.called;
    });

    it('should omit attributes that are not defined in the ' +
      'schema', function() {
      var model = this.orm.createModel('Model', options).create();
      model.set({id: 'foo', invalid: 'bar'});
      // Ensure that transform chain does not receive the undefined attribute.
      model.transform.should.have.been.calledWith({id: 'foo'});
    });

    xit('should not change immutable attributes', function() {

    });
  });

  describe('#get()', function() {

    it('should fail when getting an undefined attribute', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', options).create().get(['id', 'invalid'])
      }).should.throw('invalid attribute `invalid`');
    });
  });

  xdescribe('#show()', function() {

    it('should only return the attributes included in the scope', function() {

    });

    it('should use the output transform chain', function() {

    });

    xit('should hide hidden attributes', function() {

    });
  });

  xdescribe('#save()', function() {

    it('should save changed attributes to both datastores', function() {

    });

    it('should save to cassandra before saving to redis', function() {

    });

    it('should only fail when saving to the cassandra datastore ' +
      'fails', function() {
      // Redis failure should not stop it.
    });

    // - caching
    // - relations
    // - permissions (mixin)

    xit('should validate attributes', function() {

    });
  });

  xdescribe('#fetch()', function() {

    it('should fail if the primary key attribute is not set', function() {

    });

    // scopes
    // fetch from cache if set
    // when to fail

    it('should fetch the model from', function() {

    });
  });

  describe('#destroy()', function() {

    function noop(pk, options, cb) {cb();}

    beforeEach(function() {
      sinon.stub(this.redis, 'destroy', noop);
      sinon.stub(this.cassandra, 'destroy', noop);
    });

    afterEach(function() {
      this.redis.destroy.restore();
      this.cassandra.destroy.restore();
    });

    xit('should destroy the model from both datastores', function(done) {
      var self = this;
      var model = this.orm.createModel('Model', _.extend({}, options, {
        cachedVariables: true
      })).get('foo');
      model.destroy(function() {
        var testOptions = {schema: {id: {
          primary: true, type: 'string'
        }}, table: 'models'};
        self.redis.destroy.should.have.been.calledWith(
          'foo', testOptions, sinon.match.func);
        self.cassandra.destroy.should.have.been.calledWith(
          'foo', testOptions, sinon.match.func);
        done();
      });
    });

    it('should not attempt to destroy from the redis datastore if all ' +
      'attributes are uncached', function() {
      var self = this;
      var model = this.orm.createModel('Model', options).get('foo');
      model.destroy(function() {
        self.redis.destroy.should.not.have.been.called;
        self.cassandra.destroy.should.have.been.called;
      });
    });

    it('should fail if destroying from any datastore fails', function(done) {
      var self = this;
      this.redis.destroy.restore();
      sinon.stub(this.redis, 'destroy', function(pk, options, cb) {
        cb('error');
      });
      var model = this.orm.createModel('Model', options).get('foo');
      model.destroy(function(err) {
        self.cassandra.destroy.should.not.have.been.called;
        err.should.equal('error');
        done();
      });
    });
  });
});
<commit_msg>Added test for resetting changed attributes<commit_after>var _ = require('lodash');
var chai = require('chai');
var sinon = require('sinon');
var sinonChai = require('sinon-chai');

var datastored = require('../..');
var Model = require('../../lib/model').Model;

chai.should();
chai.use(sinonChai);

describe('Model', function() {

  var options = {
    table: 'models',
    schema: {
      id: {
        primary: true,
        type: 'string'
      }
    }
  };

  beforeEach(function() {
    this.orm = datastored.createOrm({
      redisClient: true,
      cassandraClient: true
    });
    this.redis = this.orm.datastores.redis;
    this.cassandra = this.orm.datastores.cassandra;
  });

  it('should set `methods` and `staticMethods`', function() {
    var ModelClass = this.orm.createModel('Model', _.extend({}, options, {
      methods: {
        getInstanceThis: function() {return this;}
      },
      staticMethods: {
        getStaticThis: function() {return this;}
      }
    }));

    // Test static methods.
    ModelClass.getStaticThis().should.deep.equal(ModelClass);

    // Test instance methods.
    var model = ModelClass.create({});
    model.getInstanceThis().should.deep.equal(model);
  });

  describe('constructor', function() {

    it('should fail when any required option is not defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.omit(options, ['table']));
      }).should.throw('attribute `table` is not defined');

      (function() {
        orm.createModel('Model', _.omit(options, ['schema']));
      }).should.throw('attribute `schema` is not defined');
    });

    it('should fail if a primary key attribute is not defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.extend({}, options, {
          schema: {}
        }));
      }).should.throw('a primary key attribute is required');
    });

    it('should fail if multiple primary key attributes are ' +
      'defined', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', {
          table: 'models',
          schema: {
            id1: {primary: true},
            id2: {primary: true}
          }
        });
      }).should.throw('only one primary key attribute can be defined per ' +
        'model');
    });

    it('should fail if an attribute is defined without a type', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', _.extend({}, options, {
          schema: {invalidAttribute: {}}
        }));
      }).should.throw('a primary key attribute is required');
    });
  });

  describe('#Model.create()', function() {

    beforeEach(function() {
      sinon.spy(Model.prototype, 'set');
    });

    afterEach(function() {
      Model.prototype.set.restore();
    });

    it('should initially set the attributes', function() {
      var model = this.orm.createModel('Model', options).create({id: 'foo'});
      model.set.should.have.been.calledWith({id: 'foo'});
    });
  });

  describe('#Model.get()', function() {

    it('should use the primary key attribute', function() {
      var model = this.orm.createModel('Model', options).get('foo');
      model.get('id').should.equal('foo');
    });
  });

  describe('#Model.find()', function() {

    beforeEach(function() {
      this.model = this.orm.createModel('Model', _.merge({}, options, {
        schema: {
          indexed: {
            index: true,
            type: 'string'
          },
          cachedAndIndexed: {
            index: true,
            cache: true,
            type: 'string'
          },
          name: {
            type: 'string'
          }
        }
      }));

      sinon.stub(this.model.prototype, 'transform', function(attributes) {
        _.object(_.map(attributes, function(value, name) {
          return [name, 'transformed'];
        }))
      });
    });

    afterEach(function() {
      this.model.transform.restore();
    });

    it('should only allow indexed attributes in the query', function() {
      var model = this.model;

      (function() {
        model.find({name: 'foo'});
      }).should.throw('attribute `name` is not indexed');

      (function() {
        model.find({id: 'foo'});
      }).should.throw('attribute `id` is not indexed');
    });

    it.only('should transform the query by default', function() {
      sinon.stub(this.model, 'fetchFromDatastores', function(onDatastore) {
        var datastore = sinon.mock({
          find: function(query) {
            query.should.deep.equal({indexed: 'transformed'});
            done();
          }
        });
        onDatastore(datastore);
      });
      this.model.find({indexed: 'foo'});
    });

    it('should not transform the query if requested', function(done) {
      sinon.stub(this.model, 'fetchFromDatastores', function(onDatastore) {
        var datastore = sinon.mock({
          find: function(query) {
            query.should.deep.equal({indexed: 'foo'});
            done();
          }
        });
        onDatastore(datastore);
      });
      this.model.find({indexed: 'foo'}, false);
    });

    it('should use the datastores correctly', function() {
      // Should pass whether or not to use the cache.

      sinon.stub(this.model, 'fetchFromDatastores', function(cb) {
        //
      });

      function noop(pk, options, cb) {cb();}

      beforeEach(function() {
        sinon.stub(this.redis, 'destroy', noop);
        sinon.stub(this.cassandra, 'destroy', noop);
      });

      this.model.fetchFromDatastores.restore();

      sinon.stub(this.model, 'fetchFromDatastores', function(cb) {
        cb('error');
      });

      this.model.find({indexed: 'foo'}, function(err) {
        err.should.equal('error');
        done();
      });

      this.model.fetchFromDatastores.restore();
    });

    it('should callback with `null` if no model was found', function() {

    });

    xit('should find a single model through the datastore', function() {
      // if index attribute is cached: (check redis for the index)
      // if fail, check cassandra
      // if fatal err (return fatal err)
      // else if none returned, return null

      // this.redis.find
      this.model.find({indexed: 'foo'}, function(err, model) {
        // check input transform chain
        model.get('id').should.equal();
      });

      // Check errors.
      this.model.find({indexed: 'foo'}, function(err, model) {
        err.should.equal('error');
      });

      this.model.find({indexed: 'foo'}, false, function(err, model) {
        model.get('id').should.equal();
      });

      // similar to fetch
      // redis first if ()
      // [model options, attribute map] (orm options are already given at init)
      //   should give -> id or null
      // cassandra
      // Check that the pk is set. when datastore returns id, and calls back null
      // if no model was found.
      //
      // datastore access needs different strategies. Each strategy needs to be
      // tested.
    });
  });

  describe('#transform()', function() {

    it('should transform with chains in the right order', function(done) {
      var model = this.orm.createModel('Model', _.extend({}, options, {
        transforms: [{
          input: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          output: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          fetch: function(attributes, model) {
            attributes.foo += '1';
            return attributes;
          },
          save: function(attributes, model, cb) {
            attributes.foo += '1';
            cb(null, attributes);
          }
        }, {
          input: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          output: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          fetch: function(attributes, model) {
            attributes.foo += '2';
            return attributes;
          },
          save: function(attributes, model, cb) {
            attributes.foo += '2';
            cb(null, attributes);
          }
        }]
      })).create();

      model.transform({foo: '0'}, 'input').foo.should.equal('012');
      model.transform({foo: '0'}, 'output').foo.should.equal('021');
      model.transform({foo: '0'}, 'fetch').foo.should.equal('012');
      model.transform({foo: '0'}, 'save', function(err, attributes) {
        attributes.foo.should.equal('021');
        done();
      });
    });
  });

  describe('#set()', function() {

    beforeEach(function() {
      sinon.stub(Model.prototype, 'transform');
    });

    afterEach(function() {
      Model.prototype.transform.restore();
    });

    it('should accept a single attribute', function() {
      var model = this.orm.createModel('Model', options).create();
      model.set('id', 'foo', false);
      model.get('id').should.equal('foo');
    });

    it('should accept multiple attributes', function() {
      var modelOptions = _.merge({}, options, {schema: {
        foo: {type: 'string'}
      }});
      var model = this.orm.createModel('Model', modelOptions).create();

      model.transform.returns({id: 'transformed', foo: 'transformed'});
      model.set({id: 'foo', foo: 'bar'});
      model.get(['id', 'foo']).should.deep.equal({
        id: 'transformed', foo: 'transformed'
      });
      model.transform.should.have.been.calledWith({
        id: 'foo', foo: 'bar'
      }, 'input');

      model.transform.reset();
      model.set({id: 'foo', foo: 'bar'}, false);
      model.get(['id', 'foo']).should.deep.equal({id: 'foo', foo: 'bar'});
      model.transform.should.not.have.been.called;
    });

    it('should omit attributes that are not defined in the ' +
      'schema', function() {
      var model = this.orm.createModel('Model', options).create();
      model.set({id: 'foo', invalid: 'bar'});
      // Ensure that transform chain does not receive the undefined attribute.
      model.transform.should.have.been.calledWith({id: 'foo'});
    });

    xit('should not change immutable attributes', function() {

    });

    xit('should set `changedAttributes`', function() {

    });
  });

  describe('#get()', function() {

    it('should fail when getting an undefined attribute', function() {
      var orm = this.orm;

      (function() {
        orm.createModel('Model', options).create().get(['id', 'invalid'])
      }).should.throw('invalid attribute `invalid`');
    });
  });

  xdescribe('#show()', function() {

    it('should only return the attributes included in the scope', function() {

    });

    it('should use the output transform chain', function() {

    });

    xit('should hide hidden attributes', function() {

    });
  });

  xdescribe('#save()', function() {

    it('should save changed attributes to both datastores', function() {

    });

    it('should save to cassandra before saving to redis', function() {

    });

    it('should only fail when saving to the cassandra datastore ' +
      'fails', function() {
      // Redis failure should not stop it.
    });

    // - caching
    // - relations
    // - permissions (mixin)

    xit('should validate attributes', function() {

    });

    it('should not fail when no attributes have changed', function(done) {
      this.model.save(function(err) {
        expect(err).to.be.undefined;
        done();
      });
    });

    it('should reset changed attributes when done', function(done) {
      var model = this.model;
      model.set('foo', 'bar');

      model.save(function(err) {
        expect(err).to.be.undefined;
        model.changedAttributes.should.be.empty;
        done();
      });
    });
  });

  xdescribe('#fetch()', function() {

    it('should fail if the primary key attribute is not set', function() {

    });

    // scopes
    // fetch from cache if set
    // when to fail

    it('should fetch the model from', function() {

    });
  });

  describe('#destroy()', function() {

    function noop(pk, options, cb) {cb();}

    beforeEach(function() {
      sinon.stub(this.redis, 'destroy', noop);
      sinon.stub(this.cassandra, 'destroy', noop);
    });

    afterEach(function() {
      this.redis.destroy.restore();
      this.cassandra.destroy.restore();
    });

    xit('should destroy the model from both datastores', function(done) {
      var self = this;
      var model = this.orm.createModel('Model', _.extend({}, options, {
        cachedVariables: true
      })).get('foo');
      model.destroy(function() {
        var testOptions = {schema: {id: {
          primary: true, type: 'string'
        }}, table: 'models'};
        self.redis.destroy.should.have.been.calledWith(
          'foo', testOptions, sinon.match.func);
        self.cassandra.destroy.should.have.been.calledWith(
          'foo', testOptions, sinon.match.func);
        done();
      });
    });

    it('should not attempt to destroy from the redis datastore if all ' +
      'attributes are uncached', function() {
      var self = this;
      var model = this.orm.createModel('Model', options).get('foo');
      model.destroy(function() {
        self.redis.destroy.should.not.have.been.called;
        self.cassandra.destroy.should.have.been.called;
      });
    });

    it('should fail if destroying from any datastore fails', function(done) {
      var self = this;
      this.redis.destroy.restore();
      sinon.stub(this.redis, 'destroy', function(pk, options, cb) {
        cb('error');
      });
      var model = this.orm.createModel('Model', options).get('foo');
      model.destroy(function(err) {
        self.cassandra.destroy.should.not.have.been.called;
        err.should.equal('error');
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*eslint-env node, mocha */
import { TestUtils, expect, React } from '../../../test-helpers/expect-unit';
import Helmet from 'react-helmet';

import { Auth } from '../../../src/pages/auth';
import Messages from '../../../src/components/messages';
import Header from '../../../src/components/header';
import HeaderLogo from '../../../src/components/header-logo';
import Login from '../../../src/components/login';
import Footer from '../../../src/components/footer';
import Register from '../../../src/components/register';

describe('Auth page with redux', function() {

  it('MUST render important components', function() {
    let renderer = TestUtils.createRenderer();
    renderer.render(<Auth messages={[]} ui={{}} />);

    return expect(
      renderer,
      'to have rendered',

    );
  });

  it('MUST render passed messages', function() {
    let renderer = TestUtils.createRenderer();
    renderer.render(<Auth messages={[{message: 'Im foo message'}]} ui={{}} />);

    return expect(
      renderer,
      'to contain',

        <Messages messages={[{message: 'Im foo message'}]} />

    );
  });

  it('MUST pass ui.registrationSuccess to Register component', function() {
    let renderer = TestUtils.createRenderer();
    renderer.render(<Auth messages={[]} ui={{registrationSuccess: false}} />);

    return expect(
      renderer,
      'to have rendered',

        <Register registration_success={false} />

    );
  });

});
<commit_msg>unit/page/auth not to WARN<commit_after>/*
 This file is a part of libertysoil.org website
 Copyright (C) 2015  Loki Education (Social Enterprise)

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/*eslint-env node, mocha */
import { TestUtils, expect, React } from '../../../test-helpers/expect-unit';
import Helmet from 'react-helmet';
import sinon from 'sinon';

import { Auth } from '../../../src/pages/auth';
import Messages from '../../../src/components/messages';
import Header from '../../../src/components/header';
import HeaderLogo from '../../../src/components/header-logo';
import Login from '../../../src/components/login';
import Footer from '../../../src/components/footer';
import Register from '../../../src/components/register';

describe('Auth page with redux', function() {
  before(() => {
    sinon.stub(console, 'error', (warning) => { throw new Error(warning); });
  });

  after(() => {
    console.error.restore();
  });

  it('MUST render important components', () => {
    const renderer = TestUtils.createRenderer();
    renderer.render(<Auth is_logged_in messages={[]} ui={{}} />);

    return expect(
      renderer,
      'to have rendered',

    );
  });

  it('MUST render passed messages', () => {
    const renderer = TestUtils.createRenderer();
    renderer.render(<Auth is_logged_in messages={[{ message: 'Im foo message' }]} ui={{}} />);

    return expect(
      renderer,
      'to contain',

        <Messages messages={[{message: 'Im foo message'}]} />

    );
  });

  it('MUST pass ui.registrationSuccess to Register component', () => {
    const renderer = TestUtils.createRenderer();
    renderer.render(<Auth is_logged_in messages={[]} ui={{ registrationSuccess: false }} />);

    return expect(
      renderer,
      'to have rendered',

        <Register registration_success={false} />

    );
  });

});
<|endoftext|>


<commit_before><commit_msg>clean up Memory.username assignment<commit_after><|endoftext|>


<commit_before>var pythonMirror = process.env['npm_config_python_mirror'] || process.env.PYTHON_MIRROR || 'https://www.python.org/ftp/python/'

var buildTools = {
  installerName: 'BuildTools_Full.exe',
  installerUrl: 'https://download.microsoft.com/download/5/f/7/5f7acaeb-8363-451f-9425-68a90f98b238/visualcppbuildtools_full.exe',
  logName: 'build-tools-log.txt'
}

if (arch == "x64")
{
  var python = {
  installerName: 'python-2.7.14.amd64.msi',
  installerUrl: pythonMirror.replace(/\/*$/, '/2.7.14/python-2.7.14.amd64.msi'),
  targetName: 'python27',
  logName: 'python-log.txt'
  }
} else
{
  var python = {
  installerName: 'python-2.7.14.msi',
  installerUrl: pythonMirror.replace(/\/*$/, '/2.7.14/python-2.7.14.msi'),
  targetName: 'python27',
  logName: 'python-log.txt'
  }
}

module.exports = {
  buildTools,
  python
}
<commit_msg>Update constants.js<commit_after>var pythonMirror = process.env['npm_config_python_mirror'] || process.env.PYTHON_MIRROR || 'https://www.python.org/ftp/python/'

var buildTools = {
  installerName: 'BuildTools_Full.exe',
  installerUrl: 'https://download.microsoft.com/download/5/f/7/5f7acaeb-8363-451f-9425-68a90f98b238/visualcppbuildtools_full.exe',
  logName: 'build-tools-log.txt'
}

var python = {
  installerName: 'python-2.7.14.msi',
  installerUrl: pythonMirror.replace(/\/*$/, '/2.7.14/python-2.7.14.msi'),
  targetName: 'python27',
  logName: 'python-log.txt'
}

if (arch == "x64") {
  var python = {
  installerName: 'python-2.7.14.amd64.msi',
  installerUrl: pythonMirror.replace(/\/*$/, '/2.7.14/python-2.7.14.amd64.msi'),
  targetName: 'python27',
  logName: 'python-log.txt'
  }
}

module.exports = {
  buildTools,
  python
}
<|endoftext|>


<commit_before>export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000;

export const DAYS_IN_WEEK = 7;

export const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
<commit_msg>Added possibility of displaying weekday labels for odd days like in Github contributions heatmap. Horizontal case support<commit_after>export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000;

export const DAYS_IN_WEEK = 7;

export const MONTH_LABELS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

export const DAY_LABELS = ['', 'Mon', '', 'Wed', '', 'Fri', ''];<|endoftext|>


<commit_before>import axios from 'axios';
import {memoize, partial, property, stubTrue} from 'lodash';
import createContainer from './createContainer';
import extract from './extract';
import pipe from './pipe';
import condition from './condition';
import isResponseEmpty from './conditions/isResponseEmpty';
import requestUrlMatchesRegex from './conditions/requestUrlMatchesRegex';
import refactorRequestUrl from './preparators/refactorRequestUrl';
import extractMetaTags from './extractors/extractMetaTags';
import extractOEmbed from './extractors/extractOEmbed';
import extractOEmbedFromService from './extractors/extractOEmbedFromService';
import OEmbedFormats from './extractors/oEmbed/OEmbedFormats';
import findServiceFromList from './extractors/oEmbed/findServiceFromList';
import findServiceFromUrl from './extractors/oEmbed/findServiceFromUrl';
import mapResponseProps from './presenters/mapResponseProps';
import fillResponseUrl from './presenters/fillResponseUrl';

/**
 *
 */
export default createContainer()
	.withUnique('handleError', () => stubTrue)
	.withUnique('getHeaders', () => {
		const head = memoize(axios.head);
		return async (url) =>
			head(url).then(property('headers'));
	})
	.withUnique('getBody', () => {
		const get = memoize(axios.get);
		return async (url) =>
			get(url).then(property('data'));
	})
	.withUnique('isYoutubeRequest', () =>
		partial(
			requestUrlMatchesRegex,
			/youtube\.com|youtu\.be/i
		)
	)
	.withUnique('prepareYoutubeRequest', () =>
		partial(
			refactorRequestUrl,
			/^(.*)(v=|v\/|embed\/|youtu\.be\/)([a-z0-9_-]+)(.*)$/i,
			'https://www.youtube.com/watch?v=$3'
		)
	)
	.withUnique('oEmbedServices', () => ({
		'youtube': {
			pattern: /youtube\.com|youtu\.be/i,
			endpoint: 'http://www.youtube.com/oembed?format=json&url=:url',
			format: OEmbedFormats.json
		}
	}))
	.withUnique('findServiceFromList', (get) =>
		partial(findServiceFromList, get('oEmbedServices'))
	)
	.withUnique('findServiceFromUrl', (get) =>
		partial(findServiceFromList, get('getBody'))
	)
	.withUnique('extractOEmbedFromService', (get) =>
		partial(extractOEmbedFromService, get('getBody'))
	)
	.withUnique('extractOEmbedFromList', (get) =>
		partial(
			extractOEmbed,
			get('findServiceFromList'),
			get('extractOEmbedFromService')
		)
	)
	.withUnique('extractOEmbedFromUrl', (get) =>
		partial(
			extractOEmbed,
			get('findServiceFromUrl'),
			get('extractOEmbedFromService')
		)
	)
	.withUnique('mapOEmbed', () =>
		partial(mapResponseProps, {
			author_name: 'authorName',
			provider_name: 'providerName',
			provider_url: 'providerUrl',
			thumbnail_url: 'thumbnailUrl',
			thumbnail_width: 'thumbnailWidth',
			thumbnail_height: 'thumbnailHeight'
		})
	)
	.withUnique('extractOpenGraph', (get) =>
		partial(extractMetaTags, get('getBody'), /^og:/i)
	)
	.withUnique('mapOpenGraph', () =>
		partial(mapResponseProps, {
			'og:url': 'url',
			'og:type': 'type',
			'og:title': 'title',
			'og:description': 'description',
			'og:site_name': 'providerName',
			'og:image': 'thumbnailUrl',
			'og:image:url': 'thumbnailUrl',
			'og:image:width': 'width',
			'og:image:height': 'height',
			'og:video:width': 'width',
			'og:video:height': 'height'
		})
	)
	.withUnique('extractTwitterTags', (get) =>
		partial(extractMetaTags, get('getBody'), /^twitter:/i)
	)
	.withUnique('mapTwitterTags', () =>
		partial(mapResponseProps, {
			'twitter:card': 'type',
			'twitter:title': 'title',
			'twitter:description': 'description',
			'twitter:site': 'providerName',
			'twitter:creator': 'authorName'
		})
	)
	.withUnique('middlewares', (get) =>
		pipe([
			condition(
				get('isYoutubeRequest'),
				get('prepareYoutubeRequest')
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOEmbedFromList'),
					get('mapOEmbed')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOEmbedFromUrl'),
					get('mapOEmbed')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOpenGraph'),
					get('mapOpenGraph')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractTwitterTags'),
					get('mapTwitterTags')
				])
			),
			//	condition(
			//		get('isYoutubeRequest'),
			//		youtubePresenter()
			//	),
			fillResponseUrl
		], get('handleError'))
	)
	.withUnique('extract', (get) =>
		partial(extract, get('middlewares'))
	);
<commit_msg>Fixed oEmbed configuration<commit_after>import axios from 'axios';
import {memoize, partial, property, stubTrue} from 'lodash';
import createContainer from './createContainer';
import extract from './extract';
import pipe from './pipe';
import condition from './condition';
import isResponseEmpty from './conditions/isResponseEmpty';
import requestUrlMatchesRegex from './conditions/requestUrlMatchesRegex';
import refactorRequestUrl from './preparators/refactorRequestUrl';
import extractMetaTags from './extractors/extractMetaTags';
import extractOEmbed from './extractors/extractOEmbed';
import extractOEmbedFromService from './extractors/extractOEmbedFromService';
import OEmbedFormats from './extractors/oEmbed/OEmbedFormats';
import findServiceFromList from './extractors/oEmbed/findServiceFromList';
import findServiceFromUrl from './extractors/oEmbed/findServiceFromUrl';
import mapResponseProps from './presenters/mapResponseProps';
import fillResponseUrl from './presenters/fillResponseUrl';

/**
 *
 */
export default createContainer()
	.withUnique('handleError', () => stubTrue)
	.withUnique('getHeaders', () => {
		const head = memoize(axios.head);
		return async (url) =>
			head(url).then(property('headers'));
	})
	.withUnique('getBody', () => {
		const get = memoize(axios.get);
		return async (url) =>
			get(url).then(property('data'));
	})
	.withUnique('isYoutubeRequest', () =>
		partial(
			requestUrlMatchesRegex,
			/youtube\.com|youtu\.be/i
		)
	)
	.withUnique('prepareYoutubeRequest', () =>
		partial(
			refactorRequestUrl,
			/^(.*)(v=|v\/|embed\/|youtu\.be\/)([a-z0-9_-]+)(.*)$/i,
			'https://www.youtube.com/watch?v=$3'
		)
	)
	.withUnique('oEmbedServices', () => ({
		'youtube': {
			pattern: /youtube\.com|youtu\.be/i,
			endpoint: 'http://www.youtube.com/oembed?format=json&url=:url',
			format: OEmbedFormats.json
		}
	}))
	.withUnique('findServiceFromList', (get) =>
		partial(findServiceFromList, get('oEmbedServices'))
	)
	.withUnique('findServiceFromUrl', (get) =>
		partial(findServiceFromUrl, get('getBody'))
	)
	.withUnique('extractOEmbedFromService', (get) =>
		partial(extractOEmbedFromService, get('getBody'))
	)
	.withUnique('extractOEmbedFromList', (get) =>
		partial(
			extractOEmbed,
			get('findServiceFromList'),
			get('extractOEmbedFromService')
		)
	)
	.withUnique('extractOEmbedFromUrl', (get) =>
		partial(
			extractOEmbed,
			get('findServiceFromUrl'),
			get('extractOEmbedFromService')
		)
	)
	.withUnique('mapOEmbed', () =>
		partial(mapResponseProps, {
			author_name: 'authorName',
			provider_name: 'providerName',
			provider_url: 'providerUrl',
			thumbnail_url: 'thumbnailUrl',
			thumbnail_width: 'thumbnailWidth',
			thumbnail_height: 'thumbnailHeight'
		})
	)
	.withUnique('extractOpenGraph', (get) =>
		partial(extractMetaTags, get('getBody'), /^og:/i)
	)
	.withUnique('mapOpenGraph', () =>
		partial(mapResponseProps, {
			'og:url': 'url',
			'og:type': 'type',
			'og:title': 'title',
			'og:description': 'description',
			'og:site_name': 'providerName',
			'og:image': 'thumbnailUrl',
			'og:image:url': 'thumbnailUrl',
			'og:image:width': 'width',
			'og:image:height': 'height',
			'og:video:width': 'width',
			'og:video:height': 'height'
		})
	)
	.withUnique('extractTwitterTags', (get) =>
		partial(extractMetaTags, get('getBody'), /^twitter:/i)
	)
	.withUnique('mapTwitterTags', () =>
		partial(mapResponseProps, {
			'twitter:card': 'type',
			'twitter:title': 'title',
			'twitter:description': 'description',
			'twitter:site': 'providerName',
			'twitter:creator': 'authorName'
		})
	)
	.withUnique('middlewares', (get) =>
		pipe([
			condition(
				get('isYoutubeRequest'),
				get('prepareYoutubeRequest')
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOEmbedFromList'),
					get('mapOEmbed')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOEmbedFromUrl'),
					get('mapOEmbed')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractOpenGraph'),
					get('mapOpenGraph')
				])
			),
			condition(
				isResponseEmpty,
				pipe([
					get('extractTwitterTags'),
					get('mapTwitterTags')
				])
			),
			//	condition(
			//		get('isYoutubeRequest'),
			//		youtubePresenter()
			//	),
			fillResponseUrl
		], get('handleError'))
	)
	.withUnique('extract', (get) =>
		partial(extract, get('middlewares'))
	);
<|endoftext|>


<commit_before>"use strict";

var _ = require('underscore');
var Article = require('substance-article');

// A contains method has been added to Strings in Javascript 1.8.6:
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/contains
if (typeof String.prototype.contains === 'undefined') {
  String.prototype.contains = function(ch) {
    return this.indexOf(ch) != -1;
  };
}

// Substance.Converter
// -------------------
//
// Converts Pandoc Input to a Substance Article

var Converter = function(input, doc_id) {
  this.input = input;
  this.doc_id = doc_id || "foo_doc";
};

Converter.Prototype = function() {

  this.output = function() {
    var doc = this.input,
        nodesList = doc.get("content").nodes,
        content = [],
        punctuations = ',.;:"?!';

    function getAnnotations(id){
      var anns = _.filter(doc.nodes,function(node){
        if(_.isUndefined(node.path)){
      	  return false;
        } else {
          return node.path[0] == id;
        }
      })
      if (_.isEmpty(anns)) return false;
      return anns;
    }

    // Process nodes
    function process(node) {
      var nodeType = node.properties.type;

      function makeHeaderId(node) {
        return node.replace(/['";:,.\/?\\-]/g, '').split(' ').join('-').toLowerCase();
      }

      function processNode(node) {

        var result = [],
            annotations = getAnnotations(node.properties.id),
            currentWord = 0,
            annWord = 0,
            ranges = _.flatten(_.pluck(annotations,'range')),
            annCounter = 0,
            currentRange = null;

        function processAnn(contents,annotation) {
          var ann = [];
          _.each(contents.split(''), function(ch, index) {
            if(punctuations.contains(ch)) {
              ann.push({"Str":ch});
              annWord++
            }
            else if(ch == ' ') {
              ann.push('Space');
              annWord++
            }
            else {
              if(!ann[annWord]) ann[annWord] = {'Str':''};
              ann[annWord].Str += ch;
              if(contents[index+1] == ' ' || punctuations.contains(contents[index + 1])) annWord++;
            }
          });

          switch (annotation.type) {
            case 'strong':
              ann = {
                "Strong":ann
              }
              break;
            case 'emphasis':
              ann = {
                "Emph":ann
              }
              break;
            case 'link':
              ann = {
                "Link":[ann,[annotation.url,""]]
              }
              break;
            default:
              ann = {
                "Emph":ann
              }
              break;
          }
          annWord = 0;
          return ann;
        }

        _.each(node.properties.content.split(''), function(ch, index) {
          if(ranges.indexOf(index) != -1) {
            var ann = annotations[annCounter],
                annContent = node.properties.content.substr(ann.range[0],ann.range[1]-ann.range[0]),
                type = ann.type,
                annObj = processAnn(annContent,ann);
            ranges.splice(0,2);
            annCounter++;
            currentRange = ann.range[1];
            result.push(annObj);
            currentWord++;
          } else {
            if(_.isNull(currentRange) || (index>=currentRange)){
              if(punctuations.contains(ch)) {
                result.push({"Str":ch});
                currentWord++;
              }
              else if(ch == ' ') {
                result.push('Space');
                currentWord++;
              }
              else {
                if(!result[currentWord]) result[currentWord] = {'Str':''};
                result[currentWord].Str += ch;
                if(node.properties.content[index+1] == ' ' || punctuations.contains(node.properties.content[index + 1]) || ranges.indexOf(index + 1) != -1) currentWord++;
              }
            }
          }
        });
        return result;
      }

      switch (nodeType) {
        case 'paragraph':
          var atomic = processNode(node);
          content.push({"Para":atomic});
          break;
        case 'heading':
          var atomic = processNode(node);
          var id = makeHeaderId(node.content);
          content.push({"Header":[node.properties.level,[id,[],[]],atomic]});
          break;
        case 'codeblock':
          var atomic = processNode(node);
          content.push({"CodeBlock":[["",[],[]],node.properties.content]});
          break;
        case 'image':
          content.push({"Para":[{"Image":[[],[node.properties.url,""]]}]});
          break;
        default:
          var atomic = processNode(node);
          content.push({"Para":atomic});
          break;
      };
    };

    _.each(nodesList, function(nodeid){
    	var node = doc.get(nodeid);
      process(node);
    });

    content = [{
      "docTitle": [],
      "docAuthors": [],
      "docDate": []
    },content];

    return content;
  };

  // Do the actual conversion
  this.convert = function() {
    var json = this.input;

    var doc_id = this.doc_id;

    // The resulting Substance Doc
    var doc = new Article({"id": this.doc_id});

    var lastid = 0,
        lastpid = 0,
        offset = 0;

    function getId() {
      lastid += 1;
      return lastid;
    }

    function getPId() {
      lastpid += 1;
      return lastpid;
    }

    var elements = json[1];
    var fragments = [];
    var offset = 0;

    var annotations = {
      "annotations": [],
      "strong": [],
      "emphasis": [],
      "inline-code": [],
      "link": [],
      "idea": [],
      "error": [],
      "question": []
    };

    // Process nodes
    function process(node) {
      var nodeType = '';
      var lastNodeId;

      // inspect element
      if(typeof node === 'object'){
        if(Array.isArray(node)) {
          _.each(node, function (subNode){
            process(subNode);
          });
        } else {
          nodeType = _.first(Object.keys(node));
        }
      } else {
        nodeType = node;
      }

      // Shared internal actions
      function processAtomic (str){
        offset += str.length;
        for (var ann in annotations) {
          annotations[ann].push(str);
        }
        fragments.push(str);
      }

      function processInline (type){
        annotations[type] = []; // reset
        var start = offset;
        var val = process(node[nodeType]);
        return val['annotations'][type];
      }

      function processBlock (item, ins) {
        fragments = []; // reset
        var res = process(item);
        return res['text'].join('');
      }

      function processSimple (item) {
        fragments = [];
        offset += item.length;
        fragments.push(item);
        return item;
      }

      function processList (type) {
        var list = '';
        _.each(node[nodeType], function (li){
          fragments = [];
          switch (type) {
            case 'bl':
              res = process(li[0]['Plain']);
              list += '* ' + res['text'].join('') + '\n';
              console.log(res['text'].join(''))
              break;

            case 'ol':
        			if(!_.isUndefined(li[0][0])){
        				res = process(li[0][0]['Plain']);
        				list += res['text'].join('') + '\n';
        			}
              break;

            case 'ul':
              res = process(li[0]['Plain']);
              list += '* ' + res['text'].join('') + '\n';
              break;
          }
        });
        return list;
      }

      // Insert node into document
      function insert (type, data, target) {
        if(type == 'paragraph') {
          var id = type+'_'+getPId();
        } else {
          var id = type+'_'+getId();
        }
        lastNodeId = id;
        // insert a new node
        var node = _.extend({
          id: id,
          type: type
        }, data);

        doc.create(node);
        doc.show("content", [id], -1);
      }

      // Proceed processing
      switch (nodeType) {

        // Atomic content
        case 'Str':
          var str = node['Str'];
          processAtomic(str);
          break;

        case 'Space':
          var str = ' ';
          processAtomic(str);
          break;

        case 'LineBreak':
          var str = '\n';
          processAtomic(str);
          break;

        // Inline content
        case 'Emph':
          var annType = 'emphasis';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          insert(annType,{
          	"node": 'paragraph_' + (lastpid + 1),
            "property": "content",
            "range": [start, start + len]
          });
          break;

        case 'Strong':
          var annType = 'strong';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          insert(annType,{
          	"node": 'paragraph_' + (lastpid + 1),
            "property": "content",
            "range": [start, start + len]
          });
          break;

        case 'Code':
          /*var annType = 'inline-code';
          processInline(annType);*/
          processAtomic(node[nodeType][1])
          break;

        case 'Link':
          var annType = 'link';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          var url = node["Link"][1][0];
          insert(annType,{
          	"node": 'paragraph_' + (lastpid + 1),
            "property": "content",
            "range": [start, start + len],
            "url": url
          });
          break;

        // Block content
        case 'Header':
          insert ('heading', {
            "level": node[nodeType][0],
            "content": processBlock(node[nodeType][2])
          });

          break;

        case 'Para':
        case 'Plain':
          insert ('paragraph', {
            "content": processBlock(node[nodeType])
          });
          break;

        case 'OrderedList':
          insert ('paragraph', {
            "subtype": "ol",
            "content": processList('ol')
          });
          break;

        case 'UnorderedList':
          insert ('paragraph', {
            "subtype": "ul",
            "content": processList('ul')
          });
          break;

        case 'BulletList':
          insert ('paragraph', {
            "subtype": "bl",
            "content": processList('bl')
          });
          break;

        case 'RawBlock':
        case 'CodeBlock':
          insert ('codeblock', {
            "lang": node[nodeType][0][1][0],
            "content": processSimple(node[nodeType][1])
          }, ["figures", "back"]);
          break;

        case 'BlockQuote':
          // Find out why Para and if it always appears or further functionalities for BlockQuotes
          // Needs nodetype in the Document
          // insert ('codeblock', {
          //   "subtype": "blocQuote",
          //   "content": processBlock(node[nodeType][0]['Para'])
          // });
          break;

        case 'HorizontalRule':
          // for now we fake HR
          // should we skip it all the way?
          // offset += 1;
          // insert ('text', {
          //   "content": '------'
          // });
          break;

        case 'Image':
          // var alt = process(node[nodeType][0]);
          fragments = []; // reset
          var res = process(node[nodeType][0]);
          var alt = res['text'].join('');
          var url = node[nodeType][1][0];
          var title = node[nodeType][1][1];

          insert('image', {
                    "alt": alt,
                    "title": title,
                    "url": url
          });
          break;

        default:
          // if(nodeType.length > 1)
          break;

      }
      return {"text": fragments, "annotations": annotations};
    }

    _.each(elements, function (n) {
      process(n);
    });

    return doc;
  };
};

Converter.prototype = new Converter.Prototype();

module.exports = Converter;
<commit_msg>Use new path format for annotations.<commit_after>"use strict";

var _ = require('underscore');
var Article = require('substance-article');

// A contains method has been added to Strings in Javascript 1.8.6:
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String/contains
if (typeof String.prototype.contains === 'undefined') {
  String.prototype.contains = function(ch) {
    return this.indexOf(ch) != -1;
  };
}

// Substance.Converter
// -------------------
//
// Converts Pandoc Input to a Substance Article

var Converter = function(input, doc_id) {
  this.input = input;
  this.doc_id = doc_id || "foo_doc";
};

Converter.Prototype = function() {

  this.output = function() {
    var doc = this.input,
        nodesList = doc.get("content").nodes,
        content = [],
        punctuations = ',.;:"?!';

    function getAnnotations(id){
      var anns = _.filter(doc.nodes,function(node){
        if(_.isUndefined(node.path)){
      	  return false;
        } else {
          return node.path[0] == id;
        }
      })
      if (_.isEmpty(anns)) return false;
      return anns;
    }

    // Process nodes
    function process(node) {
      var nodeType = node.properties.type;

      function makeHeaderId(node) {
        return node.replace(/['";:,.\/?\\-]/g, '').split(' ').join('-').toLowerCase();
      }

      function processNode(node) {

        var result = [],
            annotations = getAnnotations(node.properties.id),
            currentWord = 0,
            annWord = 0,
            ranges = _.flatten(_.pluck(annotations,'range')),
            annCounter = 0,
            currentRange = null;

        function processAnn(contents,annotation) {
          var ann = [];
          _.each(contents.split(''), function(ch, index) {
            if(punctuations.contains(ch)) {
              ann.push({"Str":ch});
              annWord++
            }
            else if(ch == ' ') {
              ann.push('Space');
              annWord++
            }
            else {
              if(!ann[annWord]) ann[annWord] = {'Str':''};
              ann[annWord].Str += ch;
              if(contents[index+1] == ' ' || punctuations.contains(contents[index + 1])) annWord++;
            }
          });

          switch (annotation.type) {
            case 'strong':
              ann = {
                "Strong":ann
              }
              break;
            case 'emphasis':
              ann = {
                "Emph":ann
              }
              break;
            case 'link':
              ann = {
                "Link":[ann,[annotation.url,""]]
              }
              break;
            default:
              ann = {
                "Emph":ann
              }
              break;
          }
          annWord = 0;
          return ann;
        }

        _.each(node.properties.content.split(''), function(ch, index) {
          if(ranges.indexOf(index) != -1) {
            var ann = annotations[annCounter],
                annContent = node.properties.content.substr(ann.range[0],ann.range[1]-ann.range[0]),
                type = ann.type,
                annObj = processAnn(annContent,ann);
            ranges.splice(0,2);
            annCounter++;
            currentRange = ann.range[1];
            result.push(annObj);
            currentWord++;
          } else {
            if(_.isNull(currentRange) || (index>=currentRange)){
              if(punctuations.contains(ch)) {
                result.push({"Str":ch});
                currentWord++;
              }
              else if(ch == ' ') {
                result.push('Space');
                currentWord++;
              }
              else {
                if(!result[currentWord]) result[currentWord] = {'Str':''};
                result[currentWord].Str += ch;
                if(node.properties.content[index+1] == ' ' || punctuations.contains(node.properties.content[index + 1]) || ranges.indexOf(index + 1) != -1) currentWord++;
              }
            }
          }
        });
        return result;
      }

      switch (nodeType) {
        case 'paragraph':
          var atomic = processNode(node);
          content.push({"Para":atomic});
          break;
        case 'heading':
          var atomic = processNode(node);
          var id = makeHeaderId(node.content);
          content.push({"Header":[node.properties.level,[id,[],[]],atomic]});
          break;
        case 'codeblock':
          var atomic = processNode(node);
          content.push({"CodeBlock":[["",[],[]],node.properties.content]});
          break;
        case 'image':
          content.push({"Para":[{"Image":[[],[node.properties.url,""]]}]});
          break;
        default:
          var atomic = processNode(node);
          content.push({"Para":atomic});
          break;
      };
    };

    _.each(nodesList, function(nodeid){
    	var node = doc.get(nodeid);
      process(node);
    });

    content = [{
      "docTitle": [],
      "docAuthors": [],
      "docDate": []
    },content];

    return content;
  };

  // Do the actual conversion
  this.convert = function() {
    var json = this.input;

    var doc_id = this.doc_id;

    // The resulting Substance Doc
    var doc = new Article({"id": this.doc_id});

    var lastid = 0,
        lastpid = 0,
        offset = 0;

    function getId() {
      lastid += 1;
      return lastid;
    }

    function getPId() {
      lastpid += 1;
      return lastpid;
    }

    var elements = json[1];
    var fragments = [];
    var offset = 0;

    var annotations = {
      "annotations": [],
      "strong": [],
      "emphasis": [],
      "inline-code": [],
      "link": [],
      "idea": [],
      "error": [],
      "question": []
    };

    // Process nodes
    function process(node) {
      var nodeType = '';
      var lastNodeId;

      // inspect element
      if(typeof node === 'object'){
        if(Array.isArray(node)) {
          _.each(node, function (subNode){
            process(subNode);
          });
        } else {
          nodeType = _.first(Object.keys(node));
        }
      } else {
        nodeType = node;
      }

      // Shared internal actions
      function processAtomic (str){
        offset += str.length;
        for (var ann in annotations) {
          annotations[ann].push(str);
        }
        fragments.push(str);
      }

      function processInline (type){
        annotations[type] = []; // reset
        var start = offset;
        var val = process(node[nodeType]);
        return val['annotations'][type];
      }

      function processBlock (item, ins) {
        fragments = []; // reset
        var res = process(item);
        return res['text'].join('');
      }

      function processSimple (item) {
        fragments = [];
        offset += item.length;
        fragments.push(item);
        return item;
      }

      function processList (type) {
        var list = '';
        _.each(node[nodeType], function (li){
          fragments = [];
          switch (type) {
            case 'bl':
              res = process(li[0]['Plain']);
              list += '* ' + res['text'].join('') + '\n';
              console.log(res['text'].join(''))
              break;

            case 'ol':
        			if(!_.isUndefined(li[0][0])){
        				res = process(li[0][0]['Plain']);
        				list += res['text'].join('') + '\n';
        			}
              break;

            case 'ul':
              res = process(li[0]['Plain']);
              list += '* ' + res['text'].join('') + '\n';
              break;
          }
        });
        return list;
      }

      // Insert node into document
      function insert (type, data, target) {
        if(type == 'paragraph') {
          var id = type+'_'+getPId();
        } else {
          var id = type+'_'+getId();
        }
        lastNodeId = id;
        // insert a new node
        var node = _.extend({
          id: id,
          type: type
        }, data);

        doc.create(node);

        // Only add nodes of basetype 'content' to the content view
        // --------

        var baseType = doc.schema.baseType(node.type);

        if (baseType === "content") {
          doc.show("content", [id], -1);
        }
      }

      // Proceed processing
      switch (nodeType) {

        // Atomic content
        case 'Str':
          var str = node['Str'];
          processAtomic(str);
          break;

        case 'Space':
          var str = ' ';
          processAtomic(str);
          break;

        case 'LineBreak':
          var str = '\n';
          processAtomic(str);
          break;

        // Inline content
        case 'Emph':
          var annType = 'emphasis';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          var paraId = 'paragraph_' + (lastpid + 1);

          insert(annType,{
          	"path": [paraId, "content"],
            "property": "content",
            "range": [start, start + len]
          });
          break;

        case 'Strong':
          var annType = 'strong';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          var paraId = 'paragraph_' + (lastpid + 1);

          insert(annType,{
          	"path": [paraId, "content"],
            "property": "content",
            "range": [start, start + len]
          });
          break;

        case 'Code':
          /*var annType = 'inline-code';
          processInline(annType);*/
          processAtomic(node[nodeType][1])
          break;

        case 'Link':
          var annType = 'link';
          var start = offset;
          var txt = processInline(annType);
          var len = txt.length;
          var url = node["Link"][1][0];

          var paraId = 'paragraph_' + (lastpid + 1);
          insert(annType,{
            "path": [paraId, "content"],
            "range": [start, start + len],
            "url": url
          });
          break;

        // Block content
        case 'Header':
          insert ('heading', {
            "level": node[nodeType][0],
            "content": processBlock(node[nodeType][2])
          });

          break;

        case 'Para':
        case 'Plain':
          insert ('paragraph', {
            "content": processBlock(node[nodeType])
          });
          break;

        case 'OrderedList':
          insert ('paragraph', {
            "subtype": "ol",
            "content": processList('ol')
          });
          break;

        case 'UnorderedList':
          insert ('paragraph', {
            "subtype": "ul",
            "content": processList('ul')
          });
          break;

        case 'BulletList':
          insert ('paragraph', {
            "subtype": "bl",
            "content": processList('bl')
          });
          break;

        case 'RawBlock':
        case 'CodeBlock':
          insert ('codeblock', {
            "lang": node[nodeType][0][1][0],
            "content": processSimple(node[nodeType][1])
          }, ["figures", "back"]);
          break;

        case 'BlockQuote':
          // Find out why Para and if it always appears or further functionalities for BlockQuotes
          // Needs nodetype in the Document
          // insert ('codeblock', {
          //   "subtype": "blocQuote",
          //   "content": processBlock(node[nodeType][0]['Para'])
          // });
          break;

        case 'HorizontalRule':
          // for now we fake HR
          // should we skip it all the way?
          // offset += 1;
          // insert ('text', {
          //   "content": '------'
          // });
          break;

        case 'Image':
          // var alt = process(node[nodeType][0]);
          fragments = []; // reset
          var res = process(node[nodeType][0]);
          var alt = res['text'].join('');
          var url = node[nodeType][1][0];
          var title = node[nodeType][1][1];

          insert('image', {
                    "alt": alt,
                    "title": title,
                    "url": url
          });
          break;

        default:
          // if(nodeType.length > 1)
          break;

      }
      return {"text": fragments, "annotations": annotations};
    }

    _.each(elements, function (n) {
      process(n);
    });

    return doc;
  };
};

Converter.prototype = new Converter.Prototype();

module.exports = Converter;
<|endoftext|>