Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(NODE-5342): modernize explain tests #3720

Merged
merged 26 commits into from
Jun 28, 2023
Merged
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
70e6d40
changed to typescript
malikj2000 Jun 7, 2023
7224009
fixed formatting
malikj2000 Jun 7, 2023
1d80c77
test(NODE-5342): modernize explain tests
malikj2000 Jun 9, 2023
2fbe538
grouped aggregates under context block
malikj2000 Jun 13, 2023
a0f5d10
fixed formatting
malikj2000 Jun 13, 2023
9705cc6
fixed catchable error context block description
malikj2000 Jun 14, 2023
8cd25f1
fixed it clause
malikj2000 Jun 14, 2023
18b169c
Merge branch 'main' into NODE-5342
W-A-James Jun 14, 2023
2601c7b
fixed identical test cases
malikj2000 Jun 15, 2023
dcc5742
changed true and false context titles to verbosity
malikj2000 Jun 20, 2023
116d04d
Added all operations, is still giving test errors
malikj2000 Jun 23, 2023
479de71
missing aggregate
malikj2000 Jun 23, 2023
5cd0b2e
added aggregate testing
malikj2000 Jun 26, 2023
39a29b6
combined names and ops
malikj2000 Jun 26, 2023
880ad39
fixed server version error for aggregate
malikj2000 Jun 27, 2023
862f3f8
fixed more aggregate errors
malikj2000 Jun 27, 2023
211ecdf
aggregate
malikj2000 Jun 27, 2023
3b9e57f
fixed aggregate errors
malikj2000 Jun 27, 2023
29e8736
removed .only
malikj2000 Jun 27, 2023
783f17f
cleaned up explainDocument check
malikj2000 Jun 28, 2023
074d075
bug fix
malikj2000 Jun 28, 2023
2439c8c
added optional chaining
malikj2000 Jun 28, 2023
baeab10
removed .only
malikj2000 Jun 28, 2023
d05b2cc
specified it blocks
malikj2000 Jun 28, 2023
6b96803
Merge branch 'main' into NODE-5342
W-A-James Jun 28, 2023
3ce249b
removed .only
malikj2000 Jun 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
340 changes: 154 additions & 186 deletions test/integration/crud/explain.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,29 @@
import { expect } from 'chai';

import {
type Collection,
type CommandStartedEvent,
type Db,
type MongoClient,
MongoServerError
} from '../../mongodb';
import { type Collection, type Db, type MongoClient, MongoServerError } from '../../mongodb';

describe('Explain', function () {
dariakp marked this conversation as resolved.
Show resolved Hide resolved
let client: MongoClient;
let db: Db;
let collection: Collection;
let commandsStarted: CommandStartedEvent[];

beforeEach(async function () {
client = this.configuration.newClient({ monitorCommands: true });
db = client.db('queryPlannerExplainResult');
collection = db.collection('test');
commandsStarted = [];

await collection.insertOne({ a: 1 });
client.on('commandStarted', event => commandsStarted.push(event));
});

afterEach(async function () {
await collection.drop();
await client.close();
commandsStarted = [];
});

context('when explain is set to true', () => {
it('deleteOne returns queryPlanner explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
expect(commandsStarted[0]).to.have.nested.property('command.explain.delete');
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
});

it('deleteMany returns queryPlanner explain result', async function () {
Expand Down Expand Up @@ -80,203 +69,182 @@ describe('Explain', function () {
const explanation = await collection.findOne({ a: 1 }, { explain: true });
expect(explanation).property('queryPlanner').to.exist;
});

it('find returns queryPlanner explain result', async () => {
const [explanation] = await collection.find({ a: 1 }, { explain: true }).toArray();
expect(explanation).property('queryPlanner').to.exist;
});
});

context('when explain is not set to true', () => {
context('when explain is set to false', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: false });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).to.not.have.property('executionStats');
});

it('find returns queryPlanner explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain(false);
expect(explanation).property('queryPlanner').to.exist;
});
context('when explain is set to false', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: false });
expect(explanation).property('queryPlanner').to.exist;
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
});

context('when explain is set to queryPlanner', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'queryPlanner' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).to.not.have.property('executionStats');
});

it('findOneAndReplace returns queryPlanner explain result', async function () {
const explanation = await collection.findOneAndReplace(
{ a: 1 },
{ a: 2 },
{ explain: 'queryPlanner' }
);
expect(explanation).property('queryPlanner').to.exist;
});
it('find returns "queryPlanner" explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain(false);
expect(explanation).property('queryPlanner').to.exist;
});
});

context('when explain is set to executionStats', () => {
it('executionStats property is used in explain result', async function () {
const explanation = await collection.deleteMany({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).to.not.have.nested.property('executionStats.allPlansExecution');
});

it('distinct returns executionStats explain result', async function () {
const explanation = await collection.distinct('a', {}, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

it('find returns executionStats explain result', async function () {
const [explanation] = await collection
.find({ a: 1 }, { explain: 'executionStats' })
.toArray();
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

it('findOne returns executionStats explain result', async function () {
const explanation = await collection.findOne({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
context('when explain is set to "queryPlanner"', () => {
it('only queryPlanner property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'queryPlanner' });
expect(explanation).property('queryPlanner').to.exist;
dariakp marked this conversation as resolved.
Show resolved Hide resolved
});

context('when explain is set to allPlansExecution', () => {
it('allPlansExecution property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'allPlansExecution' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
});

it('find returns allPlansExecution explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain('allPlansExecution');
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
it('findOneAndReplace returns queryPlanner explain result', async function () {
const explanation = await collection.findOneAndReplace(
{ a: 1 },
{ a: 2 },
{ explain: 'queryPlanner' }
);
expect(explanation).property('queryPlanner').to.exist;
});
});

it('find returns queryPlanner explain result', async () => {
const [explanation] = await collection.find({ a: 1 }, { explain: true }).toArray();
expect(explanation).property('queryPlanner').to.exist;
});
context('when explain is set to "executionStats"', () => {
it('"executionStats" property is used in explain result', async function () {
const explanation = await collection.deleteMany({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).to.not.have.nested.property('executionStats.allPlansExecution');
});

it('should honor boolean explain with aggregate', async function () {
const db = client.db('shouldHonorBooleanExplainWithAggregate');
const collection = db.collection('test');
await collection.insertOne({ a: 1 });
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true })
.toArray();

if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
});
it('distinct returns executionStats explain result', async function () {
const explanation = await collection.distinct('a', {}, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

it('should honor string explain with aggregate', async function () {
const db = client.db('shouldHonorStringExplainWithAggregate');
const collection = db.collection('test');
it('find returns executionStats explain result', async function () {
const [explanation] = await collection
.find({ a: 1 }, { explain: 'executionStats' })
.toArray();
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});

await collection.insertOne({ a: 1 });
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], {
explain: 'executionStats'
})
.toArray();
if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
it('findOne returns executionStats explain result', async function () {
const explanation = await collection.findOne({ a: 1 }, { explain: 'executionStats' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
});

it('should honor boolean explain specified on cursor with aggregate', async function () {
const db = client.db('shouldHonorBooleanExplainSpecifiedOnCursor');
const collection = db.collection('test');
context('when explain is set to "allPlansExecution"', () => {
it('allPlansExecution property is used in explain result', async function () {
const explanation = await collection.deleteOne({ a: 1 }, { explain: 'allPlansExecution' });
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
expect(explanation).nested.property('executionStats.allPlansExecution').to.exist;
});

await collection.insertOne({ a: 1 });
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain(false);
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.not.have.property('executionStats');
} else if (aggResult.$cursor) {
expect(aggResult.$cursor).to.have.property('queryPlanner');
expect(aggResult.$cursor).to.not.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.not.have.property('executionStats');
}
it('find returns allPlansExecution explain result specified on cursor', async function () {
const explanation = await collection.find({ a: 1 }).explain('allPlansExecution');
expect(explanation).property('queryPlanner').to.exist;
expect(explanation).property('executionStats').to.exist;
});
});

it('should honor string explain specified on cursor with aggregate', async function () {
const db = client.db('shouldHonorStringExplainSpecifiedOnCursor');
const collection = db.collection('test');
context('aggregate()', () => {
it('when explain is set to true, aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], { explain: true })
.toArray();

if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
});

await collection.insertOne({ a: 1 });
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain('allPlansExecution');

if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
});
it('when explain is set to "executionStats", aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }], {
explain: 'executionStats'
})
.toArray();
if (aggResult[0].stages) {
expect(aggResult[0].stages).to.have.length.gte(1);
expect(aggResult[0].stages[0]).to.have.property('$cursor');
expect(aggResult[0].stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].stages[0].$cursor).to.have.property('executionStats');
} else if (aggResult[0].$cursor) {
expect(aggResult[0].$cursor).to.have.property('queryPlanner');
expect(aggResult[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult[0]).to.have.property('queryPlanner');
expect(aggResult[0]).to.have.property('executionStats');
}
});

it('should honor legacy explain with aggregate', async function () {
const db = client.db('shouldHonorLegacyExplainWithAggregate');
const collection = db.collection('test');
it('when explain is set to false, aggregate result returns queryPlanner property', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain(false);
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.not.have.property('executionStats');
} else if (aggResult.$cursor) {
expect(aggResult.$cursor).to.have.property('queryPlanner');
expect(aggResult.$cursor).to.not.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.not.have.property('executionStats');
}
});

await collection.insertOne({ a: 1 });
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain();
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
it('when explain is set to "allPlansExecution", aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain('allPlansExecution');

if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
});

it('when explain is not set, aggregate result returns queryPlanner and executionStats properties', async function () {
const aggResult = await collection
.aggregate([{ $project: { a: 1 } }, { $group: { _id: '$a' } }])
.explain();
if (aggResult && aggResult.stages) {
expect(aggResult.stages).to.have.length.gte(1);
expect(aggResult.stages[0]).to.have.property('$cursor');
expect(aggResult.stages[0].$cursor).to.have.property('queryPlanner');
expect(aggResult.stages[0].$cursor).to.have.property('executionStats');
} else {
expect(aggResult).to.have.property('queryPlanner');
expect(aggResult).to.have.property('executionStats');
}
});
});

it('should throw a catchable error with invalid explain string', async function () {
const db = client.db('shouldThrowCatchableError');
const collection = db.collection('test');
const error = await collection
.find({ a: 1 })
.explain('invalidExplain')
.catch(error => error);
expect(error).to.be.instanceOf(MongoServerError);
context('when explain is set to "invalidExplain", result returns MongoServerError', () => {
W-A-James marked this conversation as resolved.
Show resolved Hide resolved
it('should throw a catchable error with invalid explain string', async function () {
const error = await collection
.find({ a: 1 })
.explain('invalidExplain')
.catch(error => error);
expect(error).to.be.instanceOf(MongoServerError);
});
});
});