text: 'use strict';
  
  /**
   * Test dependencies.
   */
  
  const assert = require('assert');
  const co = require('co');
  const start = require('./common');
  const random = require('../lib/utils').random;
  
  const mongoose = start.mongoose;
  const Schema = mongoose.Schema;
  const ObjectId = Schema.Types.ObjectId;
  const DocumentObjectId = mongoose.Types.ObjectId;
  
  describe('model: findOneAndReplace:', function() {
    let Comments;
    let BlogPost;
    let modelname;
    let collection;
    let strictSchema;
    let db;
  
    before(function() {
      Comments = new Schema;
  
      Comments.add({
        title: String,
        date: Date,
        body: String,
        comments: [Comments]
      });
  
      BlogPost = new Schema({
        title: String,
        author: String,
        slug: String,
        date: Date,
        meta: {
          date: Date,
          visitors: Number
        },
        published: Boolean,
        mixed: {},
        numbers: [Number],
        owners: [ObjectId],
        comments: [Comments]
      });
  
      BlogPost.virtual('titleWithAuthor')
        .get(function() {
          return this.get('title') + ' by ' + this.get('author');
        })
        .set(function(val) {
          const split = val.split(' by ');
          this.set('title', split[0]);
          this.set('author', split[1]);
        });
  
      BlogPost.method('cool', function() {
        return this;
      });
  
      BlogPost.static('woot', function() {
        return this;
      });
  
      modelname = 'ReplaceOneBlogPost';
      mongoose.model(modelname, BlogPost);
  
      collection = 'replaceoneblogposts';
  
      strictSchema = new Schema({name: String}, {strict: true});
      mongoose.model('ReplaceOneStrictSchema', strictSchema);
  
      db = start();
    });
  
    after(function(done) {
      db.close(done);
    });
  
    it('returns the original document', function() {
      const M = db.model(modelname, collection);
      const title = 'remove muah';
  
      const post = new M({title: title});
  
      return co(function*() {
        yield post.save();
  
        const doc = yield M.findOneAndReplace({title: title});
  
        assert.equal(post.id, doc.id);
      });
    });
  
    it('options/conditions/doc are merged when no callback is passed', function(done) {
      const M = db.model(modelname, collection);
  
      const now = new Date;
      let query;
  
      // Model.findOneAndReplace
      query = M.findOneAndReplace({author: 'aaron'}, {}, {select: 'author'});
      assert.equal(query._fields.author, 1);
      assert.equal(query._conditions.author, 'aaron');
  
      query = M.findOneAndReplace({author: 'aaron'});
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions.author, 'aaron');
  
      query = M.findOneAndReplace();
      assert.equal(query.options.new, undefined);
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions.author, undefined);
  
      // Query.findOneAndReplace
      query = M.where('author', 'aaron').findOneAndReplace({date: now});
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions.date, now);
      assert.equal(query._conditions.author, 'aaron');
  
      query = M.find().findOneAndReplace({author: 'aaron'}, {}, {select: 'author'});
      assert.equal(query._fields.author, 1);
      assert.equal(query._conditions.author, 'aaron');
  
      query = M.find().findOneAndReplace();
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions.author, undefined);
      done();
    });
  
    it('executes when a callback is passed', function(done) {
      const M = db.model(modelname, collection + random());
      let pending = 5;
  
      M.findOneAndReplace({name: 'aaron1'}, {select: 'name'}, cb);
      M.findOneAndReplace({name: 'aaron1'}, cb);
      M.where().findOneAndReplace({name: 'aaron1'}, {select: 'name'}, cb);
      M.where().findOneAndReplace({name: 'aaron1'}, cb);
      M.where('name', 'aaron1').findOneAndReplace(cb);
  
      function cb(err, doc) {
        assert.ifError(err);
        assert.equal(doc, null); // no previously existing doc
        if (--pending) return;
        done();
      }
    });
  
    it('executed with only a callback throws', function(done) {
      const M = db.model(modelname, collection);
      let err;
  
      try {
        M.findOneAndReplace(function() {});
      } catch (e) {
        err = e;
      }
  
      assert.ok(/First argument must not be a function/.test(err));
      done();
    });
  
    it('executed with only a callback throws', function(done) {
      const M = db.model(modelname, collection);
      let err;
  
      try {
        M.findByIdAndDelete(function() {});
      } catch (e) {
        err = e;
      }
  
      assert.ok(/First argument must not be a function/.test(err));
      done();
    });
  
    it('executes when a callback is passed', function(done) {
      const M = db.model(modelname, collection + random());
      const _id = new DocumentObjectId;
      let pending = 2;
  
      M.findByIdAndDelete(_id, {select: 'name'}, cb);
      M.findByIdAndDelete(_id, cb);
  
      function cb(err, doc) {
        assert.ifError(err);
        assert.equal(doc, null); // no previously existing doc
        if (--pending) return;
        done();
      }
    });
  
    it('returns the original document', function(done) {
      const M = db.model(modelname, collection);
      const title = 'remove muah pleez';
  
      const post = new M({title: title});
      post.save(function(err) {
        assert.ifError(err);
        M.findByIdAndDelete(post.id, function(err, doc) {
          assert.ifError(err);
          assert.equal(post.id, doc.id);
          M.findById(post.id, function(err, gone) {
            assert.ifError(err);
            assert.equal(gone, null);
            done();
          });
        });
      });
    });
  
    it('options/conditions/doc are merged when no callback is passed', function(done) {
      const M = db.model(modelname, collection);
      const _id = new DocumentObjectId();
  
      let query;
  
      // Model.findByIdAndDelete
      query = M.findByIdAndDelete(_id, {select: 'author'});
      assert.equal(query._fields.author, 1);
      assert.equal(query._conditions._id.toString(), _id.toString());
  
      query = M.findByIdAndDelete(_id.toString());
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions._id, _id.toString());
  
      query = M.findByIdAndDelete();
      assert.equal(query.options.new, undefined);
      assert.equal(query._fields, undefined);
      assert.equal(query._conditions._id, undefined);
      done();
    });
  
    it('supports v3 select string syntax', function(done) {
      const M = db.model(modelname, collection);
  
      const query = M.findOneAndReplace({}, {}, {select: 'author -title'});
      assert.strictEqual(1, query._fields.author);
      assert.strictEqual(0, query._fields.title);
      done();
    });
  
    it('supports v3 select object syntax', function(done) {
      const M = db.model(modelname, collection);
  
      const query = M.findOneAndReplace({}, {}, {select: {author: 1, title: 0}});
      assert.strictEqual(1, query._fields.author);
      assert.strictEqual(0, query._fields.title);
      done();
    });
  
    it('supports v3 sort string syntax', function(done) {
      const M = db.model(modelname, collection);
  
      const query = M.findOneAndReplace({}, {}, {sort: 'author -title'});
      assert.equal(Object.keys(query.options.sort).length, 2);
      assert.equal(query.options.sort.author, 1);
      assert.equal(query.options.sort.title, -1);
      done();
    });
  
    it('supports v3 sort object syntax', function(done) {
      const M = db.model(modelname, collection);
  
      const query = M.findOneAndReplace({}, {}, {sort: {author: 1, title: -1}});
      assert.equal(Object.keys(query.options.sort).length, 2);
      assert.equal(query.options.sort.author, 1);
      assert.equal(query.options.sort.title, -1);
      done();
    });
  
    it('supports population (gh-1395)', function(done) {
      const M = db.model('A', {name: String});
      const N = db.model('B', {a: {type: Schema.ObjectId, ref: 'A'}, i: Number});
  
      M.create({name: 'i am an A'}, function(err, a) {
        if (err) return done(err);
        N.create({a: a._id, i: 10}, function(err, b) {
          if (err) return done(err);
  
          N.findOneAndReplace({_id: b._id}, {a: a._id})
            .populate('a')
            .exec(function(err, doc) {
              if (err) return done(err);
              assert.ok(doc);
              assert.ok(doc.a);
              assert.equal(doc.a.name, 'i am an A');
              done();
            });
        });
      });
    });
  
    it('only calls setters once (gh-6203)', function() {
      return co(function*() {
        const calls = [];
        const userSchema = new mongoose.Schema({
          name: String,
          foo: {
            type: String,
            set: function(val) {
              calls.push(val);
              return val + val;
            }
          }
        });
        const Model = db.model('gh6203', userSchema);
  
        yield Model.findOneAndReplace({ foo: '123' }, { name: 'bar' });
  
        assert.deepEqual(calls, ['123']);
      });
    });
  
    describe('middleware', function() {
      it('works', function(done) {
        const s = new Schema({
          topping: {type: String, default: 'bacon'},
          base: String
        });
  
        let preCount = 0;
        s.pre('findOneAndReplace', function() {
          ++preCount;
        });
  
        let postCount = 0;
        s.post('findOneAndReplace', function() {
          ++postCount;
        });
  
        const Breakfast = db.model('gh-439', s);
        const breakfast = new Breakfast({
          base: 'eggs'
        });
  
        breakfast.save(function(error) {
          assert.ifError(error);
  
          Breakfast.findOneAndReplace(
            {base: 'eggs'},
            {},
            function(error, breakfast) {
              assert.ifError(error);
              assert.equal(breakfast.base, 'eggs');
              assert.equal(preCount, 1);
              assert.equal(postCount, 1);
              done();
            });
        });
      });
  
      it('works with exec() (gh-439)', function(done) {
        const s = new Schema({
          topping: {type: String, default: 'bacon'},
          base: String
        });
  
        let preCount = 0;
        s.pre('findOneAndReplace', function() {
          ++preCount;
        });
  
        let postCount = 0;
        s.post('findOneAndReplace', function() {
          ++postCount;
        });
  
        const Breakfast = db.model('Breakfast', s);
        const breakfast = new Breakfast({
          base: 'eggs'
        });
  
        breakfast.save(function(error) {
          assert.ifError(error);
  
          Breakfast.
            findOneAndReplace({base: 'eggs'}, {}).
            exec(function(error, breakfast) {
              assert.ifError(error);
              assert.equal(breakfast.base, 'eggs');
              assert.equal(preCount, 1);
              assert.equal(postCount, 1);
              done();
            });
        });
      });
    });
  });
  