Skip to content

Commit 19ba74a

Browse files
authored
test: Operations needed for versioned API tests (#2717)
Adds operations to the unified test runner. NODE-2287
1 parent 5175359 commit 19ba74a

File tree

3 files changed

+183
-29
lines changed

3 files changed

+183
-29
lines changed

test/functional/unified-spec-runner/match.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { expect } from 'chai';
22
import { isDeepStrictEqual } from 'util';
3-
import { Binary, Document, Long, ObjectId } from '../../../src';
3+
import { Binary, Document, Long, ObjectId, MongoError } from '../../../src';
44
import {
55
CommandFailedEvent,
66
CommandStartedEvent,
77
CommandSucceededEvent
88
} from '../../../src/cmap/events';
99
import { CommandEvent, EntitiesMap } from './entities';
10-
import { ExpectedEvent } from './schema';
10+
import { ExpectedError, ExpectedEvent } from './schema';
1111

1212
export interface ExistsOperator {
1313
$$exists: boolean;
@@ -242,3 +242,60 @@ export function matchesEvents(
242242
}
243243
}
244244
}
245+
246+
export function expectErrorCheck(
247+
error: Error | MongoError,
248+
expected: ExpectedError,
249+
entities: EntitiesMap
250+
): boolean {
251+
if (Object.keys(expected)[0] === 'isClientError' || Object.keys(expected)[0] === 'isError') {
252+
// FIXME: We cannot tell if Error arose from driver and not from server
253+
return;
254+
}
255+
256+
if (expected.errorContains) {
257+
if (error.message.includes(expected.errorContains)) {
258+
throw new Error(
259+
`Error message was supposed to contain '${expected.errorContains}' but had '${error.message}'`
260+
);
261+
}
262+
}
263+
264+
if (!(error instanceof MongoError)) {
265+
throw new Error(`Assertions need ${error} to be a MongoError`);
266+
}
267+
268+
if (expected.errorCode) {
269+
if (error.code !== expected.errorCode) {
270+
throw new Error(`${error} was supposed to have code '${expected.errorCode}'`);
271+
}
272+
}
273+
274+
if (expected.errorCodeName) {
275+
if (error.codeName !== expected.errorCodeName) {
276+
throw new Error(`${error} was supposed to have '${expected.errorCodeName}' codeName`);
277+
}
278+
}
279+
280+
if (expected.errorLabelsContain) {
281+
for (const errorLabel of expected.errorLabelsContain) {
282+
if (!error.hasErrorLabel(errorLabel)) {
283+
throw new Error(`${error} was supposed to have '${errorLabel}'`);
284+
}
285+
}
286+
}
287+
288+
if (expected.errorLabelsOmit) {
289+
for (const errorLabel of expected.errorLabelsOmit) {
290+
if (error.hasErrorLabel(errorLabel)) {
291+
throw new Error(`${error} was not supposed to have '${errorLabel}'`);
292+
}
293+
}
294+
}
295+
296+
if (expected.expectResult) {
297+
if (!expectResultCheck(error, expected.expectResult, entities)) {
298+
throw new Error(`${error} supposed to match result ${JSON.stringify(expected.expectResult)}`);
299+
}
300+
}
301+
}

test/functional/unified-spec-runner/operations.ts

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
/* eslint-disable @typescript-eslint/no-unused-vars */
22
import { expect } from 'chai';
3-
import { ChangeStream, Document, InsertOneOptions, MongoError } from '../../../src';
3+
import { Collection, Db } from '../../../src';
4+
import { ChangeStream, Document, InsertOneOptions } from '../../../src';
5+
import { BulkWriteResult } from '../../../src/bulk/common';
46
import { EventCollector } from '../../tools/utils';
57
import { EntitiesMap } from './entities';
6-
import { expectResultCheck } from './match';
8+
import { expectErrorCheck, expectResultCheck } from './match';
79
import type * as uni from './schema';
810

9-
export class UnifiedOperation {
10-
name: string;
11-
constructor(op: uni.OperationDescription) {
12-
this.name = op.name;
13-
}
14-
}
15-
1611
async function abortTransactionOperation(
1712
entities: EntitiesMap,
1813
op: uni.OperationDescription
@@ -23,7 +18,22 @@ async function aggregateOperation(
2318
entities: EntitiesMap,
2419
op: uni.OperationDescription
2520
): Promise<Document> {
26-
throw new Error('not implemented.');
21+
const dbOrCollection = entities.get(op.object) as Db | Collection;
22+
if (!(dbOrCollection instanceof Db || dbOrCollection instanceof Collection)) {
23+
throw new Error(`Operation object '${op.object}' must be a db or collection`);
24+
}
25+
return dbOrCollection
26+
.aggregate(op.arguments.pipeline, {
27+
allowDiskUse: op.arguments.allowDiskUse,
28+
batchSize: op.arguments.batchSize,
29+
bypassDocumentValidation: op.arguments.bypassDocumentValidation,
30+
maxTimeMS: op.arguments.maxTimeMS,
31+
maxAwaitTimeMS: op.arguments.maxAwaitTimeMS,
32+
collation: op.arguments.collation,
33+
hint: op.arguments.hint,
34+
out: op.arguments.out
35+
})
36+
.toArray();
2737
}
2838
async function assertCollectionExistsOperation(
2939
entities: EntitiesMap,
@@ -94,14 +104,16 @@ async function assertSessionTransactionStateOperation(
94104
async function bulkWriteOperation(
95105
entities: EntitiesMap,
96106
op: uni.OperationDescription
97-
): Promise<Document> {
98-
throw new Error('not implemented.');
107+
): Promise<BulkWriteResult> {
108+
const collection = entities.getEntity('collection', op.object);
109+
return collection.bulkWrite(op.arguments.requests);
99110
}
100111
async function commitTransactionOperation(
101112
entities: EntitiesMap,
102113
op: uni.OperationDescription
103114
): Promise<Document> {
104-
throw new Error('not implemented.');
115+
const session = entities.getEntity('session', op.object);
116+
return session.commitTransaction();
105117
}
106118
async function createChangeStreamOperation(
107119
entities: EntitiesMap,
@@ -148,7 +160,8 @@ async function deleteOneOperation(
148160
entities: EntitiesMap,
149161
op: uni.OperationDescription
150162
): Promise<Document> {
151-
throw new Error('not implemented.');
163+
const collection = entities.getEntity('collection', op.object);
164+
return collection.deleteOne(op.arguments.filter);
152165
}
153166
async function dropCollectionOperation(
154167
entities: EntitiesMap,
@@ -168,19 +181,24 @@ async function findOperation(
168181
): Promise<Document> {
169182
const collection = entities.getEntity('collection', op.object);
170183
const { filter, sort, batchSize, limit } = op.arguments;
171-
return await collection.find(filter, { sort, batchSize, limit }).toArray();
184+
return collection.find(filter, { sort, batchSize, limit }).toArray();
172185
}
173186
async function findOneAndReplaceOperation(
174187
entities: EntitiesMap,
175188
op: uni.OperationDescription
176189
): Promise<Document> {
177-
throw new Error('not implemented.');
190+
const collection = entities.getEntity('collection', op.object);
191+
return collection.findOneAndReplace(op.arguments.filter, op.arguments.replacement);
178192
}
179193
async function findOneAndUpdateOperation(
180194
entities: EntitiesMap,
181195
op: uni.OperationDescription
182196
): Promise<Document> {
183-
throw new Error('not implemented.');
197+
const collection = entities.getEntity('collection', op.object);
198+
const returnOriginal = op.arguments.returnDocument === 'Before';
199+
return (
200+
await collection.findOneAndUpdate(op.arguments.filter, op.arguments.update, { returnOriginal })
201+
).value;
184202
}
185203
async function failPointOperation(
186204
entities: EntitiesMap,
@@ -201,7 +219,7 @@ async function insertOneOperation(
201219
session
202220
} as InsertOneOptions;
203221

204-
return await collection.insertOne(op.arguments.document, options);
222+
return collection.insertOne(op.arguments.document, options);
205223
}
206224
async function insertManyOperation(
207225
entities: EntitiesMap,
@@ -216,7 +234,7 @@ async function insertManyOperation(
216234
ordered: op.arguments.ordered ?? true
217235
};
218236

219-
return await collection.insertMany(op.arguments.documents, options);
237+
return collection.insertMany(op.arguments.documents, options);
220238
}
221239
async function iterateUntilDocumentOrErrorOperation(
222240
entities: EntitiesMap,
@@ -239,13 +257,20 @@ async function replaceOneOperation(
239257
entities: EntitiesMap,
240258
op: uni.OperationDescription
241259
): Promise<Document> {
242-
throw new Error('not implemented.');
260+
const collection = entities.getEntity('collection', op.object);
261+
return collection.replaceOne(op.arguments.filter, op.arguments.replacement, {
262+
bypassDocumentValidation: op.arguments.bypassDocumentValidation,
263+
collation: op.arguments.collation,
264+
hint: op.arguments.hint,
265+
upsert: op.arguments.upsert
266+
});
243267
}
244268
async function startTransactionOperation(
245269
entities: EntitiesMap,
246270
op: uni.OperationDescription
247-
): Promise<Document> {
248-
throw new Error('not implemented.');
271+
): Promise<void> {
272+
const session = entities.getEntity('session', op.object);
273+
session.startTransaction();
249274
}
250275
async function targetedFailPointOperation(
251276
entities: EntitiesMap,
@@ -277,8 +302,67 @@ async function withTransactionOperation(
277302
): Promise<Document> {
278303
throw new Error('not implemented.');
279304
}
305+
async function countDocumentsOperation(
306+
entities: EntitiesMap,
307+
op: uni.OperationDescription
308+
): Promise<number> {
309+
const collection = entities.getEntity('collection', op.object);
310+
return collection.countDocuments(op.arguments.filter as Document);
311+
}
312+
async function deleteManyOperation(
313+
entities: EntitiesMap,
314+
op: uni.OperationDescription
315+
): Promise<Document> {
316+
const collection = entities.getEntity('collection', op.object);
317+
return collection.deleteMany(op.arguments.filter);
318+
}
319+
async function distinctOperation(
320+
entities: EntitiesMap,
321+
op: uni.OperationDescription
322+
): Promise<Document> {
323+
const collection = entities.getEntity('collection', op.object);
324+
return collection.distinct(op.arguments.fieldName as string, op.arguments.filter as Document);
325+
}
326+
async function estimatedDocumentCountOperation(
327+
entities: EntitiesMap,
328+
op: uni.OperationDescription
329+
): Promise<number> {
330+
const collection = entities.getEntity('collection', op.object);
331+
return collection.estimatedDocumentCount();
332+
}
333+
async function findOneAndDeleteOperation(
334+
entities: EntitiesMap,
335+
op: uni.OperationDescription
336+
): Promise<Document> {
337+
const collection = entities.getEntity('collection', op.object);
338+
return collection.findOneAndDelete(op.arguments.filter);
339+
}
340+
async function runCommandOperation(
341+
entities: EntitiesMap,
342+
op: uni.OperationDescription
343+
): Promise<Document> {
344+
const db = entities.getEntity('db', op.object);
345+
return db.command(op.arguments.command);
346+
}
347+
async function updateManyOperation(
348+
entities: EntitiesMap,
349+
op: uni.OperationDescription
350+
): Promise<Document> {
351+
const collection = entities.getEntity('collection', op.object);
352+
return collection.updateMany(op.arguments.filter, op.arguments.update);
353+
}
354+
async function updateOneOperation(
355+
entities: EntitiesMap,
356+
op: uni.OperationDescription
357+
): Promise<Document> {
358+
const collection = entities.getEntity('collection', op.object);
359+
return collection.updateOne(op.arguments.filter, op.arguments.update);
360+
}
280361

281-
type RunOperationFn = (entities: EntitiesMap, op: uni.OperationDescription) => Promise<Document>;
362+
type RunOperationFn = (
363+
entities: EntitiesMap,
364+
op: uni.OperationDescription
365+
) => Promise<Document | number | void>;
282366
export const operations = new Map<string, RunOperationFn>();
283367

284368
operations.set('abortTransaction', abortTransactionOperation);
@@ -321,6 +405,16 @@ operations.set('download', downloadOperation);
321405
operations.set('upload', uploadOperation);
322406
operations.set('withTransaction', withTransactionOperation);
323407

408+
// Versioned API adds these:
409+
operations.set('countDocuments', countDocumentsOperation);
410+
operations.set('deleteMany', deleteManyOperation);
411+
operations.set('distinct', distinctOperation);
412+
operations.set('estimatedDocumentCount', estimatedDocumentCountOperation);
413+
operations.set('findOneAndDelete', findOneAndDeleteOperation);
414+
operations.set('runCommand', runCommandOperation);
415+
operations.set('updateMany', updateManyOperation);
416+
operations.set('updateOne', updateOneOperation);
417+
324418
export async function executeOperationAndCheck(
325419
operation: uni.OperationDescription,
326420
entities: EntitiesMap
@@ -333,9 +427,12 @@ export async function executeOperationAndCheck(
333427
try {
334428
result = await opFunc(entities, operation);
335429
} catch (error) {
430+
// FIXME: Remove when project is done:
431+
if (error.message === 'not implemented.') {
432+
throw error;
433+
}
336434
if (operation.expectError) {
337-
expect(error).to.be.instanceof(MongoError);
338-
// expectErrorCheck(error, operation.expectError);
435+
expectErrorCheck(error, operation.expectError, entities);
339436
} else {
340437
expect.fail(`Operation ${operation.name} failed with ${error.message}`);
341438
}

test/functional/unified-spec-runner/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export interface ExpectedError {
140140
errorContains?: string;
141141
errorCode?: number;
142142
errorCodeName?: string;
143-
errorLabelsContain?: [string, ...string[]];
144-
errorLabelsOmit?: [string, ...string[]];
143+
errorLabelsContain?: string[];
144+
errorLabelsOmit?: string[];
145145
expectResult?: unknown;
146146
}

0 commit comments

Comments
 (0)