From ca4d81143b77b4850f942258aa7b68eee920e431 Mon Sep 17 00:00:00 2001 From: Neal Beeken Date: Tue, 29 Oct 2024 14:42:29 -0400 Subject: [PATCH] DRIVERS-2124: test that inserts and upserts respect null _id values --- .github/workflows/json-regenerate-check.yml | 13 +- .gitignore | 3 +- .../crud/tests/unified/create-id-types.json | 283 ++++++++++++++++++ source/crud/tests/unified/create-id-types.yml | 57 ++++ source/etc/generate-crud-id-type-tests.mjs | 104 +++++++ 5 files changed, 453 insertions(+), 7 deletions(-) create mode 100644 source/crud/tests/unified/create-id-types.json create mode 100644 source/crud/tests/unified/create-id-types.yml create mode 100644 source/etc/generate-crud-id-type-tests.mjs diff --git a/.github/workflows/json-regenerate-check.yml b/.github/workflows/json-regenerate-check.yml index f4863dc4b4..b464050383 100644 --- a/.github/workflows/json-regenerate-check.yml +++ b/.github/workflows/json-regenerate-check.yml @@ -11,17 +11,18 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: - python-version: 3.7 + python-version: '3.x' - name: Set up npm - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: - node-version: 12 + node-version: lts/* - name: Install dependencies run: | + npm init -y && npm install js-yaml npm install -g js-yaml pip3 install pymongo pyyaml jinja2 - name: Regenerate JSON test files @@ -31,8 +32,8 @@ jobs: python3 ./source/client-side-encryption/etc/generate-test.py ./source/client-side-encryption/etc/test-templates/*.template ./source/client-side-encryption/tests/legacy python3 ./source/client-side-operations-timeout/etc/generate-basic-tests.py ./source/client-side-operations-timeout/etc/templates ./source/client-side-operations-timeout/tests python3 ./source/etc/generate-handshakeError-tests.py + node ./source/etc/generate-crud-id-type-tests.mjs cd source && make -B - - name: "Commit the changes" uses: stefanzweifel/git-auto-commit-action@v5 with: diff --git a/.gitignore b/.gitignore index 3ef5ceb240..df48de9a34 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ docs_build .pytest_cache node_modules package-lock.json -site \ No newline at end of file +package.json +site diff --git a/source/crud/tests/unified/create-id-types.json b/source/crud/tests/unified/create-id-types.json new file mode 100644 index 0000000000..627ef6b620 --- /dev/null +++ b/source/crud/tests/unified/create-id-types.json @@ -0,0 +1,283 @@ +{ + "description": "CRUD ID Type Tests", + "schemaVersion": "1.0", + "createEntities": [ + { + "client": { + "id": "client", + "observeEvents": [ + "commandStartedEvent" + ] + } + }, + { + "database": { + "id": "database", + "client": "client", + "databaseName": "crud_id" + } + }, + { + "collection": { + "id": "collection", + "database": "database", + "collectionName": "type_tests" + } + } + ], + "tests": [ + { + "description": "inserting _id with type null via insertOne", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "insertOne", + "object": "collection", + "arguments": { + "document": { + "_id": null + } + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via insertMany", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "insertMany", + "object": "collection", + "arguments": { + "documents": [ + { + "_id": null + } + ] + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via updateOne", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "updateOne", + "object": "collection", + "arguments": { + "filter": { + "_id": null + }, + "update": { + "$unset": { + "a": "" + } + }, + "upsert": true + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via updateMany", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "updateMany", + "object": "collection", + "arguments": { + "filter": { + "_id": null + }, + "update": { + "$unset": { + "a": "" + } + }, + "upsert": true + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via replaceOne", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "replaceOne", + "object": "collection", + "arguments": { + "filter": {}, + "replacement": { + "_id": null + }, + "upsert": true + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via bulkWrite", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "bulkWrite", + "object": "collection", + "arguments": { + "requests": [ + { + "insertOne": { + "document": { + "_id": null + } + } + } + ] + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + }, + { + "description": "inserting _id with type null via clientBulkWrite", + "operations": [ + { + "name": "dropCollection", + "object": "database", + "arguments": { + "collection": "type_tests" + } + }, + { + "name": "clientBulkWrite", + "object": "client", + "arguments": { + "models": [ + { + "namespace": "crud_id.type_tests", + "insertOne": { + "document": { + "_id": null + } + } + } + ] + } + } + ], + "outcome": [ + { + "databaseName": "crud_id", + "collectionName": "type_tests", + "documents": [ + { + "_id": null + } + ] + } + ] + } + ] +} diff --git a/source/crud/tests/unified/create-id-types.yml b/source/crud/tests/unified/create-id-types.yml new file mode 100644 index 0000000000..3e15661663 --- /dev/null +++ b/source/crud/tests/unified/create-id-types.yml @@ -0,0 +1,57 @@ +description: CRUD ID Type Tests +schemaVersion: '1.0' +createEntities: + - client: + id: client + observeEvents: [commandStartedEvent] + - database: + id: database + client: client + databaseName: crud_id + - collection: + id: collection + database: database + collectionName: type_tests +tests: + - description: inserting _id with type null via insertOne + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: insertOne, object: collection, arguments: {document: {_id: null}}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via insertMany + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: insertMany, object: collection, arguments: {documents: [{_id: null}]}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via updateOne + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: updateOne, object: collection, arguments: {filter: {_id: null}, update: {$unset: {a: ''}}, upsert: true}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via updateMany + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: updateMany, object: collection, arguments: {filter: {_id: null}, update: {$unset: {a: ''}}, upsert: true}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via replaceOne + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: replaceOne, object: collection, arguments: {filter: {}, replacement: {_id: null}, upsert: true}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via bulkWrite + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: bulkWrite, object: collection, arguments: {requests: [{insertOne: {document: {_id: null}}}]}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} + - description: inserting _id with type null via clientBulkWrite + operations: + - {name: dropCollection, object: database, arguments: {collection: type_tests}} + - {name: clientBulkWrite, object: client, arguments: {models: [{namespace: crud_id.type_tests, insertOne: {document: {_id: null}}}]}} + outcome: + - {databaseName: crud_id, collectionName: type_tests, documents: [{_id: null}]} diff --git a/source/etc/generate-crud-id-type-tests.mjs b/source/etc/generate-crud-id-type-tests.mjs new file mode 100644 index 0000000000..1bea5b5e95 --- /dev/null +++ b/source/etc/generate-crud-id-type-tests.mjs @@ -0,0 +1,104 @@ +import path from 'node:path'; +import fs from 'node:fs/promises'; +import yaml from 'js-yaml'; + +const operations = { + insertOne: (_id)=> { + return { + name: 'insertOne', + object: 'collection', + arguments: { document: { _id } } + } + }, + insertMany: (_id)=> { + return { + name: 'insertMany', + object: 'collection', + arguments: { documents: [{ _id }] } + } + }, + updateOne: (_id)=> { + return { + name: 'updateOne', + object: 'collection', + arguments: { filter: { _id }, update: { $unset: { a: '' } }, upsert: true } + } + }, + updateMany: (_id) => { + return { + name: 'updateMany', + object: 'collection', + arguments: { filter: { _id }, update: { $unset: { a: '' } }, upsert: true } + } + }, + replaceOne: (_id) => { + return { + name: 'replaceOne', + object: 'collection', + arguments: { filter: {}, replacement: { _id }, upsert: true } + } + }, + bulkWrite: (_id) => { + return { + name: 'bulkWrite', + object: 'collection', + arguments: { requests: [{ insertOne: { document: { _id } } }] } + } + }, + clientBulkWrite: (_id) => { + return { + name: 'clientBulkWrite', + object: 'client', + arguments: { models: [{ namespace: 'crud_id.type_tests', insertOne: { document: { _id } } }] } + } + }, +}; + +const idTypes = { + // double: { $numberDouble: 'NaN' }, + // string: '', + // object: {}, + // binData: { $binary: { base64: '', subType: '00' } }, + // undefined: { $undefined: true }, + // objectId: { $oid: '507f1f77bcf86cd799439011' }, + // bool: false, + // date: { $date: { $numberLong: '0' } }, + null: null, + // dbPointer: { $dbPointer: { $ref: '', $id: { $oid: '507f1f77bcf86cd799439011' } } }, + // javascript: { $javascript: '' }, + // javascriptWithScope: { $javascriptWithScope: { code: '', scope: { } } }, + // symbol: { $symbol: 'hello' }, + // int: { $numberInt: '0' }, + // timestamp: { $timestamp: { t: 0, i: 0 } }, + // long: { $numberLong: '0' }, + // decimal: { $numberDecimal: '0' }, + // minKey: { $minKey: 1 }, + // maxKey: { $maxKey: 1 }, +}; + +const illegalIdTypes = [ + { $regex: { pattern: 'abc', options: 'i' } }, + [], +]; + +const unifiedTestSuite = () => ({ + description: 'CRUD ID Type Tests', + schemaVersion: '1.0', + createEntities: [ + { client: { id: 'client', observeEvents: ["commandStartedEvent"] } }, + { database: { id: 'database', client: 'client', databaseName: 'crud_id' } }, + { collection: { id: 'collection', database: 'database', collectionName: 'type_tests' } }, + ], + tests: Object.entries(idTypes).flatMap(([idName, idValue]) => Object.entries(operations).flatMap(([operationName, operation]) => ({ + description: `inserting _id with type ${idName} via ${operationName}`, + operations: [{ name: 'dropCollection', object: 'database', arguments: { collection: 'type_tests' } }, operation(idValue)], + outcome: [{ databaseName: 'crud_id', collectionName: 'type_tests', documents: [{ _id: idValue }] }] // { _id: { $$type: idName } } - we need to change unified runners to use their matching code on outcome + }))) +}) + +const sourceDirectory = path.resolve(import.meta.dirname, '..'); +await fs.writeFile( + path.join(sourceDirectory, 'crud', 'tests', 'unified', 'create-id-types.yml'), + yaml.dump(unifiedTestSuite(), { lineWidth: 120, noRefs: true, flowLevel: 4, condenseFlow: false }), + 'utf8' +)