Skip to content

test: Operations needed for versioned API tests #2717

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

Merged
merged 3 commits into from
Jan 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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;
}