/**
 * Tests that invalid view definitions in system.views do not impact valid commands on sharded
 * collections.
 */

(function() {
"use strict";

function runTest(st, badViewDefinition) {
    const mongos = st.s;
    const config = mongos.getDB("config");
    const db = mongos.getDB("invalid_system_views");
    assert.commandWorked(db.dropDatabase());

    assert.commandWorked(config.adminCommand({enableSharding: db.getName()}));
    st.ensurePrimaryShard(db.getName(), st.shard0.shardName);

    // Create sharded and unsharded collections, then insert an invalid view into system.views.
    const viewsCollection = db.getCollection("coll");
    const staticCollection = db.getCollection("staticCollection");
    assert.commandWorked(
        config.adminCommand({shardCollection: viewsCollection.getFullName(), key: {a: 1}}));
    assert.commandWorked(
        config.adminCommand({shardCollection: staticCollection.getFullName(), key: {a: 1}}));

    assert.commandWorked(viewsCollection.createIndex({x: 1}));

    const unshardedColl = db.getCollection("unshardedColl");
    assert.commandWorked(unshardedColl.insert({b: "boo"}));

    // applyOps is not available on mongos, so we use it to insert into the system.views collection
    // directly on each shard.
    [st.shard0, st.shard1].forEach(shard => {
        assert.commandWorked(shard.getDB(db.getName()).createCollection("system.views"));
        assert.commandWorked(
            shard.adminCommand(
                {applyOps: [{op: "i", ns: db.getName() + ".system.views", o: badViewDefinition}]}),
            "failed to insert " + tojson(badViewDefinition));
    });

    // Test that a command involving views properly fails with a views-specific error code.
    assert.commandFailedWithCode(
        db.runCommand({listCollections: 1}),
        ErrorCodes.InvalidViewDefinition,
        "listCollections should have failed in the presence of an invalid view");

    // Helper function to create a message to use if an assertion fails.
    function makeErrorMessage(msg) {
        return msg +
            " should work on a valid, existing collection, despite the presence of bad views" +
            " in system.views";
    }

    assert.commandWorked(viewsCollection.insert({y: "baz", a: 5}), makeErrorMessage("insert"));

    assert.commandWorked(viewsCollection.update({y: "baz"}, {$set: {y: "qux"}}),
                         makeErrorMessage("update"));

    assert.commandWorked(viewsCollection.remove({y: "baz"}), makeErrorMessage("remove"));

    assert.commandWorked(
        db.runCommand(
            {findAndModify: viewsCollection.getName(), query: {x: 1, a: 1}, update: {x: 2}}),
        makeErrorMessage("findAndModify with update"));

    assert.commandWorked(
        db.runCommand(
            {findAndModify: viewsCollection.getName(), query: {x: 2, a: 1}, remove: true}),
        makeErrorMessage("findAndModify with remove"));

    const lookup = {
        $lookup:
            {from: unshardedColl.getName(), localField: "_id", foreignField: "_id", as: "match"}
    };
    assert.commandWorked(
        db.runCommand({aggregate: viewsCollection.getName(), pipeline: [lookup], cursor: {}}),
        makeErrorMessage("aggregate with $lookup"));

    const graphLookup = {
            $graphLookup: {
                from: unshardedColl.getName(),
                startWith: "$_id",
                connectFromField: "_id",
                connectToField: "_id",
                as: "match"
            }
        };
    assert.commandWorked(
        db.runCommand({aggregate: viewsCollection.getName(), pipeline: [graphLookup], cursor: {}}),
        makeErrorMessage("aggregate with $graphLookup"));

    assert.commandWorked(db.runCommand({dropIndexes: viewsCollection.getName(), index: "x_1"}),
                         makeErrorMessage("dropIndexes"));

    assert.commandWorked(viewsCollection.createIndex({x: 1}), makeErrorMessage("createIndexes"));

    assert.commandWorked(
        db.runCommand({collMod: viewsCollection.getName(), validator: {x: {$type: "string"}}}),
        makeErrorMessage("collMod"));

    assert.commandWorked(db.runCommand({drop: viewsCollection.getName()}),
                         makeErrorMessage("drop"));
    assert.commandWorked(db.runCommand({drop: staticCollection.getName()}),
                         makeErrorMessage("drop"));

    // An invalid view in the view catalog should not interfere with attempting to run operations on
    // nonexistent collections.
    assert.commandWorked(db.runCommand({drop: staticCollection.getName()}),
                         makeErrorMessage("drop"));

    assert.commandWorked(db.runCommand({drop: unshardedColl.getName()}), makeErrorMessage("drop"));

    // Drop the offending view so that the validate hook succeeds.
    [st.shard0, st.shard1].forEach(shard => {
        assert(shard.getDB(db.getName())["system.views"].drop());
    });
}

const st = new ShardingTest({name: "views_sharded", shards: 2, other: {enableBalancer: false}});

runTest(st, {_id: "invalid_system_views.badViewStringPipeline", viewOn: "coll", pipeline: "bad"});
runTest(st, {_id: "invalid_system_views.badViewEmptyObjectPipeline", viewOn: "coll", pipeline: {}});
runTest(st, {_id: "invalid_system_views.badViewNumericalPipeline", viewOn: "coll", pipeline: 7});
runTest(
    st,
    {_id: "invalid_system_views.badViewArrayWithIntegerPipeline", viewOn: "coll", pipeline: [1]});
runTest(st, {
    _id: "invalid_system_views.badViewArrayWithEmptyArrayPipeline",
    viewOn: "coll",
    pipeline: [[]]
});
runTest(st, {_id: 7, viewOn: "coll", pipeline: []});
runTest(st, {_id: "invalid_system_views.embedded\0null", viewOn: "coll", pipeline: []});
runTest(st, {_id: "invalidNotFullyQualifiedNs", viewOn: "coll", pipeline: []});
runTest(st, {_id: "invalid_system_views.missingViewOnField", pipeline: []});

st.stop();
}());
