Skip to content

Commit

Permalink
test: Operations needed for versioned API tests (#2717)
Browse files Browse the repository at this point in the history
Adds operations to the unified test runner.

NODE-2287
  • Loading branch information
nbbeeken authored Jan 29, 2021
1 parent 5175359 commit 19ba74a
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 29 deletions.
61 changes: 59 additions & 2 deletions test/functional/unified-spec-runner/match.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { expect } from 'chai';
import { isDeepStrictEqual } from 'util';
import { Binary, Document, Long, ObjectId } from '../../../src';
import { Binary, Document, Long, ObjectId, MongoError } from '../../../src';
import {
CommandFailedEvent,
CommandStartedEvent,
CommandSucceededEvent
} from '../../../src/cmap/events';
import { CommandEvent, EntitiesMap } from './entities';
import { ExpectedEvent } from './schema';
import { ExpectedError, ExpectedEvent } from './schema';

export interface ExistsOperator {
$$exists: boolean;
Expand Down Expand Up @@ -242,3 +242,60 @@ export function matchesEvents(
}
}
}

export function expectErrorCheck(
error: Error | MongoError,
expected: ExpectedError,
entities: EntitiesMap
): boolean {
if (Object.keys(expected)[0] === 'isClientError' || Object.keys(expected)[0] === 'isError') {
// FIXME: We cannot tell if Error arose from driver and not from server
return;
}

if (expected.errorContains) {
if (error.message.includes(expected.errorContains)) {
throw new Error(
`Error message was supposed to contain '${expected.errorContains}' but had '${error.message}'`
);
}
}

if (!(error instanceof MongoError)) {
throw new Error(`Assertions need ${error} to be a MongoError`);
}

if (expected.errorCode) {
if (error.code !== expected.errorCode) {
throw new Error(`${error} was supposed to have code '${expected.errorCode}'`);
}
}

if (expected.errorCodeName) {
if (error.codeName !== expected.errorCodeName) {
throw new Error(`${error} was supposed to have '${expected.errorCodeName}' codeName`);
}
}

if (expected.errorLabelsContain) {
for (const errorLabel of expected.errorLabelsContain) {
if (!error.hasErrorLabel(errorLabel)) {
throw new Error(`${error} was supposed to have '${errorLabel}'`);
}
}
}

if (expected.errorLabelsOmit) {
for (const errorLabel of expected.errorLabelsOmit) {
if (error.hasErrorLabel(errorLabel)) {
throw new Error(`${error} was not supposed to have '${errorLabel}'`);
}
}
}

if (expected.expectResult) {
if (!expectResultCheck(error, expected.expectResult, entities)) {
throw new Error(`${error} supposed to match result ${JSON.stringify(expected.expectResult)}`);
}
}
}
147 changes: 122 additions & 25 deletions test/functional/unified-spec-runner/operations.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { expect } from 'chai';
import { ChangeStream, Document, InsertOneOptions, MongoError } from '../../../src';
import { Collection, Db } from '../../../src';
import { ChangeStream, Document, InsertOneOptions } from '../../../src';
import { BulkWriteResult } from '../../../src/bulk/common';
import { EventCollector } from '../../tools/utils';
import { EntitiesMap } from './entities';
import { expectResultCheck } from './match';
import { expectErrorCheck, expectResultCheck } from './match';
import type * as uni from './schema';

export class UnifiedOperation {
name: string;
constructor(op: uni.OperationDescription) {
this.name = op.name;
}
}

async function abortTransactionOperation(
entities: EntitiesMap,
op: uni.OperationDescription
Expand All @@ -23,7 +18,22 @@ async function aggregateOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const dbOrCollection = entities.get(op.object) as Db | Collection;
if (!(dbOrCollection instanceof Db || dbOrCollection instanceof Collection)) {
throw new Error(`Operation object '${op.object}' must be a db or collection`);
}
return dbOrCollection
.aggregate(op.arguments.pipeline, {
allowDiskUse: op.arguments.allowDiskUse,
batchSize: op.arguments.batchSize,
bypassDocumentValidation: op.arguments.bypassDocumentValidation,
maxTimeMS: op.arguments.maxTimeMS,
maxAwaitTimeMS: op.arguments.maxAwaitTimeMS,
collation: op.arguments.collation,
hint: op.arguments.hint,
out: op.arguments.out
})
.toArray();
}
async function assertCollectionExistsOperation(
entities: EntitiesMap,
Expand Down Expand Up @@ -94,14 +104,16 @@ async function assertSessionTransactionStateOperation(
async function bulkWriteOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
): Promise<BulkWriteResult> {
const collection = entities.getEntity('collection', op.object);
return collection.bulkWrite(op.arguments.requests);
}
async function commitTransactionOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const session = entities.getEntity('session', op.object);
return session.commitTransaction();
}
async function createChangeStreamOperation(
entities: EntitiesMap,
Expand Down Expand Up @@ -148,7 +160,8 @@ async function deleteOneOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const collection = entities.getEntity('collection', op.object);
return collection.deleteOne(op.arguments.filter);
}
async function dropCollectionOperation(
entities: EntitiesMap,
Expand All @@ -168,19 +181,24 @@ async function findOperation(
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
const { filter, sort, batchSize, limit } = op.arguments;
return await collection.find(filter, { sort, batchSize, limit }).toArray();
return collection.find(filter, { sort, batchSize, limit }).toArray();
}
async function findOneAndReplaceOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const collection = entities.getEntity('collection', op.object);
return collection.findOneAndReplace(op.arguments.filter, op.arguments.replacement);
}
async function findOneAndUpdateOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const collection = entities.getEntity('collection', op.object);
const returnOriginal = op.arguments.returnDocument === 'Before';
return (
await collection.findOneAndUpdate(op.arguments.filter, op.arguments.update, { returnOriginal })
).value;
}
async function failPointOperation(
entities: EntitiesMap,
Expand All @@ -201,7 +219,7 @@ async function insertOneOperation(
session
} as InsertOneOptions;

return await collection.insertOne(op.arguments.document, options);
return collection.insertOne(op.arguments.document, options);
}
async function insertManyOperation(
entities: EntitiesMap,
Expand All @@ -216,7 +234,7 @@ async function insertManyOperation(
ordered: op.arguments.ordered ?? true
};

return await collection.insertMany(op.arguments.documents, options);
return collection.insertMany(op.arguments.documents, options);
}
async function iterateUntilDocumentOrErrorOperation(
entities: EntitiesMap,
Expand All @@ -239,13 +257,20 @@ async function replaceOneOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
const collection = entities.getEntity('collection', op.object);
return collection.replaceOne(op.arguments.filter, op.arguments.replacement, {
bypassDocumentValidation: op.arguments.bypassDocumentValidation,
collation: op.arguments.collation,
hint: op.arguments.hint,
upsert: op.arguments.upsert
});
}
async function startTransactionOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
throw new Error('not implemented.');
): Promise<void> {
const session = entities.getEntity('session', op.object);
session.startTransaction();
}
async function targetedFailPointOperation(
entities: EntitiesMap,
Expand Down Expand Up @@ -277,8 +302,67 @@ async function withTransactionOperation(
): Promise<Document> {
throw new Error('not implemented.');
}
async function countDocumentsOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<number> {
const collection = entities.getEntity('collection', op.object);
return collection.countDocuments(op.arguments.filter as Document);
}
async function deleteManyOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
return collection.deleteMany(op.arguments.filter);
}
async function distinctOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
return collection.distinct(op.arguments.fieldName as string, op.arguments.filter as Document);
}
async function estimatedDocumentCountOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<number> {
const collection = entities.getEntity('collection', op.object);
return collection.estimatedDocumentCount();
}
async function findOneAndDeleteOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
return collection.findOneAndDelete(op.arguments.filter);
}
async function runCommandOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const db = entities.getEntity('db', op.object);
return db.command(op.arguments.command);
}
async function updateManyOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
return collection.updateMany(op.arguments.filter, op.arguments.update);
}
async function updateOneOperation(
entities: EntitiesMap,
op: uni.OperationDescription
): Promise<Document> {
const collection = entities.getEntity('collection', op.object);
return collection.updateOne(op.arguments.filter, op.arguments.update);
}

type RunOperationFn = (entities: EntitiesMap, op: uni.OperationDescription) => Promise<Document>;
type RunOperationFn = (
entities: EntitiesMap,
op: uni.OperationDescription
) => Promise<Document | number | void>;
export const operations = new Map<string, RunOperationFn>();

operations.set('abortTransaction', abortTransactionOperation);
Expand Down Expand Up @@ -321,6 +405,16 @@ operations.set('download', downloadOperation);
operations.set('upload', uploadOperation);
operations.set('withTransaction', withTransactionOperation);

// Versioned API adds these:
operations.set('countDocuments', countDocumentsOperation);
operations.set('deleteMany', deleteManyOperation);
operations.set('distinct', distinctOperation);
operations.set('estimatedDocumentCount', estimatedDocumentCountOperation);
operations.set('findOneAndDelete', findOneAndDeleteOperation);
operations.set('runCommand', runCommandOperation);
operations.set('updateMany', updateManyOperation);
operations.set('updateOne', updateOneOperation);

export async function executeOperationAndCheck(
operation: uni.OperationDescription,
entities: EntitiesMap
Expand All @@ -333,9 +427,12 @@ export async function executeOperationAndCheck(
try {
result = await opFunc(entities, operation);
} catch (error) {
// FIXME: Remove when project is done:
if (error.message === 'not implemented.') {
throw error;
}
if (operation.expectError) {
expect(error).to.be.instanceof(MongoError);
// expectErrorCheck(error, operation.expectError);
expectErrorCheck(error, operation.expectError, entities);
} else {
expect.fail(`Operation ${operation.name} failed with ${error.message}`);
}
Expand Down
4 changes: 2 additions & 2 deletions test/functional/unified-spec-runner/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export interface ExpectedError {
errorContains?: string;
errorCode?: number;
errorCodeName?: string;
errorLabelsContain?: [string, ...string[]];
errorLabelsOmit?: [string, ...string[]];
errorLabelsContain?: string[];
errorLabelsOmit?: string[];
expectResult?: unknown;
}

0 comments on commit 19ba74a

Please sign in to comment.