Skip to content

Commit 0b47a01

Browse files
authored
test: Add unified spec runner scaffolding (#2689)
Using the Unified spec test schema this scaffolding outlines the structure of a unified runner. Most tests are skipped by the runOn requirements or not implemented errors thrown by empty operation functions. NODE-2287
1 parent de06784 commit 0b47a01

File tree

11 files changed

+1080
-10
lines changed

11 files changed

+1080
-10
lines changed

.mocharc.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
{
2-
"extension": ["js"],
3-
"require": ["ts-node/register", "source-map-support/register"],
2+
"$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/mocharc.json",
3+
"extension": [
4+
"js",
5+
"ts"
6+
],
7+
"require": [
8+
"ts-node/register",
9+
"source-map-support/register"
10+
],
411
"file": "test/tools/runner",
512
"ui": "test/tools/runner/metadata_ui.js",
613
"recursive": true,

package-lock.json

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,13 @@
3434
"@microsoft/tsdoc-config": "^0.13.9",
3535
"@types/aws4": "^1.5.1",
3636
"@types/bl": "^2.1.0",
37+
"@types/chai": "^4.2.14",
38+
"@types/chai-subset": "^1.3.3",
3739
"@types/kerberos": "^1.1.0",
3840
"@types/mocha": "^8.2.0",
3941
"@types/node": "^14.6.4",
4042
"@types/saslprep": "^1.0.0",
43+
"@types/semver": "^7.3.4",
4144
"@typescript-eslint/eslint-plugin": "^3.10.0",
4245
"@typescript-eslint/parser": "^3.10.0",
4346
"chai": "^4.2.0",
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"parser": "@typescript-eslint/parser",
3+
"parserOptions": {
4+
"ecmaVersion": 2018
5+
},
6+
"plugins": [
7+
"@typescript-eslint",
8+
"prettier",
9+
"promise",
10+
"eslint-plugin-tsdoc"
11+
],
12+
"extends": [
13+
"eslint:recommended",
14+
"plugin:@typescript-eslint/eslint-recommended",
15+
"plugin:@typescript-eslint/recommended",
16+
"prettier/@typescript-eslint",
17+
"plugin:prettier/recommended"
18+
],
19+
"env": {
20+
"node": true,
21+
"mocha": true,
22+
"es6": true
23+
},
24+
"rules": {
25+
"prettier/prettier": "error",
26+
"tsdoc/syntax": "warn"
27+
}
28+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { MongoClient, Db, Collection, GridFSBucket, Document } from '../../../src/index';
2+
import { ClientSession } from '../../../src/sessions';
3+
import { ChangeStream } from '../../../src/change_stream';
4+
import type { ClientEntity, EntityDescription } from './schema';
5+
import type {
6+
CommandFailedEvent,
7+
CommandStartedEvent,
8+
CommandSucceededEvent
9+
} from '../../../src/cmap/events';
10+
import { patchCollectionOptions, patchDbOptions } from './unified-utils';
11+
import { TestConfiguration } from './unified.test';
12+
13+
export type CommandEvent = CommandStartedEvent | CommandSucceededEvent | CommandFailedEvent;
14+
15+
export class UnifiedMongoClient extends MongoClient {
16+
events: CommandEvent[];
17+
observedEvents: ('commandStarted' | 'commandSucceeded' | 'commandFailed')[];
18+
19+
static EVENT_NAME_LOOKUP = {
20+
commandStartedEvent: 'commandStarted',
21+
commandSucceededEvent: 'commandSucceeded',
22+
commandFailedEvent: 'commandFailed'
23+
} as const;
24+
25+
constructor(url: string, description: ClientEntity) {
26+
super(url, { monitorCommands: true, ...description.uriOptions });
27+
this.events = [];
28+
// apm
29+
this.observedEvents = (description.observeEvents ?? []).map(
30+
e => UnifiedMongoClient.EVENT_NAME_LOOKUP[e]
31+
);
32+
for (const eventName of this.observedEvents) {
33+
this.on(eventName, this.pushEvent);
34+
}
35+
}
36+
37+
// NOTE: this must be an arrow function for `this` to work.
38+
pushEvent: (e: CommandEvent) => void = e => {
39+
this.events.push(e);
40+
};
41+
42+
/** Disables command monitoring for the client and returns a list of the captured events. */
43+
stopCapturingEvents(): CommandEvent[] {
44+
for (const eventName of this.observedEvents) {
45+
this.off(eventName, this.pushEvent);
46+
}
47+
return this.events;
48+
}
49+
}
50+
51+
export type Entity =
52+
| UnifiedMongoClient
53+
| Db
54+
| Collection
55+
| ClientSession
56+
| ChangeStream
57+
| GridFSBucket
58+
| Document; // Results from operations
59+
60+
export type EntityCtor =
61+
| typeof UnifiedMongoClient
62+
| typeof Db
63+
| typeof Collection
64+
| typeof ClientSession
65+
| typeof ChangeStream
66+
| typeof GridFSBucket;
67+
68+
export type EntityTypeId = 'client' | 'db' | 'collection' | 'session' | 'bucket' | 'stream';
69+
70+
const ENTITY_CTORS = new Map<EntityTypeId, EntityCtor>();
71+
ENTITY_CTORS.set('client', UnifiedMongoClient);
72+
ENTITY_CTORS.set('db', Db);
73+
ENTITY_CTORS.set('collection', Collection);
74+
ENTITY_CTORS.set('session', ClientSession);
75+
ENTITY_CTORS.set('bucket', GridFSBucket);
76+
ENTITY_CTORS.set('stream', ChangeStream);
77+
78+
export class EntitiesMap<E = Entity> extends Map<string, E> {
79+
mapOf(type: 'client'): EntitiesMap<UnifiedMongoClient>;
80+
mapOf(type: 'db'): EntitiesMap<Db>;
81+
mapOf(type: 'collection'): EntitiesMap<Collection>;
82+
mapOf(type: 'session'): EntitiesMap<ClientSession>;
83+
mapOf(type: 'bucket'): EntitiesMap<GridFSBucket>;
84+
mapOf(type: 'stream'): EntitiesMap<ChangeStream>;
85+
mapOf(type: EntityTypeId): EntitiesMap<Entity> {
86+
const ctor = ENTITY_CTORS.get(type);
87+
if (!ctor) {
88+
throw new Error(`Unknown type ${type}`);
89+
}
90+
return new EntitiesMap(Array.from(this.entries()).filter(([, e]) => e instanceof ctor));
91+
}
92+
93+
getEntity(type: 'client', key: string, assertExists?: boolean): UnifiedMongoClient;
94+
getEntity(type: 'db', key: string, assertExists?: boolean): Db;
95+
getEntity(type: 'collection', key: string, assertExists?: boolean): Collection;
96+
getEntity(type: 'session', key: string, assertExists?: boolean): ClientSession;
97+
getEntity(type: 'bucket', key: string, assertExists?: boolean): GridFSBucket;
98+
getEntity(type: 'stream', key: string, assertExists?: boolean): ChangeStream;
99+
getEntity(type: EntityTypeId, key: string, assertExists = true): Entity {
100+
const entity = this.get(key);
101+
if (!entity) {
102+
if (assertExists) throw new Error(`Entity ${key} does not exist`);
103+
return;
104+
}
105+
const ctor = ENTITY_CTORS.get(type);
106+
if (!ctor) {
107+
throw new Error(`Unknown type ${type}`);
108+
}
109+
if (!(entity instanceof ctor)) {
110+
throw new Error(`${key} is not an instance of ${type}`);
111+
}
112+
return entity;
113+
}
114+
115+
async cleanup(): Promise<void> {
116+
for (const [, client] of this.mapOf('client')) {
117+
await client.close();
118+
}
119+
for (const [, session] of this.mapOf('session')) {
120+
await session.endSession();
121+
}
122+
this.clear();
123+
}
124+
125+
static async createEntities(
126+
config: TestConfiguration,
127+
entities?: EntityDescription[]
128+
): Promise<EntitiesMap> {
129+
const map = new EntitiesMap();
130+
for (const entity of entities ?? []) {
131+
if ('client' in entity) {
132+
const client = new UnifiedMongoClient(config.url(), entity.client);
133+
await client.connect();
134+
map.set(entity.client.id, client);
135+
} else if ('database' in entity) {
136+
const client = map.getEntity('client', entity.database.client);
137+
const db = client.db(
138+
entity.database.databaseName,
139+
patchDbOptions(entity.database.databaseOptions)
140+
);
141+
map.set(entity.database.id, db);
142+
} else if ('collection' in entity) {
143+
const db = map.getEntity('db', entity.collection.database);
144+
const collection = db.collection(
145+
entity.collection.collectionName,
146+
patchCollectionOptions(entity.collection.collectionOptions)
147+
);
148+
map.set(entity.collection.id, collection);
149+
} else if ('session' in entity) {
150+
map.set(entity.session.id, null);
151+
} else if ('bucket' in entity) {
152+
map.set(entity.bucket.id, null);
153+
} else if ('stream' in entity) {
154+
map.set(entity.stream.id, null);
155+
} else {
156+
throw new Error(`Unsupported Entity ${JSON.stringify(entity)}`);
157+
}
158+
}
159+
return map;
160+
}
161+
}

0 commit comments

Comments
 (0)