/**
 * restore_document_validation.js
 *
 * This file test that mongorestore works with document validation. It both checks that when
 * validation is turned on invalid documents are not restored and that when a user indicates
 * they want to bypass validation, that all documents are restored.
 */

(function() {
  'use strict';
  if (typeof getToolTest === 'undefined') {
    load('jstests/configs/plain_28.config.js');
  }

  /**
   * Part 1: Test that restore follows document validation rules.
   */
  jsTest.log('Testing that restore reacts well to document validation');

  var toolTest = getToolTest('document_validation');
  var commonToolArgs = getCommonToolArguments();

  // where we'll put the dump
  var dumpTarget = 'doc_validation';
  resetDbpath(dumpTarget);

  // the db we will use
  var testDB = toolTest.db.getSiblingDB('test');

  // create 1000 documents, half of which will pass the validation
  var data = [];
  for (var i = 0; i < 1000; i++) {
    if (i%2 === 0) {
      data.push({_id: i, num: i+1, s: String(i)});
    } else {
      data.push({_id: i, num: i+1, s: String(i), baz: i});
    }
  }
  testDB.bar.insertMany(data, {ordered: true});
  // sanity check the insertion worked
  assert.eq(1000, testDB.bar.count(), 'all documents should be inserted');

  var ret = toolTest.runTool.apply(toolTest, ['dump', '-v']
    .concat(getDumpTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'dumping should run successfully');

  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // sanity check that we can restore the data without validation
  ret = toolTest.runTool.apply(toolTest, ['restore']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret);

  assert.eq(1000, testDB.bar.count(), 'after the restore, all documents should be seen');

  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // turn on validation
  var r = testDB.createCollection('bar', {validator: {baz: {$exists: true}}});
  assert.eq(r, {ok: 1}, 'create collection with validation should work');

  // test that it's working
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 0, "invalid documents shouldn't be inserted");

  // restore the 1000 records of which only 500 are valid
  ret = toolTest.runTool.apply(toolTest, ['restore', '-v']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'restoring against a collection with validation on should still succeed');

  assert.eq(500, testDB.bar.count(), 'only the valid documents should be restored');

  /**
   * Part 2: Test that restore can bypass document validation rules.
   */
  jsTest.log('Testing that bypass document validation works');

  testDB.dropDatabase();

  // turn on validation
  r = testDB.createCollection('bar', {validator: {baz: {$exists: true}}});
  assert.eq(r, {ok: 1}, 'create collection with validation should work');

  // test that we cannot insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 0, 'invalid documents should not be inserted');

  // restore the 1000 records again with bypassDocumentValidation turned on
  ret = toolTest.runTool.apply(toolTest, ['restore', '--bypassDocumentValidation']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'restoring documents should work with bypass document validation set');
  assert.eq(1000, testDB.bar.count(),
    'all documents should be restored with bypass document validation set');

  /**
   * Part 3: Test that restore can restore the document validation rules,
   * if they're dumped with the collection.
   */
  jsTest.log('Testing that dump and restore restores the validation rules themselves');

  // clear out the database, including the validation rules
  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // test that we can insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 1,
    'invalid documents should be inserted after validation rules are dropped');

  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // restore the 1000 records again
  ret = toolTest.runTool.apply(toolTest, ['restore']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret);
  assert.eq(1000, testDB.bar.count());

  // turn on validation on a existing collection
  testDB.runCommand({'collMod': 'bar', 'validator': {baz: {$exists: true}}});

  // re-dump everything, this time dumping the validation rules themselves
  ret = toolTest.runTool.apply(toolTest, ['dump', '-v']
    .concat(getDumpTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'the dump should run successfully');

  // clear out the database, including the validation rules
  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // test that we can insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 1,
    'invalid documents should be inserted after we drop validation rules');

  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // restore the 1000 records again
  ret = toolTest.runTool.apply(toolTest, ['restore']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'restoring rules and some invalid documents should run successfully');
  assert.eq(500, testDB.bar.count(),
    'restoring the validation rules and documents should only restore valid documents');

  /**
   * Part 4: Test that restore can bypass the document validation rules,
   * even if they're dumped with the collection and restored with the collection.
   */
  jsTest.log('Testing that bypass document validation works when restoring the rules as well');

  // clear out the database, including the validation rules
  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // test that we can insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 1,
    'invalid documents should be inserted after validation rules are dropped');

  testDB.dropDatabase();
  assert.eq(0, testDB.bar.count(), 'after the drop, no documents should be seen');

  // restore the 1000 records again with bypassDocumentValidation turned on
  ret = toolTest.runTool.apply(toolTest, ['restore', '--bypassDocumentValidation']
    .concat(getRestoreTarget(dumpTarget))
    .concat(commonToolArgs));
  assert.eq(0, ret, 'restoring documents should work with bypass document validation set');
  assert.eq(1000, testDB.bar.count(),
    'all documents should be restored with bypass document validation set');
  testDB.dropDatabase();

  /**
   * Part 5: Test that restore will stop inserting when getting validation errors if --stopOnError is enabled.
   */
  // turn on validation
  r = testDB.createCollection('bar', {validator: {baz: {$exists: true}}});
  assert.eq(r, {ok: 1}, 'create collection with validation should work');

  // test that we cannot insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 0, 'invalid documents should not be inserted');

  // restore the 1000 records again with bypassDocumentValidation turned on
  ret = toolTest.runTool.apply(toolTest, ['restore',
    '--file', toolTest.extFile,
    '--db', testDB.toString(),
    '-c', 'bar',
    '--stopOnError']
    .concat(commonToolArgs));
  assert.neq(0, ret,
    'restoring documents should report documentation validation errors when using --stopOnError');
  testDB.dropDatabase();

  /**
   * Part 6: Test that restore will stop inserting when getting validation errors if --maintainInsertionOrder is enabled.
   */
  r = testDB.createCollection('bar', {validator: {baz: {$exists: true}}});
  assert.eq(r, {ok: 1}, 'create collection with validation should work');

  // test that we cannot insert an 'invalid' document
  r = testDB.bar.insert({num: 10000});
  assert.eq(r.nInserted, 0, 'invalid documents should not be inserted');

  // import the 1000 records again with bypassDocumentValidation turned on
  ret = toolTest.runTool.apply(toolTest, ['restore',
    '--file', toolTest.extFile,
    '--db', 'test',
    '-c', 'bar',
    '--maintainInsertionOrder']
    .concat(commonToolArgs));
  assert.neq(0, ret, 'restoring documents should report documentation validation errors when using --maintainInsertionOrder');
}());
