/**
 * Tests for the create_sharded_collection_util.js module.
 */
(function() {
"use strict";

load("jstests/sharding/libs/create_sharded_collection_util.js");
load("jstests/sharding/libs/find_chunks_util.js");

const st = new ShardingTest({mongos: 1, shards: 3, rs: {nodes: 1}});
const collection = st.s.getCollection("test.create_sharded_collection_util");

function assertCreatedWithChunks(shardKey, chunks) {
    collection.drop();
    CreateShardedCollectionUtil.shardCollectionWithChunks(collection, shardKey, chunks);

    const configDB = st.s.getDB("config");
    const actualChunks =
        findChunksUtil.findChunksByNs(configDB, collection.getFullName()).sort({min: 1}).toArray();
    assert.eq(chunks.slice().sort((a, b) => bsonWoCompare(a.min, b.min)),
              actualChunks.map(chunk => ({min: chunk.min, max: chunk.max, shard: chunk.shard})));

    // CreateShardedCollectionUtil.shardCollectionWithChunks() should have cleaned up any zones it
    // generated temporarily.
    assert.eq([], configDB.tags.find().toArray());
    assert.eq([], configDB.shards.find({tags: {$ne: []}}).toArray());
}

// Can create a chunk on each one of shards.
assertCreatedWithChunks({a: 1}, [
    {min: {a: MinKey}, max: {a: 0}, shard: st.shard0.shardName},
    {min: {a: 0}, max: {a: 10}, shard: st.shard1.shardName},
    {min: {a: 10}, max: {a: MaxKey}, shard: st.shard2.shardName},
]);

// Can omit creating chunks on one of the shards.
assertCreatedWithChunks({a: 1}, [
    {min: {a: MinKey}, max: {a: 0}, shard: st.shard0.shardName},
    {min: {a: 0}, max: {a: 10}, shard: st.shard0.shardName},
    {min: {a: 10}, max: {a: 20}, shard: st.shard1.shardName},
    {min: {a: 20}, max: {a: 30}, shard: st.shard1.shardName},
    {min: {a: 30}, max: {a: MaxKey}, shard: st.shard1.shardName},
]);

// Can have the input list of chunk ranges not be sorted.
assertCreatedWithChunks({a: 1}, [
    {min: {a: 0}, max: {a: 10}, shard: st.shard0.shardName},
    {min: {a: 30}, max: {a: MaxKey}, shard: st.shard0.shardName},
    {min: {a: 10}, max: {a: 20}, shard: st.shard0.shardName},
    {min: {a: MinKey}, max: {a: 0}, shard: st.shard0.shardName},
    {min: {a: 20}, max: {a: 30}, shard: st.shard0.shardName},
]);

// Can shard using compound shard key.
assertCreatedWithChunks({a: 1, b: 1}, [
    {min: {a: MinKey, b: MinKey}, max: {a: 0, b: 0}, shard: st.shard0.shardName},
    {min: {a: 0, b: 0}, max: {a: 0, b: 10}, shard: st.shard1.shardName},
    {min: {a: 0, b: 10}, max: {a: MaxKey, b: MaxKey}, shard: st.shard2.shardName},
]);

// Can shard using hashed shard key.
assertCreatedWithChunks({a: "hashed"}, [
    {min: {a: MinKey}, max: {a: NumberLong(0)}, shard: st.shard0.shardName},
    {min: {a: NumberLong(0)}, max: {a: NumberLong(10)}, shard: st.shard1.shardName},
    {min: {a: NumberLong(10)}, max: {a: MaxKey}, shard: st.shard2.shardName},
]);

function assertFailToCreateWithChunks(shardKey, chunks, errRegex) {
    collection.drop();
    const err = assert.throws(
        () => CreateShardedCollectionUtil.shardCollectionWithChunks(collection, shardKey, chunks));

    assert(errRegex.test(err.message),
           `${tojson(errRegex)} didn't match error message: ${tojson(err)}`);
}

// Cannot have chunks array be empty.
assertFailToCreateWithChunks({a: 1}, [], /empty/);

// Cannot omit MinKey chunk.
assertFailToCreateWithChunks({a: 1},
                             [
                                 {min: {a: 0}, max: {a: 10}, shard: st.shard1.shardName},
                                 {min: {a: 10}, max: {a: MaxKey}, shard: st.shard2.shardName},
                             ],
                             /MinKey/);

// Cannot omit MaxKey chunk.
assertFailToCreateWithChunks({a: 1},
                             [
                                 {min: {a: MinKey}, max: {a: 0}, shard: st.shard0.shardName},
                                 {min: {a: 0}, max: {a: 10}, shard: st.shard1.shardName},
                             ],
                             /MaxKey/);

// Cannot have any gaps between chunks.
assertFailToCreateWithChunks({a: 1},
                             [
                                 {min: {a: MinKey}, max: {a: 0}, shard: st.shard0.shardName},
                                 {min: {a: 10}, max: {a: MaxKey}, shard: st.shard2.shardName},
                             ],
                             /found gap/);

st.stop();
})();
