From de6f2cc3e47ef3b5e4776fc4ac16aeb56cd76136 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Mon, 20 Jul 2020 16:02:15 -0400 Subject: [PATCH 01/26] copied and pasted from pymongo --- test/__init__.py | 869 +++++++++++++++++ test/certificates/ca.pem | 21 + test/certificates/client.pem | 48 + test/certificates/crl.pem | 13 + test/certificates/password_protected.pem | 51 + test/certificates/server.pem | 49 + test/certificates/trusted-ca.pem | 82 ++ test/crud/v1/read/aggregate-collation.json | 38 + test/crud/v1/read/aggregate-out.json | 121 +++ test/crud/v1/read/aggregate.json | 53 + test/crud/v1/read/count-collation.json | 47 + test/crud/v1/read/count-empty.json | 39 + test/crud/v1/read/count.json | 112 +++ test/crud/v1/read/distinct-collation.json | 33 + test/crud/v1/read/distinct.json | 55 ++ test/crud/v1/read/find-collation.json | 34 + test/crud/v1/read/find.json | 105 ++ .../crud/v1/write/bulkWrite-arrayFilters.json | 111 +++ test/crud/v1/write/bulkWrite-collation.json | 217 +++++ test/crud/v1/write/bulkWrite.json | 778 +++++++++++++++ test/crud/v1/write/deleteMany-collation.json | 47 + test/crud/v1/write/deleteMany.json | 76 ++ test/crud/v1/write/deleteOne-collation.json | 51 + test/crud/v1/write/deleteOne.json | 96 ++ .../v1/write/findOneAndDelete-collation.json | 59 ++ test/crud/v1/write/findOneAndDelete.json | 127 +++ .../v1/write/findOneAndReplace-collation.json | 58 ++ .../v1/write/findOneAndReplace-upsert.json | 201 ++++ test/crud/v1/write/findOneAndReplace.json | 273 ++++++ .../write/findOneAndUpdate-arrayFilters.json | 203 ++++ .../v1/write/findOneAndUpdate-collation.json | 67 ++ test/crud/v1/write/findOneAndUpdate.json | 379 ++++++++ test/crud/v1/write/insertMany.json | 159 +++ test/crud/v1/write/insertOne.json | 39 + test/crud/v1/write/replaceOne-collation.json | 53 + test/crud/v1/write/replaceOne.json | 205 ++++ .../v1/write/updateMany-arrayFilters.json | 185 ++++ test/crud/v1/write/updateMany-collation.json | 62 ++ test/crud/v1/write/updateMany.json | 183 ++++ .../crud/v1/write/updateOne-arrayFilters.json | 395 ++++++++ test/crud/v1/write/updateOne-collation.json | 54 ++ test/crud/v1/write/updateOne.json | 167 ++++ test/crud/v2/aggregate-merge.json | 415 ++++++++ test/crud/v2/aggregate-out-readConcern.json | 385 ++++++++ test/crud/v2/bulkWrite-arrayFilters.json | 226 +++++ .../v2/bulkWrite-delete-hint-clientError.json | 150 +++ .../v2/bulkWrite-delete-hint-serverError.json | 209 ++++ test/crud/v2/bulkWrite-delete-hint.json | 204 ++++ .../v2/bulkWrite-update-hint-clientError.json | 235 +++++ .../v2/bulkWrite-update-hint-serverError.json | 343 +++++++ test/crud/v2/bulkWrite-update-hint.json | 366 +++++++ test/crud/v2/db-aggregate.json | 81 ++ test/crud/v2/deleteMany-hint-clientError.json | 100 ++ test/crud/v2/deleteMany-hint-serverError.json | 141 +++ test/crud/v2/deleteMany-hint.json | 128 +++ test/crud/v2/deleteOne-hint-clientError.json | 84 ++ test/crud/v2/deleteOne-hint-serverError.json | 121 +++ test/crud/v2/deleteOne-hint.json | 116 +++ .../v2/find-allowdiskuse-clientError.json | 40 + .../v2/find-allowdiskuse-serverError.json | 61 ++ test/crud/v2/find-allowdiskuse.json | 78 ++ .../v2/findOneAndDelete-hint-clientError.json | 84 ++ .../v2/findOneAndDelete-hint-serverError.json | 113 +++ test/crud/v2/findOneAndDelete-hint.json | 110 +++ .../findOneAndReplace-hint-clientError.json | 90 ++ .../findOneAndReplace-hint-serverError.json | 123 +++ test/crud/v2/findOneAndReplace-hint.json | 128 +++ .../v2/findOneAndUpdate-hint-clientError.json | 94 ++ .../v2/findOneAndUpdate-hint-serverError.json | 131 +++ test/crud/v2/findOneAndUpdate-hint.json | 136 +++ test/crud/v2/replaceOne-hint.json | 146 +++ ...ged-bulkWrite-delete-hint-clientError.json | 155 +++ ...ged-bulkWrite-update-hint-clientError.json | 245 +++++ ...nowledged-deleteMany-hint-clientError.json | 105 ++ ...knowledged-deleteOne-hint-clientError.json | 89 ++ ...ged-findOneAndDelete-hint-clientError.json | 89 ++ ...ed-findOneAndReplace-hint-clientError.json | 95 ++ ...ged-findOneAndUpdate-hint-clientError.json | 99 ++ ...nowledged-replaceOne-hint-clientError.json | 99 ++ ...nowledged-updateMany-hint-clientError.json | 115 +++ ...knowledged-updateOne-hint-clientError.json | 103 ++ test/crud/v2/updateMany-hint-clientError.json | 110 +++ test/crud/v2/updateMany-hint-serverError.json | 161 ++++ test/crud/v2/updateMany-hint.json | 168 ++++ test/crud/v2/updateOne-hint-clientError.json | 98 ++ test/crud/v2/updateOne-hint-serverError.json | 147 +++ test/crud/v2/updateOne-hint.json | 154 +++ test/crud/v2/updateWithPipelines.json | 408 ++++++++ test/test_crud_v2.py | 72 ++ test/utils.py | 910 ++++++++++++++++++ test/utils_spec_runner.py | 694 +++++++++++++ test/version.py | 88 ++ 92 files changed, 14857 insertions(+) create mode 100644 test/certificates/ca.pem create mode 100644 test/certificates/client.pem create mode 100644 test/certificates/crl.pem create mode 100644 test/certificates/password_protected.pem create mode 100644 test/certificates/server.pem create mode 100644 test/certificates/trusted-ca.pem create mode 100644 test/crud/v1/read/aggregate-collation.json create mode 100644 test/crud/v1/read/aggregate-out.json create mode 100644 test/crud/v1/read/aggregate.json create mode 100644 test/crud/v1/read/count-collation.json create mode 100644 test/crud/v1/read/count-empty.json create mode 100644 test/crud/v1/read/count.json create mode 100644 test/crud/v1/read/distinct-collation.json create mode 100644 test/crud/v1/read/distinct.json create mode 100644 test/crud/v1/read/find-collation.json create mode 100644 test/crud/v1/read/find.json create mode 100644 test/crud/v1/write/bulkWrite-arrayFilters.json create mode 100644 test/crud/v1/write/bulkWrite-collation.json create mode 100644 test/crud/v1/write/bulkWrite.json create mode 100644 test/crud/v1/write/deleteMany-collation.json create mode 100644 test/crud/v1/write/deleteMany.json create mode 100644 test/crud/v1/write/deleteOne-collation.json create mode 100644 test/crud/v1/write/deleteOne.json create mode 100644 test/crud/v1/write/findOneAndDelete-collation.json create mode 100644 test/crud/v1/write/findOneAndDelete.json create mode 100644 test/crud/v1/write/findOneAndReplace-collation.json create mode 100644 test/crud/v1/write/findOneAndReplace-upsert.json create mode 100644 test/crud/v1/write/findOneAndReplace.json create mode 100644 test/crud/v1/write/findOneAndUpdate-arrayFilters.json create mode 100644 test/crud/v1/write/findOneAndUpdate-collation.json create mode 100644 test/crud/v1/write/findOneAndUpdate.json create mode 100644 test/crud/v1/write/insertMany.json create mode 100644 test/crud/v1/write/insertOne.json create mode 100644 test/crud/v1/write/replaceOne-collation.json create mode 100644 test/crud/v1/write/replaceOne.json create mode 100644 test/crud/v1/write/updateMany-arrayFilters.json create mode 100644 test/crud/v1/write/updateMany-collation.json create mode 100644 test/crud/v1/write/updateMany.json create mode 100644 test/crud/v1/write/updateOne-arrayFilters.json create mode 100644 test/crud/v1/write/updateOne-collation.json create mode 100644 test/crud/v1/write/updateOne.json create mode 100644 test/crud/v2/aggregate-merge.json create mode 100644 test/crud/v2/aggregate-out-readConcern.json create mode 100644 test/crud/v2/bulkWrite-arrayFilters.json create mode 100644 test/crud/v2/bulkWrite-delete-hint-clientError.json create mode 100644 test/crud/v2/bulkWrite-delete-hint-serverError.json create mode 100644 test/crud/v2/bulkWrite-delete-hint.json create mode 100644 test/crud/v2/bulkWrite-update-hint-clientError.json create mode 100644 test/crud/v2/bulkWrite-update-hint-serverError.json create mode 100644 test/crud/v2/bulkWrite-update-hint.json create mode 100644 test/crud/v2/db-aggregate.json create mode 100644 test/crud/v2/deleteMany-hint-clientError.json create mode 100644 test/crud/v2/deleteMany-hint-serverError.json create mode 100644 test/crud/v2/deleteMany-hint.json create mode 100644 test/crud/v2/deleteOne-hint-clientError.json create mode 100644 test/crud/v2/deleteOne-hint-serverError.json create mode 100644 test/crud/v2/deleteOne-hint.json create mode 100644 test/crud/v2/find-allowdiskuse-clientError.json create mode 100644 test/crud/v2/find-allowdiskuse-serverError.json create mode 100644 test/crud/v2/find-allowdiskuse.json create mode 100644 test/crud/v2/findOneAndDelete-hint-clientError.json create mode 100644 test/crud/v2/findOneAndDelete-hint-serverError.json create mode 100644 test/crud/v2/findOneAndDelete-hint.json create mode 100644 test/crud/v2/findOneAndReplace-hint-clientError.json create mode 100644 test/crud/v2/findOneAndReplace-hint-serverError.json create mode 100644 test/crud/v2/findOneAndReplace-hint.json create mode 100644 test/crud/v2/findOneAndUpdate-hint-clientError.json create mode 100644 test/crud/v2/findOneAndUpdate-hint-serverError.json create mode 100644 test/crud/v2/findOneAndUpdate-hint.json create mode 100644 test/crud/v2/replaceOne-hint.json create mode 100644 test/crud/v2/unacknowledged-bulkWrite-delete-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-bulkWrite-update-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-deleteMany-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-deleteOne-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-findOneAndDelete-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-findOneAndReplace-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-findOneAndUpdate-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-replaceOne-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-updateMany-hint-clientError.json create mode 100644 test/crud/v2/unacknowledged-updateOne-hint-clientError.json create mode 100644 test/crud/v2/updateMany-hint-clientError.json create mode 100644 test/crud/v2/updateMany-hint-serverError.json create mode 100644 test/crud/v2/updateMany-hint.json create mode 100644 test/crud/v2/updateOne-hint-clientError.json create mode 100644 test/crud/v2/updateOne-hint-serverError.json create mode 100644 test/crud/v2/updateOne-hint.json create mode 100644 test/crud/v2/updateWithPipelines.json create mode 100644 test/test_crud_v2.py create mode 100644 test/utils.py create mode 100644 test/utils_spec_runner.py create mode 100644 test/version.py diff --git a/test/__init__.py b/test/__init__.py index eb417f8..1e756ed 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -11,3 +11,872 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. + +"""Test suite for pymongo, bson, and gridfs. +""" + +import gc +import os +import socket +import sys +import threading +import time +import unittest +import warnings + +try: + from xmlrunner import XMLTestRunner + HAVE_XML = True +# ValueError is raised when version 3+ is installed on Jython 2.7. +except (ImportError, ValueError): + HAVE_XML = False + +try: + import ipaddress + HAVE_IPADDRESS = True +except ImportError: + HAVE_IPADDRESS = False + +from contextlib import contextmanager +from functools import wraps +from unittest import SkipTest + +import pymongo +import pymongo.errors + +from bson.son import SON +from pymongo import common, message +from pymongo.common import partition_node +from pymongo.ssl_support import HAVE_SSL, validate_cert_reqs +from test.version import Version + +if HAVE_SSL: + import ssl + +try: + # Enable the fault handler to dump the traceback of each running thread + # after a segfault. + import faulthandler + faulthandler.enable() +except ImportError: + pass + +# Enable debug output for uncollectable objects. PyPy does not have set_debug. +if hasattr(gc, 'set_debug'): + gc.set_debug( + gc.DEBUG_UNCOLLECTABLE | + getattr(gc, 'DEBUG_OBJECTS', 0) | + getattr(gc, 'DEBUG_INSTANCES', 0)) + +# The host and port of a single mongod or mongos, or the seed host +# for a replica set. +host = os.environ.get("DB_IP", 'localhost') +port = int(os.environ.get("DB_PORT", 27017)) + +db_user = os.environ.get("DB_USER", "user") +db_pwd = os.environ.get("DB_PASSWORD", "password") + +CERT_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), + 'certificates') +CLIENT_PEM = os.environ.get('CLIENT_PEM', + os.path.join(CERT_PATH, 'client.pem')) +CA_PEM = os.environ.get('CA_PEM', os.path.join(CERT_PATH, 'ca.pem')) + +TLS_OPTIONS = dict(tls=True) +if CLIENT_PEM: + TLS_OPTIONS['tlsCertificateKeyFile'] = CLIENT_PEM +if CA_PEM: + TLS_OPTIONS['tlsCAFile'] = CA_PEM + +COMPRESSORS = os.environ.get("COMPRESSORS") + +def is_server_resolvable(): + """Returns True if 'server' is resolvable.""" + socket_timeout = socket.getdefaulttimeout() + socket.setdefaulttimeout(1) + try: + try: + socket.gethostbyname('server') + return True + except socket.error: + return False + finally: + socket.setdefaulttimeout(socket_timeout) + + +def _create_user(authdb, user, pwd=None, roles=None, **kwargs): + cmd = SON([('createUser', user)]) + # X509 doesn't use a password + if pwd: + cmd['pwd'] = pwd + cmd['roles'] = roles or ['root'] + cmd.update(**kwargs) + return authdb.command(cmd) + + +class client_knobs(object): + def __init__( + self, + heartbeat_frequency=None, + min_heartbeat_interval=None, + kill_cursor_frequency=None, + events_queue_frequency=None): + self.heartbeat_frequency = heartbeat_frequency + self.min_heartbeat_interval = min_heartbeat_interval + self.kill_cursor_frequency = kill_cursor_frequency + self.events_queue_frequency = events_queue_frequency + + self.old_heartbeat_frequency = None + self.old_min_heartbeat_interval = None + self.old_kill_cursor_frequency = None + self.old_events_queue_frequency = None + + def enable(self): + self.old_heartbeat_frequency = common.HEARTBEAT_FREQUENCY + self.old_min_heartbeat_interval = common.MIN_HEARTBEAT_INTERVAL + self.old_kill_cursor_frequency = common.KILL_CURSOR_FREQUENCY + self.old_events_queue_frequency = common.EVENTS_QUEUE_FREQUENCY + + if self.heartbeat_frequency is not None: + common.HEARTBEAT_FREQUENCY = self.heartbeat_frequency + + if self.min_heartbeat_interval is not None: + common.MIN_HEARTBEAT_INTERVAL = self.min_heartbeat_interval + + if self.kill_cursor_frequency is not None: + common.KILL_CURSOR_FREQUENCY = self.kill_cursor_frequency + + if self.events_queue_frequency is not None: + common.EVENTS_QUEUE_FREQUENCY = self.events_queue_frequency + + def __enter__(self): + self.enable() + + def disable(self): + common.HEARTBEAT_FREQUENCY = self.old_heartbeat_frequency + common.MIN_HEARTBEAT_INTERVAL = self.old_min_heartbeat_interval + common.KILL_CURSOR_FREQUENCY = self.old_kill_cursor_frequency + common.EVENTS_QUEUE_FREQUENCY = self.old_events_queue_frequency + + def __exit__(self, exc_type, exc_val, exc_tb): + self.disable() + + +def _all_users(db): + return set(u['user'] for u in db.command('usersInfo').get('users', [])) + + +class ClientContext(object): + + def __init__(self): + """Create a client and grab essential information from the server.""" + self.connection_attempts = [] + self.connected = False + self.w = None + self.nodes = set() + self.replica_set_name = None + self.cmd_line = None + self.server_status = None + self.version = Version(-1) # Needs to be comparable with Version + self.auth_enabled = False + self.test_commands_enabled = False + self.is_mongos = False + self.mongoses = [] + self.is_rs = False + self.has_ipv6 = False + self.tls = False + self.ssl_certfile = False + self.server_is_resolvable = is_server_resolvable() + self.default_client_options = {} + self.sessions_enabled = False + self.client = None + self.conn_lock = threading.Lock() + + if COMPRESSORS: + self.default_client_options["compressors"] = COMPRESSORS + + @property + def ismaster(self): + return self.client.admin.command('isMaster') + + def _connect(self, host, port, **kwargs): + # Jython takes a long time to connect. + if sys.platform.startswith('java'): + timeout_ms = 10000 + else: + timeout_ms = 5000 + if COMPRESSORS: + kwargs["compressors"] = COMPRESSORS + client = pymongo.MongoClient( + host, port, serverSelectionTimeoutMS=timeout_ms, **kwargs) + try: + try: + client.admin.command('isMaster') # Can we connect? + except pymongo.errors.OperationFailure as exc: + # SERVER-32063 + self.connection_attempts.append( + 'connected client %r, but isMaster failed: %s' % ( + client, exc)) + else: + self.connection_attempts.append( + 'successfully connected client %r' % (client,)) + # If connected, then return client with default timeout + return pymongo.MongoClient(host, port, **kwargs) + except pymongo.errors.ConnectionFailure as exc: + self.connection_attempts.append( + 'failed to connect client %r: %s' % (client, exc)) + return None + + def _init_client(self): + self.client = self._connect(host, port) + if HAVE_SSL and not self.client: + # Is MongoDB configured for SSL? + self.client = self._connect(host, port, **TLS_OPTIONS) + if self.client: + self.tls = True + self.default_client_options.update(TLS_OPTIONS) + self.ssl_certfile = True + + if self.client: + self.connected = True + + try: + self.cmd_line = self.client.admin.command('getCmdLineOpts') + except pymongo.errors.OperationFailure as e: + msg = e.details.get('errmsg', '') + if e.code == 13 or 'unauthorized' in msg or 'login' in msg: + # Unauthorized. + self.auth_enabled = True + else: + raise + else: + self.auth_enabled = self._server_started_with_auth() + + if self.auth_enabled: + # See if db_user already exists. + if not self._check_user_provided(): + _create_user(self.client.admin, db_user, db_pwd) + + self.client = self._connect( + host, port, username=db_user, password=db_pwd, + replicaSet=self.replica_set_name, + **self.default_client_options) + + # May not have this if OperationFailure was raised earlier. + self.cmd_line = self.client.admin.command('getCmdLineOpts') + + self.server_status = self.client.admin.command('serverStatus') + if self.storage_engine == "mmapv1": + # MMAPv1 does not support retryWrites=True. + self.default_client_options['retryWrites'] = False + + ismaster = self.ismaster + self.sessions_enabled = 'logicalSessionTimeoutMinutes' in ismaster + + if 'setName' in ismaster: + self.replica_set_name = str(ismaster['setName']) + self.is_rs = True + if self.auth_enabled: + # It doesn't matter which member we use as the seed here. + self.client = pymongo.MongoClient( + host, + port, + username=db_user, + password=db_pwd, + replicaSet=self.replica_set_name, + **self.default_client_options) + else: + self.client = pymongo.MongoClient( + host, + port, + replicaSet=self.replica_set_name, + **self.default_client_options) + + # Get the authoritative ismaster result from the primary. + ismaster = self.ismaster + nodes = [partition_node(node.lower()) + for node in ismaster.get('hosts', [])] + nodes.extend([partition_node(node.lower()) + for node in ismaster.get('passives', [])]) + nodes.extend([partition_node(node.lower()) + for node in ismaster.get('arbiters', [])]) + self.nodes = set(nodes) + else: + self.nodes = set([(host, port)]) + self.w = len(ismaster.get("hosts", [])) or 1 + self.version = Version.from_client(self.client) + + if 'enableTestCommands=1' in self.cmd_line['argv']: + self.test_commands_enabled = True + elif 'parsed' in self.cmd_line: + params = self.cmd_line['parsed'].get('setParameter', []) + if 'enableTestCommands=1' in params: + self.test_commands_enabled = True + else: + params = self.cmd_line['parsed'].get('setParameter', {}) + if params.get('enableTestCommands') == '1': + self.test_commands_enabled = True + + self.is_mongos = (self.ismaster.get('msg') == 'isdbgrid') + self.has_ipv6 = self._server_started_with_ipv6() + if self.is_mongos: + # Check for another mongos on the next port. + address = self.client.address + next_address = address[0], address[1] + 1 + self.mongoses.append(address) + mongos_client = self._connect(*next_address, + **self.default_client_options) + if mongos_client: + ismaster = mongos_client.admin.command('ismaster') + if ismaster.get('msg') == 'isdbgrid': + self.mongoses.append(next_address) + + def init(self): + with self.conn_lock: + if not self.client and not self.connection_attempts: + self._init_client() + + def connection_attempt_info(self): + return '\n'.join(self.connection_attempts) + + @property + def host(self): + if self.is_rs: + primary = self.client.primary + return str(primary[0]) if primary is not None else host + return host + + @property + def port(self): + if self.is_rs: + primary = self.client.primary + return primary[1] if primary is not None else port + return port + + @property + def pair(self): + return "%s:%d" % (self.host, self.port) + + @property + def has_secondaries(self): + if not self.client: + return False + return bool(len(self.client.secondaries)) + + @property + def storage_engine(self): + try: + return self.server_status.get("storageEngine", {}).get("name") + except AttributeError: + # Raised if self.server_status is None. + return None + + def _check_user_provided(self): + """Return True if db_user/db_password is already an admin user.""" + client = pymongo.MongoClient( + host, port, + username=db_user, + password=db_pwd, + serverSelectionTimeoutMS=100, + **self.default_client_options) + + try: + return db_user in _all_users(client.admin) + except pymongo.errors.OperationFailure as e: + msg = e.details.get('errmsg', '') + if e.code == 18 or 'auth fails' in msg: + # Auth failed. + return False + else: + raise + + def _server_started_with_auth(self): + # MongoDB >= 2.0 + if 'parsed' in self.cmd_line: + parsed = self.cmd_line['parsed'] + # MongoDB >= 2.6 + if 'security' in parsed: + security = parsed['security'] + # >= rc3 + if 'authorization' in security: + return security['authorization'] == 'enabled' + # < rc3 + return (security.get('auth', False) or + bool(security.get('keyFile'))) + return parsed.get('auth', False) or bool(parsed.get('keyFile')) + # Legacy + argv = self.cmd_line['argv'] + return '--auth' in argv or '--keyFile' in argv + + def _server_started_with_ipv6(self): + if not socket.has_ipv6: + return False + + if 'parsed' in self.cmd_line: + if not self.cmd_line['parsed'].get('net', {}).get('ipv6'): + return False + else: + if '--ipv6' not in self.cmd_line['argv']: + return False + + # The server was started with --ipv6. Is there an IPv6 route to it? + try: + for info in socket.getaddrinfo(self.host, self.port): + if info[0] == socket.AF_INET6: + return True + except socket.error: + pass + + return False + + def _require(self, condition, msg, func=None): + def make_wrapper(f): + @wraps(f) + def wrap(*args, **kwargs): + self.init() + # Always raise SkipTest if we can't connect to MongoDB + if not self.connected: + raise SkipTest( + "Cannot connect to MongoDB on %s" % (self.pair,)) + if condition(): + return f(*args, **kwargs) + raise SkipTest(msg) + return wrap + + if func is None: + def decorate(f): + return make_wrapper(f) + return decorate + return make_wrapper(func) + + def create_user(self, dbname, user, pwd=None, roles=None, **kwargs): + kwargs['writeConcern'] = {'w': self.w} + return _create_user(self.client[dbname], user, pwd, roles, **kwargs) + + def drop_user(self, dbname, user): + self.client[dbname].command( + 'dropUser', user, writeConcern={'w': self.w}) + + def require_connection(self, func): + """Run a test only if we can connect to MongoDB.""" + return self._require( + lambda: True, # _require checks if we're connected + "Cannot connect to MongoDB on %s" % (self.pair,), + func=func) + + def require_no_mmap(self, func): + """Run a test only if the server is not using the MMAPv1 storage + engine. Only works for standalone and replica sets; tests are + run regardless of storage engine on sharded clusters. """ + def is_not_mmap(): + if self.is_mongos: + return True + return self.storage_engine != 'mmapv1' + + return self._require( + is_not_mmap, "Storage engine must not be MMAPv1", func=func) + + def require_version_min(self, *ver): + """Run a test only if the server version is at least ``version``.""" + other_version = Version(*ver) + return self._require(lambda: self.version >= other_version, + "Server version must be at least %s" + % str(other_version)) + + def require_version_max(self, *ver): + """Run a test only if the server version is at most ``version``.""" + other_version = Version(*ver) + return self._require(lambda: self.version <= other_version, + "Server version must be at most %s" + % str(other_version)) + + def require_auth(self, func): + """Run a test only if the server is running with auth enabled.""" + return self.check_auth_with_sharding( + self._require(lambda: self.auth_enabled, + "Authentication is not enabled on the server", + func=func)) + + def require_no_auth(self, func): + """Run a test only if the server is running without auth enabled.""" + return self._require(lambda: not self.auth_enabled, + "Authentication must not be enabled on the server", + func=func) + + def require_replica_set(self, func): + """Run a test only if the client is connected to a replica set.""" + return self._require(lambda: self.is_rs, + "Not connected to a replica set", + func=func) + + def require_secondaries_count(self, count): + """Run a test only if the client is connected to a replica set that has + `count` secondaries. + """ + def sec_count(): + return 0 if not self.client else len(self.client.secondaries) + return self._require(lambda: sec_count() >= count, + "Not enough secondaries available") + + def require_no_replica_set(self, func): + """Run a test if the client is *not* connected to a replica set.""" + return self._require( + lambda: not self.is_rs, + "Connected to a replica set, not a standalone mongod", + func=func) + + def require_ipv6(self, func): + """Run a test only if the client can connect to a server via IPv6.""" + return self._require(lambda: self.has_ipv6, + "No IPv6", + func=func) + + def require_no_mongos(self, func): + """Run a test only if the client is not connected to a mongos.""" + return self._require(lambda: not self.is_mongos, + "Must be connected to a mongod, not a mongos", + func=func) + + def require_mongos(self, func): + """Run a test only if the client is connected to a mongos.""" + return self._require(lambda: self.is_mongos, + "Must be connected to a mongos", + func=func) + + def require_multiple_mongoses(self, func): + """Run a test only if the client is connected to a sharded cluster + that has 2 mongos nodes.""" + return self._require(lambda: len(self.mongoses) > 1, + "Must have multiple mongoses available", + func=func) + + def require_standalone(self, func): + """Run a test only if the client is connected to a standalone.""" + return self._require(lambda: not (self.is_mongos or self.is_rs), + "Must be connected to a standalone", + func=func) + + def require_no_standalone(self, func): + """Run a test only if the client is not connected to a standalone.""" + return self._require(lambda: self.is_mongos or self.is_rs, + "Must be connected to a replica set or mongos", + func=func) + + def check_auth_with_sharding(self, func): + """Skip a test when connected to mongos < 2.0 and running with auth.""" + condition = lambda: not (self.auth_enabled and + self.is_mongos and self.version < (2,)) + return self._require(condition, + "Auth with sharding requires MongoDB >= 2.0.0", + func=func) + + def is_topology_type(self, topologies): + if 'single' in topologies and not (self.is_mongos or self.is_rs): + return True + if 'replicaset' in topologies and self.is_rs: + return True + if 'sharded' in topologies and self.is_mongos: + return True + return False + + def require_cluster_type(self, topologies=[]): + """Run a test only if the client is connected to a cluster that + conforms to one of the specified topologies. Acceptable topologies + are 'single', 'replicaset', and 'sharded'.""" + def _is_valid_topology(): + return self.is_topology_type(topologies) + return self._require( + _is_valid_topology, + "Cluster type not in %s" % (topologies)) + + def require_test_commands(self, func): + """Run a test only if the server has test commands enabled.""" + return self._require(lambda: self.test_commands_enabled, + "Test commands must be enabled", + func=func) + + def require_failCommand_fail_point(self, func): + """Run a test only if the server supports the failCommand fail + point.""" + return self._require(lambda: self.supports_failCommand_fail_point, + "failCommand fail point must be supported", + func=func) + + def require_tls(self, func): + """Run a test only if the client can connect over TLS.""" + return self._require(lambda: self.tls, + "Must be able to connect via TLS", + func=func) + + def require_no_tls(self, func): + """Run a test only if the client can connect over TLS.""" + return self._require(lambda: not self.tls, + "Must be able to connect without TLS", + func=func) + + def require_ssl_certfile(self, func): + """Run a test only if the client can connect with ssl_certfile.""" + return self._require(lambda: self.ssl_certfile, + "Must be able to connect with ssl_certfile", + func=func) + + def require_server_resolvable(self, func): + """Run a test only if the hostname 'server' is resolvable.""" + return self._require(lambda: self.server_is_resolvable, + "No hosts entry for 'server'. Cannot validate " + "hostname in the certificate", + func=func) + + def require_sessions(self, func): + """Run a test only if the deployment supports sessions.""" + return self._require(lambda: self.sessions_enabled, + "Sessions not supported", + func=func) + + def supports_transactions(self): + if self.storage_engine == 'mmapv1': + return False + + if self.version.at_least(4, 1, 8): + return self.is_mongos or self.is_rs + + if self.version.at_least(4, 0): + return self.is_rs + + return False + + def require_transactions(self, func): + """Run a test only if the deployment might support transactions. + + *Might* because this does not test the storage engine or FCV. + """ + return self._require(self.supports_transactions, + "Transactions are not supported", + func=func) + + def mongos_seeds(self): + return ','.join('%s:%s' % address for address in self.mongoses) + + @property + def supports_reindex(self): + """Does the connected server support reindex?""" + return not ((self.version.at_least(4, 1, 0) and self.is_mongos) or + (self.version.at_least(4, 5, 0) and ( + self.is_mongos or self.is_rs))) + + @property + def supports_getpreverror(self): + """Does the connected server support getpreverror?""" + return not (self.version.at_least(4, 1, 0) or self.is_mongos) + + @property + def supports_failCommand_fail_point(self): + """Does the server support the failCommand fail point?""" + if self.is_mongos: + return (self.version.at_least(4, 1, 5) and + self.test_commands_enabled) + else: + return (self.version.at_least(4, 0) and + self.test_commands_enabled) + + + @property + def requires_hint_with_min_max_queries(self): + """Does the server require a hint with min/max queries.""" + # Changed in SERVER-39567. + return self.version.at_least(4, 1, 10) + + +# Reusable client context +client_context = ClientContext() + + +def sanitize_cmd(cmd): + cp = cmd.copy() + cp.pop('$clusterTime', None) + cp.pop('$db', None) + cp.pop('$readPreference', None) + cp.pop('lsid', None) + # OP_MSG encoding may move the payload type one field to the + # end of the command. Do the same here. + name = next(iter(cp)) + try: + identifier = message._FIELD_MAP[name] + docs = cp.pop(identifier) + cp[identifier] = docs + except KeyError: + pass + return cp + + +def sanitize_reply(reply): + cp = reply.copy() + cp.pop('$clusterTime', None) + cp.pop('operationTime', None) + return cp + + +class PyMongoTestCase(unittest.TestCase): + def assertEqualCommand(self, expected, actual, msg=None): + self.assertEqual(sanitize_cmd(expected), sanitize_cmd(actual), msg) + + def assertEqualReply(self, expected, actual, msg=None): + self.assertEqual(sanitize_reply(expected), sanitize_reply(actual), msg) + + @contextmanager + def fail_point(self, command_args): + cmd_on = SON([('configureFailPoint', 'failCommand')]) + cmd_on.update(command_args) + client_context.client.admin.command(cmd_on) + try: + yield + finally: + client_context.client.admin.command( + 'configureFailPoint', cmd_on['configureFailPoint'], mode='off') + + +class IntegrationTest(PyMongoTestCase): + """Base class for TestCases that need a connection to MongoDB to pass.""" + + @classmethod + @client_context.require_connection + def setUpClass(cls): + cls.client = client_context.client + cls.db = cls.client.pymongo_test + if client_context.auth_enabled: + cls.credentials = {'username': db_user, 'password': db_pwd} + else: + cls.credentials = {} + + +# Use assertRaisesRegex if available, otherwise use Python 2.7's +# deprecated assertRaisesRegexp, with a 'p'. +if not hasattr(unittest.TestCase, 'assertRaisesRegex'): + unittest.TestCase.assertRaisesRegex = unittest.TestCase.assertRaisesRegexp + + +class MockClientTest(unittest.TestCase): + """Base class for TestCases that use MockClient. + + This class is *not* an IntegrationTest: if properly written, MockClient + tests do not require a running server. + + The class temporarily overrides HEARTBEAT_FREQUENCY to speed up tests. + """ + + def setUp(self): + super(MockClientTest, self).setUp() + + self.client_knobs = client_knobs( + heartbeat_frequency=0.001, + min_heartbeat_interval=0.001) + + self.client_knobs.enable() + + def tearDown(self): + self.client_knobs.disable() + super(MockClientTest, self).tearDown() + + +def setup(): + client_context.init() + warnings.resetwarnings() + warnings.simplefilter("always") + + +def _get_executors(topology): + executors = [] + for server in topology._servers.values(): + # Some MockMonitor do not have an _executor. + executors.append(getattr(server._monitor, '_executor', None)) + executors.append(topology._Topology__events_executor) + if topology._srv_monitor: + executors.append(topology._srv_monitor._executor) + return [e for e in executors if e is not None] + + +def all_executors_stopped(topology): + running = [e for e in _get_executors(topology) if not e._stopped] + if running: + print(' Topology %s has THREADS RUNNING: %s, created at: %s' % ( + topology, running, topology._settings._stack)) + return False + return True + + +def print_unclosed_clients(): + from pymongo.topology import Topology + processed = set() + # Call collect to manually cleanup any would-be gc'd clients to avoid + # false positives. + gc.collect() + for obj in gc.get_objects(): + try: + if isinstance(obj, Topology): + # Avoid printing the same Topology multiple times. + if obj._topology_id in processed: + continue + all_executors_stopped(obj) + processed.add(obj._topology_id) + except ReferenceError: + pass + + +def teardown(): + garbage = [] + for g in gc.garbage: + garbage.append('GARBAGE: %r' % (g,)) + garbage.append(' gc.get_referents: %r' % (gc.get_referents(g),)) + garbage.append(' gc.get_referrers: %r' % (gc.get_referrers(g),)) + if garbage: + assert False, '\n'.join(garbage) + c = client_context.client + if c: + c.drop_database("pymongo-pooling-tests") + c.drop_database("pymongo_test") + c.drop_database("pymongo_test1") + c.drop_database("pymongo_test2") + c.drop_database("pymongo_test_mike") + c.drop_database("pymongo_test_bernie") + c.close() + + # Jython does not support gc.get_objects. + if not sys.platform.startswith('java'): + print_unclosed_clients() + + +class PymongoTestRunner(unittest.TextTestRunner): + def run(self, test): + setup() + result = super(PymongoTestRunner, self).run(test) + teardown() + return result + + +if HAVE_XML: + class PymongoXMLTestRunner(XMLTestRunner): + def run(self, test): + setup() + result = super(PymongoXMLTestRunner, self).run(test) + teardown() + return result + + +def test_cases(suite): + """Iterator over all TestCases within a TestSuite.""" + for suite_or_case in suite._tests: + if isinstance(suite_or_case, unittest.TestCase): + # unittest.TestCase + yield suite_or_case + else: + # unittest.TestSuite + for case in test_cases(suite_or_case): + yield case + + +# Helper method to workaround https://bugs.python.org/issue21724 +def clear_warning_registry(): + """Clear the __warningregistry__ for all modules.""" + for name, module in list(sys.modules.items()): + if hasattr(module, "__warningregistry__"): + setattr(module, "__warningregistry__", {}) diff --git a/test/certificates/ca.pem b/test/certificates/ca.pem new file mode 100644 index 0000000..6ac86cf --- /dev/null +++ b/test/certificates/ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDfzCCAmegAwIBAgIDB1MGMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy +aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u +Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx +CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIwMjMxMVoXDTM5MDUyMjIwMjMxMVoweTEb +MBkGA1UEAxMSRHJpdmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAw +DgYDVQQKEwdNb25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQI +EwhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCl7VN+WsQfHlwapcOpTLZVoeMAl1LTbWTFuXSAavIyy0W1Ytky1UP/ +bxCSW0mSWwCgqoJ5aXbAvrNRp6ArWu3LsTQIEcD3pEdrFIVQhYzWUs9fXqPyI9k+ +QNNQ+MRFKeGteTPYwF2eVEtPzUHU5ws3+OKp1m6MCLkwAG3RBFUAfddUnLvGoZiT +pd8/eNabhgHvdrCw+tYFCWvSjz7SluEVievpQehrSEPKe8DxJq/IM3tSl3tdylzT +zeiKNO7c7LuQrgjAfrZl7n2SriHIlNmqiDR/kdd8+TxBuxjFlcf2WyHCO3lIcIgH +KXTlhUCg50KfHaxHu05Qw0x8869yIzqbAgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggEBAEHuhTL8KQZcKCTSJbYA9MgZj7U32arMGBbc1hiq +VBREwvdVz4+9tIyWMzN9R/YCKmUTnCq8z3wTlC8kBtxYn/l4Tj8nJYcgLJjQ0Fwe +gT564CmvkUat8uXPz6olOCdwkMpJ9Sj62i0mpgXJdBfxKQ6TZ9yGz6m3jannjZpN +LchB7xSAEWtqUgvNusq0dApJsf4n7jZ+oBZVaQw2+tzaMfaLqHgMwcu1FzA8UKCD +sxCgIsZUs8DdxaD418Ot6nPfheOTqe24n+TTa+Z6O0W0QtnofJBx7tmAo1aEc57i +77s89pfwIJetpIlhzNSMKurCAocFCJMJLAASJFuu6dyDvPo= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/test/certificates/client.pem b/test/certificates/client.pem new file mode 100644 index 0000000..5b07001 --- /dev/null +++ b/test/certificates/client.pem @@ -0,0 +1,48 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAsNS8UEuin7/K29jXfIOLpIoh1jEyWVqxiie2Onx7uJJKcoKo +khA3XeUnVN0k6X5MwYWcN52xcns7LYtyt06nRpTG2/emoV44w9uKTuHsvUbiOwSV +m/ToKQQ4FUFZoqorXH+ZmJuIpJNfoW+3CkE1vEDCIecIq6BNg5ySsPtvSuSJHGjp +mc7/5ZUDvFE2aJ8QbJU3Ws0HXiEb6ymi048LlzEL2VKX3w6mqqh+7dcZGAy7qYk2 +5FZ9ktKvCeQau7mTyU1hsPrKFiKtMN8Q2ZAItX13asw5/IeSTq2LgLFHlbj5Kpq4 +GmLdNCshzH5X7Ew3IYM8EHmsX8dmD6mhv7vpVwIDAQABAoIBABOdpb4qhcG+3twA +c/cGCKmaASLnljQ/UU6IFTjrsjXJVKTbRaPeVKX/05sgZQXZ0t3s2mV5AsQ2U1w8 +Cd+3w+qaemzQThW8hAOGCROzEDX29QWi/o2sX0ydgTMqaq0Wv3SlWv6I0mGfT45y +/BURIsrdTCvCmz2erLqa1dL4MWJXRFjT9UTs5twlecIOM2IHKoGGagFhymRK4kDe +wTRC9fpfoAgyfus3pCO/wi/F8yKGPDEwY+zgkhrJQ+kSeki7oKdGD1H540vB8gRt +EIqssE0Y6rEYf97WssQlxJgvoJBDSftOijS6mwvoasDUwfFqyyPiirawXWWhHXkc +DjIi/XECgYEA5xfjilw9YyM2UGQNESbNNunPcj7gDZbN347xJwmYmi9AUdPLt9xN +3XaMqqR22k1DUOxC/5hH0uiXir7mDfqmC+XS/ic/VOsa3CDWejkEnyGLiwSHY502 +wD/xWgHwUiGVAG9HY64vnDGm6L3KGXA2oqxanL4V0+0+Ht49pZ16i8sCgYEAw+Ox +CHGtpkzjCP/z8xr+1VTSdpc/4CP2HONnYopcn48KfQnf7Nale69/1kZpypJlvQSG +eeA3jMGigNJEkb8/kaVoRLCisXcwLc0XIfCTeiK6FS0Ka30D/84Qm8UsHxRdpGkM +kYITAa2r64tgRL8as4/ukeXBKE+oOhX43LeEfyUCgYBkf7IX2Ndlhsm3GlvIarxy +NipeP9PGdR/hKlPbq0OvQf9R1q7QrcE7H7Q6/b0mYNV2mtjkOQB7S2WkFDMOP0P5 +BqDEoKLdNkV/F9TOYH+PCNKbyYNrodJOt0Ap6Y/u1+Xpw3sjcXwJDFrO+sKqX2+T +PStG4S+y84jBedsLbDoAEwKBgQCTz7/KC11o2yOFqv09N+WKvBKDgeWlD/2qFr3w +UU9K5viXGVhqshz0k5z25vL09Drowf1nAZVpFMO2SPOMtq8VC6b+Dfr1xmYIaXVH +Gu1tf77CM9Zk/VSDNc66e7GrUgbHBK2DLo+A+Ld9aRIfTcSsMbNnS+LQtCrQibvb +cG7+MQKBgQCY11oMT2dUekoZEyW4no7W5D74lR8ztMjp/fWWTDo/AZGPBY6cZoZF +IICrzYtDT/5BzB0Jh1f4O9ZQkm5+OvlFbmoZoSbMzHL3oJCBOY5K0/kdGXL46WWh +IRJSYakNU6VIS7SjDpKgm9D8befQqZeoSggSjIIULIiAtYgS80vmGA== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIDAxOUMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy +aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u +Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx +CzAJBgNVBAYTAlVTMB4XDTE5MDUyMjIzNTU1NFoXDTM5MDUyMjIzNTU1NFowaTEP +MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx +FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD +VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALDUvFBLop+/ +ytvY13yDi6SKIdYxMllasYontjp8e7iSSnKCqJIQN13lJ1TdJOl+TMGFnDedsXJ7 +Oy2LcrdOp0aUxtv3pqFeOMPbik7h7L1G4jsElZv06CkEOBVBWaKqK1x/mZibiKST +X6FvtwpBNbxAwiHnCKugTYOckrD7b0rkiRxo6ZnO/+WVA7xRNmifEGyVN1rNB14h +G+spotOPC5cxC9lSl98Opqqofu3XGRgMu6mJNuRWfZLSrwnkGru5k8lNYbD6yhYi +rTDfENmQCLV9d2rMOfyHkk6ti4CxR5W4+SqauBpi3TQrIcx+V+xMNyGDPBB5rF/H +Zg+pob+76VcCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF +BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQAqRcLAGvYMaGYOV4HJTzNotT2qE0I9THNQ +wOV1fBg69x6SrUQTQLjJEptpOA288Wue6Jt3H+p5qAGV5GbXjzN/yjCoItggSKxG +Xg7279nz6/C5faoIKRjpS9R+MsJGlttP9nUzdSxrHvvqm62OuSVFjjETxD39DupE +YPFQoHOxdFTtBQlc/zIKxVdd20rs1xJeeU2/L7jtRBSPuR/Sk8zot7G2/dQHX49y +kHrq8qz12kj1T6XDXf8KZawFywXaz0/Ur+fUYKmkVk1T0JZaNtF4sKqDeNE4zcns +p3xLVDSl1Q5Gwj7bgph9o4Hxs9izPwiqjmNaSjPimGYZ399zcurY +-----END CERTIFICATE----- diff --git a/test/certificates/crl.pem b/test/certificates/crl.pem new file mode 100644 index 0000000..733a0ac --- /dev/null +++ b/test/certificates/crl.pem @@ -0,0 +1,13 @@ +-----BEGIN X509 CRL----- +MIIB6jCB0wIBATANBgkqhkiG9w0BAQsFADB5MRswGQYDVQQDExJEcml2ZXJzIFRl +c3RpbmcgQ0ExEDAOBgNVBAsTB0RyaXZlcnMxEDAOBgNVBAoTB01vbmdvREIxFjAU +BgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYDVQQG +EwJVUxcNMTkwNTIyMjI0NTUzWhcNMTkwNjIxMjI0NTUzWjAVMBMCAncVFw0xOTA1 +MjIyMjQ1MzJaoA8wDTALBgNVHRQEBAICEAAwDQYJKoZIhvcNAQELBQADggEBACwQ +W9OF6ExJSzzYbpCRroznkfdLG7ghNSxIpBQUGtcnYbkP4em6TdtAj5K3yBjcKn4a +hnUoa5EJGr2Xgg0QascV/1GuWEJC9rsYYB9boVi95l1CrkS0pseaunM086iItZ4a +hRVza8qEMBc3rdsracA7hElYMKdFTRLpIGciJehXzv40yT5XFBHGy/HIT0CD50O7 +BDOHzA+rCFCvxX8UY9myDfb1r1zUW7Gzjn241VT7bcIJmhFE9oV0popzDyqr6GvP +qB2t5VmFpbnSwkuc4ie8Jizip1P8Hg73lut3oVAHACFGPpfaNIAp4GcSH61zJmff +9UBe3CJ1INwqyiuqGeA= +-----END X509 CRL----- diff --git a/test/certificates/password_protected.pem b/test/certificates/password_protected.pem new file mode 100644 index 0000000..cc9e124 --- /dev/null +++ b/test/certificates/password_protected.pem @@ -0,0 +1,51 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIC8as6PDVhwECAggA +MB0GCWCGSAFlAwQBAgQQTYOgCJcRqUI7dsgqNojv/ASCBNCG9fiu642V4AuFK34c +Q42lvy/cR0CIXLq/rDXN1L685kdeKex7AfDuRtnjY2+7CLJiJimgQNJXDJPHab/k +MBHbwbBs38fg6eSYX8V08/IyyTege5EJMhYxmieHDC3DXKt0gyHk6hA/r5+Mr49h +HeVGwqBLJEQ3gVIeHaOleZYspsXXWqOPHnFiqnk/biaJS0+LkDDEiQgTLEYSnOjP +lexxUc4BV/TN0Z920tZCMfwx7IXD/C+0AkV/Iqq4LALmT702EccB3indaIJ8biGR +radqDLR32Q+vT9uZHgT8EFiUsISMqhob2mnyTfFV/s9ghWwogjSz0HrRcq6fxdg7 +oeyT9K0ET53AGTGmV0206byPu6qCj1eNvtn+t1Ob+d5hecaTugRMVheWPlc5frsz +AcewDNa0pv4pZItjAGMqOPJHfzEDnzTJXpLqGYhg044H1+OCY8+1YK7U0u8dO+/3 +f5AoDMq18ipDVTFTooJURej4/Wjbrfad3ZFjp86nxfHPeWM1YjC9+IlLtK1wr0/U +V8TjGqCkw8yHayz01A86iA8X53YQBg+tyMGjxmivo6LgFGKa9mXGvDkN+B+0+OcA +PqldAuH/TJhnkqzja767e4n9kcr+TmV19Hn1hcJPTDrRU8+sSqQFsWN4pvHazAYB +UdWie+EXI0eU2Av9JFgrVcpRipXjB48BaPwuBw8hm+VStCH7ynF4lJy6/3esjYwk +Mx+NUf8+pp1DRzpzuJa2vAutzqia5r58+zloQMxkgTZtJkQU6OCRoUhHGVk7WNb1 +nxsibOSzyVSP9ZNbHIHAn43vICFGrPubRs200Kc4CdXsOSEWoP0XYebhiNJgGtQs +KoISsV4dFRLwhaJhIlayTBQz6w6Ph87WbtuiAqoLiuqdXhUGz/79j/6JZqCH8t/H +eZs4Dhu+HdD/wZKJDYAS+JBsiwYWnI3y/EowZYgLdOMI4u6xYDejhxwEw20LW445 +qjJ7pV/iX2uavazHgC91Bfd4zodfXIQ1IDyTmb51UFwx0ARzG6enntduO6xtcYU9 +MXwfrEpuZ/MkWTLkR0PHPbIPcR1MiVwPKdvrLk42Bzj/urtXYrAFUckMFMzEh+uv +0lix2hbq/Xwj4dXcY4w9hnC6QQDCJTf9S6MU6OisrZHKk0qZ2Vb4aU/eBcBsHBwo +X/QGcDHneHxlrrs2eLX26Vh8Odc5h8haeIxnfaa1t+Yv56OKHuAztPMnJOUL7KtQ +A556LxT0b5IGx0RcfUcbG8XbxEHseACptoDOoguh9923IBI0uXmpi8q0P815LPUu +0AsE47ATDMGPnXbopejRDicfgMGjykJn8vKO8r/Ia3Fpnomx4iJNCXGqomL+GMpZ +IhQbKNrRG6XZMlx5kVCT0Qr1nOWMiOTSDCQ5vrG3c1Viu+0bctvidEvs+LCm98tb +7ty8F0uOno0rYGNQz18OEE1Tj+E19Vauz1U35Z5SsgJJ/GfzhSJ79Srmdg2PsAzk +AUNTKXux1GLf1cMjTiiU5g+tCEtUL9Me7lsv3L6aFdrCyRbhXUQfJh4NAG8+3Pvh +EaprThBzKsVvbOfU81mOaH9YMmUgmxG86vxDiNtaWd4v6c1k+HGspJr/q49pcXZP +ltBMuS9AihstZ1sHJsyQCmNXkA== +-----END ENCRYPTED PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDgzCCAmugAwIBAgIDBXUHMA0GCSqGSIb3DQEBCwUAMHkxGzAZBgNVBAMTEkRy +aXZlcnMgVGVzdGluZyBDQTEQMA4GA1UECxMHRHJpdmVyczEQMA4GA1UEChMHTW9u +Z29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlvcmsx +CzAJBgNVBAYTAlVTMB4XDTE5MDUyMzAwMDEyOVoXDTM5MDUyMzAwMDEyOVowaTEP +MA0GA1UEAxMGY2xpZW50MRAwDgYDVQQLEwdEcml2ZXJzMQwwCgYDVQQKEwNNREIx +FjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3JrMQswCQYD +VQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOqCb0Lo4XsV +W327Wlnqc5rwWa5Elw0rFuehSfViRIcYfuFWAPXoOj3fIDsYz6d41G8hp6tkF88p +swlbzDF8Fc7mXDhauwwl2F/NrWYUXwCT8fKju4DtGd2JlDMi1TRDeofkYCGVPp70 +vNqd0H8iDWWs8OmiNrdBLJwNiGaf9y15ena4ImQGitXLFn+qNSXYJ1Rs8p7Y2PTr +L+dff5gJCVbANwGII1rjMAsrMACPVmr8c1Lxoq4fSdJiLweosrv2Lk0WWGsO0Seg +ZY71dNHEyNjItE+VtFEtslJ5L261i3BfF/FqNnH2UmKXzShwfwxyHT8o84gSAltQ +5/lVJ4QQKosCAwEAAaMkMCIwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF +BwMCMA0GCSqGSIb3DQEBCwUAA4IBAQBOAlKxIMFcTZ+4k8NJv97RSf+zOb5Wu2ct +uxSZxzgKTxLFUuEM8XQiEz1iHQ3XG+uV1fzA74YLQiKjjLrU0mx54eM1vaRtOXvF +sJlzZU8Z2+523FVPx4HBPyObQrfXmIoAiHoQ4VUeepkPRpXxpifgWd/OCWhLDr2/ +0Kgcb0ybaGVDpA0UD9uVIwgFjRu6id7wG+lVcdRxJYskTOOaN2o1hMdAKkrpFQbd +zNRfEoBPUYR3QAmAKP2HBjpgp4ktOHoOKMlfeAuuMCUocSnmPKc3xJaH/6O7rHcf +/Rm0X411RH8JfoXYsSiPsd601kZefhuWvJH0sJLibRDvT7zs8C1v +-----END CERTIFICATE----- diff --git a/test/certificates/server.pem b/test/certificates/server.pem new file mode 100644 index 0000000..e745e03 --- /dev/null +++ b/test/certificates/server.pem @@ -0,0 +1,49 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAhNrB0E6GY/kFSd8/vNpu/t952tbnOsD5drV0XPvmuy7SgKDY +a/S+xb/jPnlZKKehdBnH7qP/gYbv34ZykzcDFZscjPLiGc2cRGP+NQCSFK0d2/7d +y15zSD3zhj14G8+MkpAejTU+0/qFNZMc5neDvGanTe0+8aWa0DXssM0MuTxIv7j6 +CtsMWeqLLofN7a1Kw2UvmieCHfHMuA/08pJwRnV/+5T9WONBPJja2ZQRrG1BjpI4 +81zSPUZesIqi8yDlExdvgNaRZIEHi/njREqwVgJOZomUY57zmKypiMzbz48dDTsV +gUStxrEqbaP+BEjQYPX5+QQk4GdMjkLf52LR6QIDAQABAoIBAHSs+hHLJNOf2zkp +S3y8CUblVMsQeTpsR6otaehPgi9Zy50TpX4KD5D0GMrBH8BIl86y5Zd7h+VlcDzK +gs0vPxI2izhuBovKuzaE6rf5rFFkSBjxGDCG3o/PeJOoYFdsS3RcBbjVzju0hFCs +xnDQ/Wz0anJRrTnjyraY5SnQqx/xuhLXkj/lwWoWjP2bUqDprnuLOj16soNu60Um +JziWbmWx9ty0wohkI/8DPBl9FjSniEEUi9pnZXPElFN6kwPkgdfT5rY/TkMH4lsu +ozOUc5xgwlkT6kVjXHcs3fleuT/mOfVXLPgNms85JKLucfd6KiV7jYZkT/bXIjQ+ +7CZEn0ECgYEA5QiKZgsfJjWvZpt21V/i7dPje2xdwHtZ8F9NjX7ZUFA7mUPxUlwe +GiXxmy6RGzNdnLOto4SF0/7ebuF3koO77oLup5a2etL+y/AnNAufbu4S5D72sbiz +wdLzr3d5JQ12xeaEH6kQNk2SD5/ShctdS6GmTgQPiJIgH0MIdi9F3v0CgYEAlH84 +hMWcC+5b4hHUEexeNkT8kCXwHVcUjGRaYFdSHgovvWllApZDHSWZ+vRcMBdlhNPu +09Btxo99cjOZwGYJyt20QQLGc/ZyiOF4ximQzabTeFgLkTH3Ox6Mh2Rx9yIruYoX +nE3UfMDkYELanEJUv0zenKpZHw7tTt5yXXSlEF0CgYBSsEOvVcKYO/eoluZPYQAA +F2jgzZ4HeUFebDoGpM52lZD+463Dq2hezmYtPaG77U6V3bUJ/TWH9VN/Or290vvN +v83ECcC2FWlSXdD5lFyqYx/E8gqE3YdgqfW62uqM+xBvoKsA9zvYLydVpsEN9v8m +6CSvs/2btA4O21e5u5WBTQKBgGtAb6vFpe0gHRDs24SOeYUs0lWycPhf+qFjobrP +lqnHpa9iPeheat7UV6BfeW3qmBIVl/s4IPE2ld4z0qqZiB0Tf6ssu/TpXNPsNXS6 +dLFz+myC+ufFdNEoQUtQitd5wKbjTCZCOGRaVRgJcSdG6Tq55Fa22mOKPm+mTmed +ZdKpAoGAFsTYBAHPxs8nzkCJCl7KLa4/zgbgywO6EcQgA7tfelB8bc8vcAMG5o+8 +YqAfwxrzhVSVbJx0fibTARXROmbh2pn010l2wj3+qUajM8NiskCPFbSjGy7HSUze +P8Kt1uMDJdj55gATzn44au31QBioZY2zXleorxF21cr+BZCJgfA= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgICdxUwDQYJKoZIhvcNAQELBQAweTEbMBkGA1UEAxMSRHJp +dmVycyBUZXN0aW5nIENBMRAwDgYDVQQLEwdEcml2ZXJzMRAwDgYDVQQKEwdNb25n +b0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREwDwYDVQQIEwhOZXcgWW9yazEL +MAkGA1UEBhMCVVMwHhcNMTkwNTIyMjIzMjU2WhcNMzkwNTIyMjIzMjU2WjBwMRIw +EAYDVQQDEwlsb2NhbGhvc3QxEDAOBgNVBAsTB0RyaXZlcnMxEDAOBgNVBAoTB01v +bmdvREIxFjAUBgNVBAcTDU5ldyBZb3JrIENpdHkxETAPBgNVBAgTCE5ldyBZb3Jr +MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAITa +wdBOhmP5BUnfP7zabv7fedrW5zrA+Xa1dFz75rsu0oCg2Gv0vsW/4z55WSinoXQZ +x+6j/4GG79+GcpM3AxWbHIzy4hnNnERj/jUAkhStHdv+3ctec0g984Y9eBvPjJKQ +Ho01PtP6hTWTHOZ3g7xmp03tPvGlmtA17LDNDLk8SL+4+grbDFnqiy6Hze2tSsNl +L5ongh3xzLgP9PKScEZ1f/uU/VjjQTyY2tmUEaxtQY6SOPNc0j1GXrCKovMg5RMX +b4DWkWSBB4v540RKsFYCTmaJlGOe85isqYjM28+PHQ07FYFErcaxKm2j/gRI0GD1 ++fkEJOBnTI5C3+di0ekCAwEAAaMwMC4wLAYDVR0RBCUwI4IJbG9jYWxob3N0hwR/ +AAABhxAAAAAAAAAAAAAAAAAAAAABMA0GCSqGSIb3DQEBCwUAA4IBAQBol8+YH7MA +HwnIh7KcJ8h87GkCWsjOJCDJWiYBJArQ0MmgDO0qdx+QEtvLMn3XNtP05ZfK0WyX +or4cWllAkMFYaFbyB2hYazlD1UAAG+22Rku0UP6pJMLbWe6pnqzx+RL68FYdbZhN +fCW2xiiKsdPoo2VEY7eeZKrNr/0RFE5EKXgzmobpTBQT1Dl3Ve4aWLoTy9INlQ/g +z40qS7oq1PjjPLgxINhf4ncJqfmRXugYTOnyFiVXLZTys5Pb9SMKdToGl3NTYWLL +2AZdjr6bKtT+WtXyHqO0cQ8CkAW0M6VOlMluACllcJxfrtdlQS2S4lUIj76QKBdZ +khBHXq/b8MFX +-----END CERTIFICATE----- diff --git a/test/certificates/trusted-ca.pem b/test/certificates/trusted-ca.pem new file mode 100644 index 0000000..a6f6f31 --- /dev/null +++ b/test/certificates/trusted-ca.pem @@ -0,0 +1,82 @@ +# CA bundle file used to test tlsCAFile loading for OCSP. +# Copied from the server: +# https://github.com/mongodb/mongo/blob/r4.3.4/jstests/libs/trusted-ca.pem + +# Autogenerated file, do not edit. +# Generate using jstests/ssl/x509/mkcert.py --config jstests/ssl/x509/certs.yml trusted-ca.pem +# +# CA for alternate client/server certificate chain. +-----BEGIN CERTIFICATE----- +MIIDojCCAooCBG585gswDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxETAP +BgNVBAgMCE5ldyBZb3JrMRYwFAYDVQQHDA1OZXcgWW9yayBDaXR5MRAwDgYDVQQK +DAdNb25nb0RCMQ8wDQYDVQQLDAZLZXJuZWwxHzAdBgNVBAMMFlRydXN0ZWQgS2Vy +bmVsIFRlc3QgQ0EwHhcNMTkwOTI1MjMyNzQxWhcNMzkwOTI3MjMyNzQxWjB8MQsw +CQYDVQQGEwJVUzERMA8GA1UECAwITmV3IFlvcmsxFjAUBgNVBAcMDU5ldyBZb3Jr +IENpdHkxEDAOBgNVBAoMB01vbmdvREIxDzANBgNVBAsMBktlcm5lbDEfMB0GA1UE +AwwWVHJ1c3RlZCBLZXJuZWwgVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANlRxtpMeCGhkotkjHQqgqvO6O6hoRoAGGJlDaTVtqrjmC8nwySz +1nAFndqUHttxS3A5j4enOabvffdOcV7+Z6vDQmREF6QZmQAk81pmazSc3wOnRiRs +AhXjld7i+rhB50CW01oYzQB50rlBFu+ONKYj32nBjD+1YN4AZ2tuRlbxfx2uf8Bo +Zowfr4n9nHVcWXBLFmaQLn+88WFO/wuwYUOn6Di1Bvtkvqum0or5QeAF0qkJxfhg +3a4vBnomPdwEXCgAGLvHlB41CWG09EuAjrnE3HPPi5vII8pjY2dKKMomOEYmA+KJ +AC1NlTWdN0TtsoaKnyhMMhLWs3eTyXL7kbkCAwEAAaMxMC8wDAYDVR0TBAUwAwEB +/zAfBgNVHREEGDAWgglsb2NhbGhvc3SCCTEyNy4wLjAuMTANBgkqhkiG9w0BAQsF +AAOCAQEAQk56MO9xAhtO077COCqIYe6pYv3uzOplqjXpJ7Cph7GXwQqdFWfKls7B +cLfF/fhIUZIu5itStEkY+AIwht4mBr1F5+hZUp9KZOed30/ewoBXAUgobLipJV66 +FKg8NRtmJbiZrrC00BSO+pKfQThU8k0zZjBmNmpjxnbKZZSFWUKtbhHV1vujver6 +SXZC7R6692vLwRBMoZxhgy/FkYRdiN0U9wpluKd63eo/O02Nt6OEMyeiyl+Z3JWi +8g5iHNrBYGBbGSnDOnqV6tjEY3eq600JDWiodpA1OQheLi78pkc/VQZwof9dyBCm +6BoCskTjip/UB+vIhdPFT9sgUdgDTg== +-----END CERTIFICATE----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZUcbaTHghoZKL +ZIx0KoKrzujuoaEaABhiZQ2k1baq45gvJ8Mks9ZwBZ3alB7bcUtwOY+Hpzmm7333 +TnFe/merw0JkRBekGZkAJPNaZms0nN8Dp0YkbAIV45Xe4vq4QedAltNaGM0AedK5 +QRbvjjSmI99pwYw/tWDeAGdrbkZW8X8drn/AaGaMH6+J/Zx1XFlwSxZmkC5/vPFh +Tv8LsGFDp+g4tQb7ZL6rptKK+UHgBdKpCcX4YN2uLwZ6Jj3cBFwoABi7x5QeNQlh +tPRLgI65xNxzz4ubyCPKY2NnSijKJjhGJgPiiQAtTZU1nTdE7bKGip8oTDIS1rN3 +k8ly+5G5AgMBAAECggEAS7GjLKgT88reSzUTgubHquYf1fZwMak01RjTnsVdoboy +aMJVwzPsjgo2yEptUQvuNcGmz54cg5vJaVlmPaspGveg6WGaRmswEo/MP4GK98Fo +IFKkKM2CEHO74O14XLN/w8yFA02+IdtM3X/haEFE71VxXNmwawRXIBxN6Wp4j5Fb +mPLKIspnWQ/Y/Fn799sCFAzX5mKkbCt1IEgKssgQQEm1UkvmCkcZE+mdO/ErYP8A +COO0LpM+TK6WQY2LKiteeCCiosTZFb1GO7MkXrRP5uOBZKaW5kq1R0b6PcopJPCM +OcYF0Zli6KB7oiQLdXgU2jCaxYOnuRb6RYh2l7NvAQKBgQD6CZ9TKOn/EUQtukyw +pvYTyt1hoLXqYGcbRtLc1gcC+Z2BD28hd3eD/mEUv+g/8bq/OP4wYV9X+VRvR8xN +MmfAG/sJeOCOClz1A1TyNeA+G0GZ25qWHyHQ2W4WlSG1CXQgxGzU6wo/t6wiVW5R +O4jplFVEOXznf4vmVfBJK50R2QKBgQDegGxm23jF2N5sIYDZ14oxms8bbjPz8zH6 +tiIRYNGbSzI7J4KFGY2HiBwtf1yxS22HBL69Y1WrEzGm1vm4aZG/GUwBzI79QZAO ++YFIGaIrdlv12Zm6lpJMmAWlOs9XFirC17oQEwOQFweOdQSt7F/+HMZOigdikRBV +pK+8Kfay4QKBgQDarDevHwUmkg8yftA7Xomv3aenjkoK5KzH6jTX9kbDj1L0YG8s +sbLQuVRmNUAFTH+qZUnJPh+IbQIvIHfIu+CI3u+55QFeuCl8DqHoAr5PEr9Ys/qK +eEe2w7HIBj0oe1AYqDEWNUkNWLEuhdCpMowW3CeGN1DJlX7gvyAang4MYQKBgHwM +aWNnFQxo/oiWnTnWm2tQfgszA7AMdF7s0E2UBwhnghfMzU3bkzZuwhbznQATp3rR +QG5iRU7dop7717ni0akTN3cBTu8PcHuIy3UhJXLJyDdnG/gVHnepgew+v340E58R +muB/WUsqK8JWp0c4M8R+0mjTN47ShaLZ8EgdtTbBAoGBAKOcpuDfFEMI+YJgn8zX +h0nFT60LX6Lx+zcSDY9+6J6a4n5NhC+weYCDFOGlsLka1SwHcg1xanfrLVjpH7Ok +HDJGLrSh1FP2Rq/oFxZ/OKCjonHLa8IulqD/AA+sqYRbysKNsT3Pi0554F2xFEqQ +z/C84nlT1R2uTCWIxvrnpU2h +-----END PRIVATE KEY----- +# Pre Oct 2019 trusted-ca.pem +# Transitional pending BUILD update. +-----BEGIN CERTIFICATE----- +MIIDpjCCAo6gAwIBAgIDAghHMA0GCSqGSIb3DQEBBQUAMHwxHzAdBgNVBAMTFlRy +dXN0ZWQgS2VybmVsIFRlc3QgQ0ExDzANBgNVBAsTBktlcm5lbDEQMA4GA1UEChMH +TW9uZ29EQjEWMBQGA1UEBxMNTmV3IFlvcmsgQ2l0eTERMA8GA1UECBMITmV3IFlv +cmsxCzAJBgNVBAYTAlVTMB4XDTE2MDMzMTE0NTY1NVoXDTM2MDMzMTE0NTY1NVow +fDEfMB0GA1UEAxMWVHJ1c3RlZCBLZXJuZWwgVGVzdCBDQTEPMA0GA1UECxMGS2Vy +bmVsMRAwDgYDVQQKEwdNb25nb0RCMRYwFAYDVQQHEw1OZXcgWW9yayBDaXR5MREw +DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCePFHZTydC96SlSHSyu73vw//ddaE33kPllBB9DP2L7yRF +6D/blFmno9fSM+Dfg64VfGV+0pCXPIZbpH29nzJu0DkvHzKiWK7P1zUj8rAHaX++ +d6k0yeTLFM9v+7YE9rHoANVn22aOyDvTgAyMmA0CLn+SmUy6WObwMIf9cZn97Znd +lww7IeFNyK8sWtfsVN4yRBnjr7kKN2Qo0QmWeFa7jxVQptMJQrY8k1PcyVUOgOjQ +ocJLbWLlm9k0/OMEQSwQHJ+d9weUbKjlZ9ExOrm4QuuA2tJhb38baTdAYw3Jui4f +yD6iBAGD0Jkpc+3YaWv6CBmK8NEFkYJD/gn+lJ75AgMBAAGjMTAvMAwGA1UdEwQF +MAMBAf8wHwYDVR0RBBgwFoIJbG9jYWxob3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcN +AQEFBQADggEBADYikjB6iwAUs6sglwkE4rOkeMkJdRCNwK/5LpFJTWrDjBvBQCdA +Y5hlAVq8PfIYeh+wEuSvsEHXmx7W29X2+p4VuJ95/xBA6NLapwtzuiijRj2RBAOG +1EGuyFQUPTL27DR3+tfayNykDclsVDNN8+l7nt56j8HojP74P5OMHtn+6HX5+mtF +FfZMTy0mWguCsMOkZvjAskm6s4U5gEC8pYEoC0ZRbfUdyYsxZe/nrXIFguVlVPCB +XnfB/0iG9t+VH5cUVj1LP9skXTW4kXfhQmljUuo+EVBNR6n2nfTnpoC65WeAgHV4 +V+s9mJsUv2x72KtKYypqEVT0gaJ1WIN9N1s= +-----END CERTIFICATE----- diff --git a/test/crud/v1/read/aggregate-collation.json b/test/crud/v1/read/aggregate-collation.json new file mode 100644 index 0000000..85662a4 --- /dev/null +++ b/test/crud/v1/read/aggregate-collation.json @@ -0,0 +1,38 @@ +{ + "data": [ + { + "_id": 1, + "x": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "Aggregate with collation", + "operation": { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$match": { + "x": "PING" + } + } + ], + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": [ + { + "_id": 1, + "x": "ping" + } + ] + } + } + ] +} diff --git a/test/crud/v1/read/aggregate-out.json b/test/crud/v1/read/aggregate-out.json new file mode 100644 index 0000000..205cf76 --- /dev/null +++ b/test/crud/v1/read/aggregate-out.json @@ -0,0 +1,121 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "Aggregate with $out", + "operation": { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "batchSize": 2 + } + }, + "outcome": { + "result": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Aggregate with $out and batch size of 0", + "operation": { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "batchSize": 0 + } + }, + "outcome": { + "result": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/read/aggregate.json b/test/crud/v1/read/aggregate.json new file mode 100644 index 0000000..797a922 --- /dev/null +++ b/test/crud/v1/read/aggregate.json @@ -0,0 +1,53 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "Aggregate with multiple stages", + "operation": { + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + } + ], + "batchSize": 2 + } + }, + "outcome": { + "result": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + ] +} diff --git a/test/crud/v1/read/count-collation.json b/test/crud/v1/read/count-collation.json new file mode 100644 index 0000000..6f75282 --- /dev/null +++ b/test/crud/v1/read/count-collation.json @@ -0,0 +1,47 @@ +{ + "data": [ + { + "_id": 1, + "x": "PING" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "Count documents with collation", + "operation": { + "name": "countDocuments", + "arguments": { + "filter": { + "x": "ping" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": 1 + } + }, + { + "description": "Deprecated count with collation", + "operation": { + "name": "count", + "arguments": { + "filter": { + "x": "ping" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": 1 + } + } + ] +} diff --git a/test/crud/v1/read/count-empty.json b/test/crud/v1/read/count-empty.json new file mode 100644 index 0000000..2b8627e --- /dev/null +++ b/test/crud/v1/read/count-empty.json @@ -0,0 +1,39 @@ +{ + "data": [], + "tests": [ + { + "description": "Estimated document count with empty collection", + "operation": { + "name": "estimatedDocumentCount", + "arguments": {} + }, + "outcome": { + "result": 0 + } + }, + { + "description": "Count documents with empty collection", + "operation": { + "name": "countDocuments", + "arguments": { + "filter": {} + } + }, + "outcome": { + "result": 0 + } + }, + { + "description": "Deprecated count with empty collection", + "operation": { + "name": "count", + "arguments": { + "filter": {} + } + }, + "outcome": { + "result": 0 + } + } + ] +} diff --git a/test/crud/v1/read/count.json b/test/crud/v1/read/count.json new file mode 100644 index 0000000..9642b2f --- /dev/null +++ b/test/crud/v1/read/count.json @@ -0,0 +1,112 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "Estimated document count", + "operation": { + "name": "estimatedDocumentCount", + "arguments": {} + }, + "outcome": { + "result": 3 + } + }, + { + "description": "Count documents without a filter", + "operation": { + "name": "countDocuments", + "arguments": { + "filter": {} + } + }, + "outcome": { + "result": 3 + } + }, + { + "description": "Count documents with a filter", + "operation": { + "name": "countDocuments", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + }, + "outcome": { + "result": 2 + } + }, + { + "description": "Count documents with skip and limit", + "operation": { + "name": "countDocuments", + "arguments": { + "filter": {}, + "skip": 1, + "limit": 3 + } + }, + "outcome": { + "result": 2 + } + }, + { + "description": "Deprecated count without a filter", + "operation": { + "name": "count", + "arguments": { + "filter": {} + } + }, + "outcome": { + "result": 3 + } + }, + { + "description": "Deprecated count with a filter", + "operation": { + "name": "count", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + }, + "outcome": { + "result": 2 + } + }, + { + "description": "Deprecated count with skip and limit", + "operation": { + "name": "count", + "arguments": { + "filter": {}, + "skip": 1, + "limit": 3 + } + }, + "outcome": { + "result": 2 + } + } + ] +} diff --git a/test/crud/v1/read/distinct-collation.json b/test/crud/v1/read/distinct-collation.json new file mode 100644 index 0000000..0af0c67 --- /dev/null +++ b/test/crud/v1/read/distinct-collation.json @@ -0,0 +1,33 @@ +{ + "data": [ + { + "_id": 1, + "string": "PING" + }, + { + "_id": 2, + "string": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "Distinct with a collation", + "operation": { + "name": "distinct", + "arguments": { + "fieldName": "string", + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": [ + "PING" + ] + } + } + ] +} diff --git a/test/crud/v1/read/distinct.json b/test/crud/v1/read/distinct.json new file mode 100644 index 0000000..a57ee36 --- /dev/null +++ b/test/crud/v1/read/distinct.json @@ -0,0 +1,55 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "Distinct without a filter", + "operation": { + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": {} + } + }, + "outcome": { + "result": [ + 11, + 22, + 33 + ] + } + }, + { + "description": "Distinct with a filter", + "operation": { + "name": "distinct", + "arguments": { + "fieldName": "x", + "filter": { + "_id": { + "$gt": 1 + } + } + } + }, + "outcome": { + "result": [ + 22, + 33 + ] + } + } + ] +} diff --git a/test/crud/v1/read/find-collation.json b/test/crud/v1/read/find-collation.json new file mode 100644 index 0000000..53d0e94 --- /dev/null +++ b/test/crud/v1/read/find-collation.json @@ -0,0 +1,34 @@ +{ + "data": [ + { + "_id": 1, + "x": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "Find with a collation", + "operation": { + "name": "find", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": [ + { + "_id": 1, + "x": "ping" + } + ] + } + } + ] +} diff --git a/test/crud/v1/read/find.json b/test/crud/v1/read/find.json new file mode 100644 index 0000000..3597e37 --- /dev/null +++ b/test/crud/v1/read/find.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + }, + { + "_id": 5, + "x": 55 + } + ], + "tests": [ + { + "description": "Find with filter", + "operation": { + "name": "find", + "arguments": { + "filter": { + "_id": 1 + } + } + }, + "outcome": { + "result": [ + { + "_id": 1, + "x": 11 + } + ] + } + }, + { + "description": "Find with filter, sort, skip, and limit", + "operation": { + "name": "find", + "arguments": { + "filter": { + "_id": { + "$gt": 2 + } + }, + "sort": { + "_id": 1 + }, + "skip": 2, + "limit": 2 + } + }, + "outcome": { + "result": [ + { + "_id": 5, + "x": 55 + } + ] + } + }, + { + "description": "Find with limit, sort, and batchsize", + "operation": { + "name": "find", + "arguments": { + "filter": {}, + "sort": { + "_id": 1 + }, + "limit": 4, + "batchSize": 2 + } + }, + "outcome": { + "result": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + ] +} diff --git a/test/crud/v1/write/bulkWrite-arrayFilters.json b/test/crud/v1/write/bulkWrite-arrayFilters.json new file mode 100644 index 0000000..99e73f5 --- /dev/null +++ b/test/crud/v1/write/bulkWrite-arrayFilters.json @@ -0,0 +1,111 @@ +{ + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ], + "minServerVersion": "3.5.6", + "tests": [ + { + "description": "BulkWrite with arrayFilters", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + }, + { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 3, + "modifiedCount": 3, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 2 + } + ] + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/bulkWrite-collation.json b/test/crud/v1/write/bulkWrite-collation.json new file mode 100644 index 0000000..8e9d1bc --- /dev/null +++ b/test/crud/v1/write/bulkWrite-collation.json @@ -0,0 +1,217 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + }, + { + "_id": 4, + "x": "pong" + }, + { + "_id": 5, + "x": "pONg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "BulkWrite with delete operations and collation", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "x": "PONG" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 4, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + }, + { + "description": "BulkWrite with update operations and collation", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 3 + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "x": "ping" + }, + "replacement": { + "_id": 6, + "x": "ping" + }, + "upsert": true, + "collation": { + "locale": "en_US", + "strength": 3 + } + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "x": "pong" + }, + "update": { + "$set": { + "x": "PONG" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 6, + "modifiedCount": 4, + "upsertedCount": 1, + "upsertedIds": { + "2": 6 + } + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "PONG" + }, + { + "_id": 3, + "x": "PONG" + }, + { + "_id": 4, + "x": "PONG" + }, + { + "_id": 5, + "x": "PONG" + }, + { + "_id": 6, + "x": "ping" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/bulkWrite.json b/test/crud/v1/write/bulkWrite.json new file mode 100644 index 0000000..dc00da2 --- /dev/null +++ b/test/crud/v1/write/bulkWrite.json @@ -0,0 +1,778 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "BulkWrite with deleteOne operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 3 + } + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + } + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 1, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + }, + { + "description": "BulkWrite with deleteMany operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteMany", + "arguments": { + "filter": { + "x": { + "$lt": 11 + } + } + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "x": { + "$lte": 22 + } + } + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 2, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [] + } + } + }, + { + "description": "BulkWrite with insertOne operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 2, + "insertedIds": { + "0": 3, + "1": 4 + }, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite with replaceOne operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 33 + } + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 12 + } + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 33 + }, + "upsert": true + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "2": 3 + } + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "BulkWrite with updateOne operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 0 + }, + "update": { + "$set": { + "x": 0 + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$set": { + "x": 11 + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 33 + } + }, + "upsert": true + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 2, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "3": 3 + } + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "BulkWrite with updateMany operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "x": { + "$lt": 11 + } + }, + "update": { + "$set": { + "x": 0 + } + } + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "x": { + "$lte": 22 + } + }, + "update": { + "$unset": { + "y": 1 + } + } + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "x": { + "$lte": 22 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "x": 33 + } + }, + "upsert": true + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 4, + "modifiedCount": 2, + "upsertedCount": 1, + "upsertedIds": { + "3": 3 + } + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "BulkWrite with mixed ordered operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + } + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "x": { + "$nin": [ + 24, + 34 + ] + } + } + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "upsert": true + } + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "deletedCount": 2, + "insertedCount": 2, + "insertedIds": { + "0": 3, + "3": 4 + }, + "matchedCount": 3, + "modifiedCount": 3, + "upsertedCount": 1, + "upsertedIds": { + "5": 4 + } + }, + "collection": { + "data": [ + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 34 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite with mixed unordered operations", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "_id": 3, + "x": 33 + }, + "upsert": true + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + } + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + } + ], + "options": { + "ordered": false + } + } + }, + "outcome": { + "result": { + "deletedCount": 1, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 1, + "upsertedIds": { + "0": 3 + } + }, + "collection": { + "data": [ + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "options": { + "ordered": false + } + } + }, + "outcome": { + "error": true, + "result": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)", + "operation": { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 3, + "x": 33 + } + } + }, + { + "name": "insertOne", + "arguments": { + "document": { + "_id": 4, + "x": 44 + } + } + } + ], + "options": { + "ordered": false + } + } + }, + "outcome": { + "error": true, + "result": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/deleteMany-collation.json b/test/crud/v1/write/deleteMany-collation.json new file mode 100644 index 0000000..d17bf3b --- /dev/null +++ b/test/crud/v1/write/deleteMany-collation.json @@ -0,0 +1,47 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "DeleteMany when many documents match with collation", + "operation": { + "name": "deleteMany", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "deletedCount": 2 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/deleteMany.json b/test/crud/v1/write/deleteMany.json new file mode 100644 index 0000000..7eee85e --- /dev/null +++ b/test/crud/v1/write/deleteMany.json @@ -0,0 +1,76 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "DeleteMany when many documents match", + "operation": { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + }, + "outcome": { + "result": { + "deletedCount": 2 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + }, + { + "description": "DeleteMany when no document matches", + "operation": { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": 4 + } + } + }, + "outcome": { + "result": { + "deletedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/deleteOne-collation.json b/test/crud/v1/write/deleteOne-collation.json new file mode 100644 index 0000000..2f7f921 --- /dev/null +++ b/test/crud/v1/write/deleteOne-collation.json @@ -0,0 +1,51 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "DeleteOne when many documents matches with collation", + "operation": { + "name": "deleteOne", + "arguments": { + "filter": { + "x": "PING" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "deletedCount": 1 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/deleteOne.json b/test/crud/v1/write/deleteOne.json new file mode 100644 index 0000000..a1106de --- /dev/null +++ b/test/crud/v1/write/deleteOne.json @@ -0,0 +1,96 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "DeleteOne when many documents match", + "operation": { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + } + } + }, + "outcome": { + "result": { + "deletedCount": 1 + } + } + }, + { + "description": "DeleteOne when one document matches", + "operation": { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + } + } + }, + "outcome": { + "result": { + "deletedCount": 1 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "DeleteOne when no documents match", + "operation": { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 4 + } + } + }, + "outcome": { + "result": { + "deletedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndDelete-collation.json b/test/crud/v1/write/findOneAndDelete-collation.json new file mode 100644 index 0000000..1ff37d2 --- /dev/null +++ b/test/crud/v1/write/findOneAndDelete-collation.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "FindOneAndDelete when one document matches with collation", + "operation": { + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 2, + "x": "PING" + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "x": "ping" + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndDelete.json b/test/crud/v1/write/findOneAndDelete.json new file mode 100644 index 0000000..e424e2a --- /dev/null +++ b/test/crud/v1/write/findOneAndDelete.json @@ -0,0 +1,127 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "FindOneAndDelete when many documents match", + "operation": { + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndDelete when one document matches", + "operation": { + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 2 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndDelete when no documents match", + "operation": { + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 4 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndReplace-collation.json b/test/crud/v1/write/findOneAndReplace-collation.json new file mode 100644 index 0000000..babb2f7 --- /dev/null +++ b/test/crud/v1/write/findOneAndReplace-collation.json @@ -0,0 +1,58 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "FindOneAndReplace when one document matches with collation returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "x": "PING" + }, + "replacement": { + "x": "pong" + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "x": "pong" + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndReplace-upsert.json b/test/crud/v1/write/findOneAndReplace-upsert.json new file mode 100644 index 0000000..0f07bf9 --- /dev/null +++ b/test/crud/v1/write/findOneAndReplace-upsert.json @@ -0,0 +1,201 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + } + }, + "outcome": { + "result": { + "x": 44 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + } + }, + "outcome": { + "result": { + "x": 44 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndReplace.json b/test/crud/v1/write/findOneAndReplace.json new file mode 100644 index 0000000..70e5c3d --- /dev/null +++ b/test/crud/v1/write/findOneAndReplace.json @@ -0,0 +1,273 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "FindOneAndReplace when many documents match returning the document before modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when many documents match returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 32 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when one document matches returning the document before modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when one document matches returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 2 + }, + "replacement": { + "x": 32 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 32 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 32 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when no documents match returning the document before modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndReplace when no documents match returning the document after modification", + "operation": { + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 44 + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndUpdate-arrayFilters.json b/test/crud/v1/write/findOneAndUpdate-arrayFilters.json new file mode 100644 index 0000000..1aa13b8 --- /dev/null +++ b/test/crud/v1/write/findOneAndUpdate-arrayFilters.json @@ -0,0 +1,203 @@ +{ + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ], + "minServerVersion": "3.5.6", + "tests": [ + { + "description": "FindOneAndUpdate when no document matches arrayFilters", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + } + }, + "outcome": { + "result": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when one document matches arrayFilters", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + }, + "outcome": { + "result": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when multiple documents match arrayFilters", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + }, + "outcome": { + "result": { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndUpdate-collation.json b/test/crud/v1/write/findOneAndUpdate-collation.json new file mode 100644 index 0000000..04c1fe7 --- /dev/null +++ b/test/crud/v1/write/findOneAndUpdate-collation.json @@ -0,0 +1,67 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "FindOneAndUpdate when many documents match with collation returning the document before modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "x": "PING" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "_id": 1 + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "x": "ping" + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + }, + { + "_id": 3, + "x": "pINg" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/findOneAndUpdate.json b/test/crud/v1/write/findOneAndUpdate.json new file mode 100644 index 0000000..6da8325 --- /dev/null +++ b/test/crud/v1/write/findOneAndUpdate.json @@ -0,0 +1,379 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "tests": [ + { + "description": "FindOneAndUpdate when many documents match returning the document before modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when many documents match returning the document after modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 23 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when one document matches returning the document before modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 22 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when one document matches returning the document after modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 2 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": { + "x": 23 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when no documents match returning the document before modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when no documents match with upsert returning the document before modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "upsert": true + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when no documents match returning the document after modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + } + } + }, + "outcome": { + "result": null, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate when no documents match with upsert returning the document after modification", + "operation": { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "projection": { + "x": 1, + "_id": 0 + }, + "returnDocument": "After", + "sort": { + "x": 1 + }, + "upsert": true + } + }, + "outcome": { + "result": { + "x": 1 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/insertMany.json b/test/crud/v1/write/insertMany.json new file mode 100644 index 0000000..6a2e526 --- /dev/null +++ b/test/crud/v1/write/insertMany.json @@ -0,0 +1,159 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + } + ], + "tests": [ + { + "description": "InsertMany with non-existing documents", + "operation": { + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "options": { + "ordered": true + } + } + }, + "outcome": { + "result": { + "insertedIds": { + "0": 2, + "1": 3 + } + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)", + "operation": { + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "options": { + "ordered": false + } + } + }, + "outcome": { + "error": true, + "result": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "InsertMany continue-on-error behavior with unordered (duplicate key in requests)", + "operation": { + "name": "insertMany", + "arguments": { + "documents": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "options": { + "ordered": false + } + } + }, + "outcome": { + "error": true, + "result": { + "deletedCount": 0, + "insertedCount": 2, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/insertOne.json b/test/crud/v1/write/insertOne.json new file mode 100644 index 0000000..525de75 --- /dev/null +++ b/test/crud/v1/write/insertOne.json @@ -0,0 +1,39 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + } + ], + "tests": [ + { + "description": "InsertOne with a non-existing document", + "operation": { + "name": "insertOne", + "arguments": { + "document": { + "_id": 2, + "x": 22 + } + } + }, + "outcome": { + "result": { + "insertedId": 2 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/replaceOne-collation.json b/test/crud/v1/write/replaceOne-collation.json new file mode 100644 index 0000000..a668fe7 --- /dev/null +++ b/test/crud/v1/write/replaceOne-collation.json @@ -0,0 +1,53 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "ReplaceOne when one document matches with collation", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "x": "PING" + }, + "replacement": { + "_id": 2, + "x": "pong" + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/replaceOne.json b/test/crud/v1/write/replaceOne.json new file mode 100644 index 0000000..101af25 --- /dev/null +++ b/test/crud/v1/write/replaceOne.json @@ -0,0 +1,205 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "ReplaceOne when many documents match", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + }, + { + "description": "ReplaceOne when one document matches", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "_id": 1, + "x": 111 + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 111 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "ReplaceOne when no documents match", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 1 + } + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "ReplaceOne with upsert when no documents match without an id specified", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 1 + }, + "upsert": true + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + }, + { + "description": "ReplaceOne with upsert when no documents match with an id specified", + "operation": { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "_id": 4, + "x": 1 + }, + "upsert": true + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateMany-arrayFilters.json b/test/crud/v1/write/updateMany-arrayFilters.json new file mode 100644 index 0000000..ae4c123 --- /dev/null +++ b/test/crud/v1/write/updateMany-arrayFilters.json @@ -0,0 +1,185 @@ +{ + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ], + "minServerVersion": "3.5.6", + "tests": [ + { + "description": "UpdateMany when no documents match arrayFilters", + "operation": { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 2, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + }, + { + "description": "UpdateMany when one document matches arrayFilters", + "operation": { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 2, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + }, + { + "description": "UpdateMany when multiple documents match arrayFilters", + "operation": { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 2 + } + ] + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateMany-collation.json b/test/crud/v1/write/updateMany-collation.json new file mode 100644 index 0000000..3cb49f2 --- /dev/null +++ b/test/crud/v1/write/updateMany-collation.json @@ -0,0 +1,62 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + }, + { + "_id": 3, + "x": "pINg" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "UpdateMany when many documents match with collation", + "operation": { + "name": "updateMany", + "arguments": { + "filter": { + "x": "ping" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + }, + { + "_id": 3, + "x": "pong" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateMany.json b/test/crud/v1/write/updateMany.json new file mode 100644 index 0000000..a3c3399 --- /dev/null +++ b/test/crud/v1/write/updateMany.json @@ -0,0 +1,183 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "UpdateMany when many documents match", + "operation": { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 34 + } + ] + } + } + }, + { + "description": "UpdateMany when one document matches", + "operation": { + "name": "updateMany", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateMany when no documents match", + "operation": { + "name": "updateMany", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateMany with upsert when no documents match", + "operation": { + "name": "updateMany", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateOne-arrayFilters.json b/test/crud/v1/write/updateOne-arrayFilters.json new file mode 100644 index 0000000..087ed4b --- /dev/null +++ b/test/crud/v1/write/updateOne-arrayFilters.json @@ -0,0 +1,395 @@ +{ + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ], + "minServerVersion": "3.5.6", + "tests": [ + { + "description": "UpdateOne when no document matches arrayFilters", + "operation": { + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 4 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + } + }, + { + "description": "UpdateOne when one document matches arrayFilters", + "operation": { + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + } + }, + { + "description": "UpdateOne when multiple documents match arrayFilters", + "operation": { + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + } + }, + { + "description": "UpdateOne when no documents match multiple arrayFilters", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "y.$[i].c.$[j].d": 0 + } + }, + "arrayFilters": [ + { + "i.b": 5 + }, + { + "j.d": 3 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 1 + } + ] + } + ] + } + ] + } + } + }, + { + "description": "UpdateOne when one document matches multiple arrayFilters", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 3 + }, + "update": { + "$set": { + "y.$[i].c.$[j].d": 0 + } + }, + "arrayFilters": [ + { + "i.b": 5 + }, + { + "j.d": 1 + } + ] + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + }, + { + "_id": 3, + "y": [ + { + "b": 5, + "c": [ + { + "d": 2 + }, + { + "d": 0 + } + ] + } + ] + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateOne-collation.json b/test/crud/v1/write/updateOne-collation.json new file mode 100644 index 0000000..c49112d --- /dev/null +++ b/test/crud/v1/write/updateOne-collation.json @@ -0,0 +1,54 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "ping" + } + ], + "minServerVersion": "3.4", + "tests": [ + { + "description": "UpdateOne when one document matches with collation", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "x": "PING" + }, + "update": { + "$set": { + "x": "pong" + } + }, + "collation": { + "locale": "en_US", + "strength": 2 + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": "pong" + } + ] + } + } + } + ] +} diff --git a/test/crud/v1/write/updateOne.json b/test/crud/v1/write/updateOne.json new file mode 100644 index 0000000..76d2086 --- /dev/null +++ b/test/crud/v1/write/updateOne.json @@ -0,0 +1,167 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "minServerVersion": "2.6", + "tests": [ + { + "description": "UpdateOne when many documents match", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + }, + { + "description": "UpdateOne when one document matches", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateOne when no documents match", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + } + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateOne with upsert when no documents match", + "operation": { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 4 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "upsert": true + } + }, + "outcome": { + "result": { + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 1, + "upsertedId": 4 + }, + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 1 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/aggregate-merge.json b/test/crud/v2/aggregate-merge.json new file mode 100644 index 0000000..c61736a --- /dev/null +++ b/test/crud/v2/aggregate-merge.json @@ -0,0 +1,415 @@ +{ + "runOn": [ + { + "minServerVersion": "4.1.11" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "test_aggregate_merge", + "tests": [ + { + "description": "Aggregate with $merge", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_merge", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Aggregate with $merge and batch size of 0", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ], + "batchSize": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_merge", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ], + "cursor": {} + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Aggregate with $merge and majority readConcern", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "majority" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_merge", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ], + "readConcern": { + "level": "majority" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Aggregate with $merge and local readConcern", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "local" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_merge", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ], + "readConcern": { + "level": "local" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Aggregate with $merge and available readConcern", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "available" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_merge", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$merge": { + "into": "other_test_collection" + } + } + ], + "readConcern": { + "level": "available" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/aggregate-out-readConcern.json b/test/crud/v2/aggregate-out-readConcern.json new file mode 100644 index 0000000..c39ee0e --- /dev/null +++ b/test/crud/v2/aggregate-out-readConcern.json @@ -0,0 +1,385 @@ +{ + "runOn": [ + { + "minServerVersion": "4.1.0", + "topology": [ + "replicaset", + "sharded" + ] + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "test_aggregate_out_readconcern", + "tests": [ + { + "description": "readConcern majority with out stage", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "majority" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_out_readconcern", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "readConcern": { + "level": "majority" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "readConcern local with out stage", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "local" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_out_readconcern", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "readConcern": { + "level": "local" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "readConcern available with out stage", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "available" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_out_readconcern", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "readConcern": { + "level": "available" + } + } + } + } + ], + "outcome": { + "collection": { + "name": "other_test_collection", + "data": [ + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "readConcern linearizable with out stage", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "linearizable" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ] + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_out_readconcern", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "readConcern": { + "level": "linearizable" + } + } + } + } + ] + }, + { + "description": "invalid readConcern with out stage", + "operations": [ + { + "object": "collection", + "name": "aggregate", + "collectionOptions": { + "readConcern": { + "level": "!invalid123" + } + }, + "arguments": { + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ] + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "aggregate": "test_aggregate_out_readconcern", + "pipeline": [ + { + "$sort": { + "x": 1 + } + }, + { + "$match": { + "_id": { + "$gt": 1 + } + } + }, + { + "$out": "other_test_collection" + } + ], + "readConcern": { + "level": "!invalid123" + } + } + } + } + ] + } + ] +} diff --git a/test/crud/v2/bulkWrite-arrayFilters.json b/test/crud/v2/bulkWrite-arrayFilters.json new file mode 100644 index 0000000..2d3ce96 --- /dev/null +++ b/test/crud/v2/bulkWrite-arrayFilters.json @@ -0,0 +1,226 @@ +{ + "runOn": [ + { + "minServerVersion": "3.5.6" + } + ], + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ], + "collection_name": "test", + "database_name": "crud-tests", + "tests": [ + { + "description": "BulkWrite updateOne with arrayFilters", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 3 + } + ] + } + ], + "ordered": true + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 2 + }, + { + "b": 1 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 1 + } + ] + } + ] + } + } + }, + { + "description": "BulkWrite updateMany with arrayFilters", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": { + "$set": { + "y.$[i].b": 2 + } + }, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": { + "$set": { + "y.$[i].b": 2 + } + }, + "multi": true, + "arrayFilters": [ + { + "i.b": 1 + } + ] + } + ], + "ordered": true + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "y": [ + { + "b": 3 + }, + { + "b": 2 + } + ] + }, + { + "_id": 2, + "y": [ + { + "b": 0 + }, + { + "b": 2 + } + ] + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-delete-hint-clientError.json b/test/crud/v2/bulkWrite-delete-hint-clientError.json new file mode 100644 index 0000000..cfeac90 --- /dev/null +++ b/test/crud/v2/bulkWrite-delete-hint-clientError.json @@ -0,0 +1,150 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "BulkWrite_delete_hint", + "tests": [ + { + "description": "BulkWrite deleteOne with hints unsupported (client-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite deleteMany with hints unsupported (client-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_" + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-delete-hint-serverError.json b/test/crud/v2/bulkWrite-delete-hint-serverError.json new file mode 100644 index 0000000..c68973b --- /dev/null +++ b/test/crud/v2/bulkWrite-delete-hint-serverError.json @@ -0,0 +1,209 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.3.3" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "BulkWrite_delete_hint", + "tests": [ + { + "description": "BulkWrite deleteOne with hints unsupported (server-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "BulkWrite_delete_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": "_id_", + "limit": 1 + }, + { + "q": { + "_id": 2 + }, + "hint": { + "_id": 1 + }, + "limit": 1 + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite deleteMany with hints unsupported (server-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_" + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "BulkWrite_delete_hint", + "deletes": [ + { + "q": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_", + "limit": 0 + }, + { + "q": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + }, + "limit": 0 + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-delete-hint.json b/test/crud/v2/bulkWrite-delete-hint.json new file mode 100644 index 0000000..ece3238 --- /dev/null +++ b/test/crud/v2/bulkWrite-delete-hint.json @@ -0,0 +1,204 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.4" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "BulkWrite_delete_hint", + "tests": [ + { + "description": "BulkWrite deleteOne with hints", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 2, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "BulkWrite_delete_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": "_id_", + "limit": 1 + }, + { + "q": { + "_id": 2 + }, + "hint": { + "_id": 1 + }, + "limit": 1 + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite deleteMany with hints", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_" + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 3, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 0, + "modifiedCount": 0, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "BulkWrite_delete_hint", + "deletes": [ + { + "q": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_", + "limit": 0 + }, + { + "q": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + }, + "limit": 0 + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-update-hint-clientError.json b/test/crud/v2/bulkWrite-update-hint-clientError.json new file mode 100644 index 0000000..fa919ec --- /dev/null +++ b/test/crud/v2/bulkWrite-update-hint-clientError.json @@ -0,0 +1,235 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "test_bulkwrite_update_hint", + "tests": [ + { + "description": "BulkWrite updateOne with update hints unsupported (client-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite updateMany with update hints unsupported (client-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite replaceOne with update hints unsupported (client-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 333 + }, + "hint": "_id_" + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-update-hint-serverError.json b/test/crud/v2/bulkWrite-update-hint-serverError.json new file mode 100644 index 0000000..e8b96ff --- /dev/null +++ b/test/crud/v2/bulkWrite-update-hint-serverError.json @@ -0,0 +1,343 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.1.9" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "test_bulkwrite_update_hint", + "tests": [ + { + "description": "BulkWrite updateOne with update hints unsupported (server-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite updateMany with update hints unsupported (server-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": { + "$lt": 3 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": "_id_" + }, + { + "q": { + "_id": { + "$lt": 3 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite replaceOne with update hints unsupported (server-side error)", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 333 + }, + "hint": "_id_" + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": 3 + }, + "u": { + "x": 333 + }, + "hint": "_id_" + }, + { + "q": { + "_id": 4 + }, + "u": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/bulkWrite-update-hint.json b/test/crud/v2/bulkWrite-update-hint.json new file mode 100644 index 0000000..15e169f --- /dev/null +++ b/test/crud/v2/bulkWrite-update-hint.json @@ -0,0 +1,366 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "test_bulkwrite_update_hint", + "tests": [ + { + "description": "BulkWrite updateOne with update hints", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + { + "q": { + "_id": 1 + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 13 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite updateMany with update hints", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 4, + "modifiedCount": 4, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": { + "$lt": 3 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": "_id_" + }, + { + "q": { + "_id": { + "$lt": 3 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 13 + }, + { + "_id": 2, + "x": 24 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "BulkWrite replaceOne with update hints", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 333 + }, + "hint": "_id_" + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "result": { + "deletedCount": 0, + "insertedCount": 0, + "insertedIds": {}, + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0, + "upsertedIds": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_bulkwrite_update_hint", + "updates": [ + { + "q": { + "_id": 3 + }, + "u": { + "x": 333 + }, + "hint": "_id_" + }, + { + "q": { + "_id": 4 + }, + "u": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + ], + "ordered": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 333 + }, + { + "_id": 4, + "x": 444 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/db-aggregate.json b/test/crud/v2/db-aggregate.json new file mode 100644 index 0000000..d88b9e1 --- /dev/null +++ b/test/crud/v2/db-aggregate.json @@ -0,0 +1,81 @@ +{ + "runOn": [ + { + "minServerVersion": "3.6.0" + } + ], + "database_name": "admin", + "tests": [ + { + "description": "Aggregate with $listLocalSessions", + "operations": [ + { + "name": "aggregate", + "object": "database", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + }, + { + "$addFields": { + "dummy": "dummy field" + } + }, + { + "$project": { + "_id": 0, + "dummy": 1 + } + } + ] + }, + "result": [ + { + "dummy": "dummy field" + } + ] + } + ] + }, + { + "description": "Aggregate with $listLocalSessions and allowDiskUse", + "operations": [ + { + "name": "aggregate", + "object": "database", + "arguments": { + "pipeline": [ + { + "$listLocalSessions": {} + }, + { + "$limit": 1 + }, + { + "$addFields": { + "dummy": "dummy field" + } + }, + { + "$project": { + "_id": 0, + "dummy": 1 + } + } + ], + "allowDiskUse": true + }, + "result": [ + { + "dummy": "dummy field" + } + ] + } + ] + } + ] +} diff --git a/test/crud/v2/deleteMany-hint-clientError.json b/test/crud/v2/deleteMany-hint-clientError.json new file mode 100644 index 0000000..3a0d025 --- /dev/null +++ b/test/crud/v2/deleteMany-hint-clientError.json @@ -0,0 +1,100 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "DeleteMany_hint", + "tests": [ + { + "description": "DeleteMany with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "DeleteMany with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/deleteMany-hint-serverError.json b/test/crud/v2/deleteMany-hint-serverError.json new file mode 100644 index 0000000..5829e86 --- /dev/null +++ b/test/crud/v2/deleteMany-hint-serverError.json @@ -0,0 +1,141 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.3.3" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "DeleteMany_hint", + "tests": [ + { + "description": "DeleteMany with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteMany_hint", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_", + "limit": 0 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "DeleteMany with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteMany_hint", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + }, + "limit": 0 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/deleteMany-hint.json b/test/crud/v2/deleteMany-hint.json new file mode 100644 index 0000000..51ee386 --- /dev/null +++ b/test/crud/v2/deleteMany-hint.json @@ -0,0 +1,128 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.4" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "DeleteMany_hint", + "tests": [ + { + "description": "DeleteMany with hint string", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + }, + "result": { + "deletedCount": 2 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteMany_hint", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_", + "limit": 0 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + }, + { + "description": "DeleteMany with hint document", + "operations": [ + { + "object": "collection", + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "result": { + "deletedCount": 2 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteMany_hint", + "deletes": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + }, + "limit": 0 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/deleteOne-hint-clientError.json b/test/crud/v2/deleteOne-hint-clientError.json new file mode 100644 index 0000000..97f8ec4 --- /dev/null +++ b/test/crud/v2/deleteOne-hint-clientError.json @@ -0,0 +1,84 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "DeleteOne_hint", + "tests": [ + { + "description": "DeleteOne with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "DeleteOne with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/deleteOne-hint-serverError.json b/test/crud/v2/deleteOne-hint-serverError.json new file mode 100644 index 0000000..3cf9400 --- /dev/null +++ b/test/crud/v2/deleteOne-hint-serverError.json @@ -0,0 +1,121 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.3.3" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "DeleteOne_hint", + "tests": [ + { + "description": "DeleteOne with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteOne_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": "_id_", + "limit": 1 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "DeleteOne with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteOne_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": { + "_id": 1 + }, + "limit": 1 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/deleteOne-hint.json b/test/crud/v2/deleteOne-hint.json new file mode 100644 index 0000000..ec8e771 --- /dev/null +++ b/test/crud/v2/deleteOne-hint.json @@ -0,0 +1,116 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.4" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "DeleteOne_hint", + "tests": [ + { + "description": "DeleteOne with hint string", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteOne_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": "_id_", + "limit": 1 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "deleteOne with hint document", + "operations": [ + { + "object": "collection", + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "result": { + "deletedCount": 1 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "delete": "DeleteOne_hint", + "deletes": [ + { + "q": { + "_id": 1 + }, + "hint": { + "_id": 1 + }, + "limit": 1 + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/find-allowdiskuse-clientError.json b/test/crud/v2/find-allowdiskuse-clientError.json new file mode 100644 index 0000000..5ea0139 --- /dev/null +++ b/test/crud/v2/find-allowdiskuse-clientError.json @@ -0,0 +1,40 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.0.99" + } + ], + "collection_name": "test_find_allowdiskuse_clienterror", + "tests": [ + { + "description": "Find fails when allowDiskUse true is specified against pre 3.2 server", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": true + }, + "error": true + } + ], + "expectations": [] + }, + { + "description": "Find fails when allowDiskUse false is specified against pre 3.2 server", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": false + }, + "error": true + } + ], + "expectations": [] + } + ] +} diff --git a/test/crud/v2/find-allowdiskuse-serverError.json b/test/crud/v2/find-allowdiskuse-serverError.json new file mode 100644 index 0000000..31aa50e --- /dev/null +++ b/test/crud/v2/find-allowdiskuse-serverError.json @@ -0,0 +1,61 @@ +{ + "runOn": [ + { + "minServerVersion": "3.2", + "maxServerVersion": "4.3.0" + } + ], + "collection_name": "test_find_allowdiskuse_servererror", + "tests": [ + { + "description": "Find fails when allowDiskUse true is specified against pre 4.4 server (server-side error)", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": true + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "find": "test_find_allowdiskuse_servererror", + "filter": {}, + "allowDiskUse": true + } + } + } + ] + }, + { + "description": "Find fails when allowDiskUse false is specified against pre 4.4 server (server-side error)", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": false + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "find": "test_find_allowdiskuse_servererror", + "filter": {}, + "allowDiskUse": false + } + } + } + ] + } + ] +} diff --git a/test/crud/v2/find-allowdiskuse.json b/test/crud/v2/find-allowdiskuse.json new file mode 100644 index 0000000..2df4dbc --- /dev/null +++ b/test/crud/v2/find-allowdiskuse.json @@ -0,0 +1,78 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.1" + } + ], + "collection_name": "test_find_allowdiskuse", + "tests": [ + { + "description": "Find does not send allowDiskuse when value is not specified", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {} + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "find": "test_find_allowdiskuse", + "allowDiskUse": null + } + } + } + ] + }, + { + "description": "Find sends allowDiskuse false when false is specified", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": false + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "find": "test_find_allowdiskuse", + "allowDiskUse": false + } + } + } + ] + }, + { + "description": "Find sends allowDiskUse true when true is specified", + "operations": [ + { + "object": "collection", + "name": "find", + "arguments": { + "filter": {}, + "allowDiskUse": true + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "find": "test_find_allowdiskuse", + "allowDiskUse": true + } + } + } + ] + } + ] +} \ No newline at end of file diff --git a/test/crud/v2/findOneAndDelete-hint-clientError.json b/test/crud/v2/findOneAndDelete-hint-clientError.json new file mode 100644 index 0000000..262e78c --- /dev/null +++ b/test/crud/v2/findOneAndDelete-hint-clientError.json @@ -0,0 +1,84 @@ +{ + "runOn": [ + { + "maxServerVersion": "4.0.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndDelete_hint", + "tests": [ + { + "description": "FindOneAndDelete with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndDelete with hint document", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndDelete-hint-serverError.json b/test/crud/v2/findOneAndDelete-hint-serverError.json new file mode 100644 index 0000000..5d1dd89 --- /dev/null +++ b/test/crud/v2/findOneAndDelete-hint-serverError.json @@ -0,0 +1,113 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0", + "maxServerVersion": "4.3.3" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndDelete_hint", + "tests": [ + { + "description": "FindOneAndDelete with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndDelete_hint", + "query": { + "_id": 1 + }, + "hint": "_id_", + "remove": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndDelete with hint document", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndDelete_hint", + "query": { + "_id": 1 + }, + "hint": { + "_id": 1 + }, + "remove": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndDelete-hint.json b/test/crud/v2/findOneAndDelete-hint.json new file mode 100644 index 0000000..fe8dcfa --- /dev/null +++ b/test/crud/v2/findOneAndDelete-hint.json @@ -0,0 +1,110 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.4" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndDelete_hint", + "tests": [ + { + "description": "FindOneAndDelete with hint string", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndDelete_hint", + "query": { + "_id": 1 + }, + "hint": "_id_", + "remove": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndDelete with hint document", + "operations": [ + { + "object": "collection", + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndDelete_hint", + "query": { + "_id": 1 + }, + "hint": { + "_id": 1 + }, + "remove": true + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndReplace-hint-clientError.json b/test/crud/v2/findOneAndReplace-hint-clientError.json new file mode 100644 index 0000000..08fd4b3 --- /dev/null +++ b/test/crud/v2/findOneAndReplace-hint-clientError.json @@ -0,0 +1,90 @@ +{ + "runOn": [ + { + "maxServerVersion": "4.0.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndReplace_hint", + "tests": [ + { + "description": "FindOneAndReplace with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndReplace with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndReplace-hint-serverError.json b/test/crud/v2/findOneAndReplace-hint-serverError.json new file mode 100644 index 0000000..6710e6a --- /dev/null +++ b/test/crud/v2/findOneAndReplace-hint-serverError.json @@ -0,0 +1,123 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0", + "maxServerVersion": "4.3.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndReplace_hint", + "tests": [ + { + "description": "FindOneAndReplace with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndReplace_hint", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "hint": "_id_" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndReplace with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndReplace_hint", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "hint": { + "_id": 1 + } + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndReplace-hint.json b/test/crud/v2/findOneAndReplace-hint.json new file mode 100644 index 0000000..263fdf9 --- /dev/null +++ b/test/crud/v2/findOneAndReplace-hint.json @@ -0,0 +1,128 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.1" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndReplace_hint", + "tests": [ + { + "description": "FindOneAndReplace with hint string", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": "_id_" + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndReplace_hint", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "hint": "_id_" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 33 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndReplace with hint document", + "operations": [ + { + "object": "collection", + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": { + "_id": 1 + } + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndReplace_hint", + "query": { + "_id": 1 + }, + "update": { + "x": 33 + }, + "hint": { + "_id": 1 + } + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 33 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndUpdate-hint-clientError.json b/test/crud/v2/findOneAndUpdate-hint-clientError.json new file mode 100644 index 0000000..8cd5cdd --- /dev/null +++ b/test/crud/v2/findOneAndUpdate-hint-clientError.json @@ -0,0 +1,94 @@ +{ + "runOn": [ + { + "maxServerVersion": "4.0.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndUpdate_hint", + "tests": [ + { + "description": "FindOneAndUpdate with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndUpdate-hint-serverError.json b/test/crud/v2/findOneAndUpdate-hint-serverError.json new file mode 100644 index 0000000..1f4b2bd --- /dev/null +++ b/test/crud/v2/findOneAndUpdate-hint-serverError.json @@ -0,0 +1,131 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0", + "maxServerVersion": "4.3.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndUpdate_hint", + "tests": [ + { + "description": "FindOneAndUpdate with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndUpdate_hint", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndUpdate_hint", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/findOneAndUpdate-hint.json b/test/crud/v2/findOneAndUpdate-hint.json new file mode 100644 index 0000000..451eecc --- /dev/null +++ b/test/crud/v2/findOneAndUpdate-hint.json @@ -0,0 +1,136 @@ +{ + "runOn": [ + { + "minServerVersion": "4.3.1" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndUpdate_hint", + "tests": [ + { + "description": "FindOneAndUpdate with hint string", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndUpdate_hint", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate with hint document", + "operations": [ + { + "object": "collection", + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "result": { + "_id": 1, + "x": 11 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "findOneAndUpdate_hint", + "query": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 12 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/replaceOne-hint.json b/test/crud/v2/replaceOne-hint.json new file mode 100644 index 0000000..de4aa4d --- /dev/null +++ b/test/crud/v2/replaceOne-hint.json @@ -0,0 +1,146 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "test_replaceone_hint", + "tests": [ + { + "description": "ReplaceOne with hint string", + "operations": [ + { + "object": "collection", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + }, + "hint": "_id_" + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_replaceone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 111 + }, + "hint": "_id_" + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 111 + } + ] + } + } + }, + { + "description": "ReplaceOne with hint document", + "operations": [ + { + "object": "collection", + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + }, + "hint": { + "_id": 1 + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_replaceone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "x": 111 + }, + "hint": { + "_id": 1 + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 111 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-bulkWrite-delete-hint-clientError.json b/test/crud/v2/unacknowledged-bulkWrite-delete-hint-clientError.json new file mode 100644 index 0000000..46839db --- /dev/null +++ b/test/crud/v2/unacknowledged-bulkWrite-delete-hint-clientError.json @@ -0,0 +1,155 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "BulkWrite_delete_hint", + "tests": [ + { + "description": "Unacknowledged bulkWrite deleteOne with hints fails with client-side error", + "operations": [ + { + "name": "bulkWrite", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "arguments": { + "requests": [ + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + } + }, + { + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 2 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "Unacknowledged bulkWrite deleteMany with hints fails with client-side error", + "operations": [ + { + "name": "bulkWrite", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "arguments": { + "requests": [ + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "hint": "_id_" + } + }, + { + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gte": 4 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-bulkWrite-update-hint-clientError.json b/test/crud/v2/unacknowledged-bulkWrite-update-hint-clientError.json new file mode 100644 index 0000000..4a41d76 --- /dev/null +++ b/test/crud/v2/unacknowledged-bulkWrite-update-hint-clientError.json @@ -0,0 +1,245 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ], + "collection_name": "Bulkwrite_update_hint", + "tests": [ + { + "description": "Unacknowledged bulkWrite updateOne with hints fails with client-side error", + "operations": [ + { + "name": "bulkWrite", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "Unacknowledged bulkWrite updateMany with hints fails with client-side error", + "operations": [ + { + "name": "bulkWrite", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + }, + { + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$lt": 3 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + }, + { + "description": "Unacknowledged bulkWrite replaceOne with hints fails with client-side error", + "operations": [ + { + "name": "bulkWrite", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "arguments": { + "requests": [ + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 3 + }, + "replacement": { + "x": 333 + }, + "hint": "_id_" + } + }, + { + "name": "replaceOne", + "arguments": { + "filter": { + "_id": 4 + }, + "replacement": { + "x": 444 + }, + "hint": { + "_id": 1 + } + } + } + ], + "options": { + "ordered": true + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + }, + { + "_id": 4, + "x": 44 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-deleteMany-hint-clientError.json b/test/crud/v2/unacknowledged-deleteMany-hint-clientError.json new file mode 100644 index 0000000..532f428 --- /dev/null +++ b/test/crud/v2/unacknowledged-deleteMany-hint-clientError.json @@ -0,0 +1,105 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "DeleteMany_hint", + "tests": [ + { + "description": "Unacknowledged deleteMany with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Unacknowledged deleteMany with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "deleteMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-deleteOne-hint-clientError.json b/test/crud/v2/unacknowledged-deleteOne-hint-clientError.json new file mode 100644 index 0000000..ff3f05e --- /dev/null +++ b/test/crud/v2/unacknowledged-deleteOne-hint-clientError.json @@ -0,0 +1,89 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "DeleteOne_hint", + "tests": [ + { + "description": "Unacknowledged deleteOne with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged deleteOne with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "deleteOne", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-findOneAndDelete-hint-clientError.json b/test/crud/v2/unacknowledged-findOneAndDelete-hint-clientError.json new file mode 100644 index 0000000..0769788 --- /dev/null +++ b/test/crud/v2/unacknowledged-findOneAndDelete-hint-clientError.json @@ -0,0 +1,89 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "findOneAndDelete_hint", + "tests": [ + { + "description": "Unacknowledged findOneAndDelete with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged findOneAndDelete with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndDelete", + "arguments": { + "filter": { + "_id": 1 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-findOneAndReplace-hint-clientError.json b/test/crud/v2/unacknowledged-findOneAndReplace-hint-clientError.json new file mode 100644 index 0000000..38fbc81 --- /dev/null +++ b/test/crud/v2/unacknowledged-findOneAndReplace-hint-clientError.json @@ -0,0 +1,95 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "FindOneAndReplace_hint", + "tests": [ + { + "description": "Unacknowledged findOneAndReplace with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged findOneAndReplace with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndReplace", + "arguments": { + "filter": { + "_id": 1 + }, + "replacement": { + "x": 33 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-findOneAndUpdate-hint-clientError.json b/test/crud/v2/unacknowledged-findOneAndUpdate-hint-clientError.json new file mode 100644 index 0000000..615b4c0 --- /dev/null +++ b/test/crud/v2/unacknowledged-findOneAndUpdate-hint-clientError.json @@ -0,0 +1,99 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "FindOneAndUpdate_hint", + "tests": [ + { + "description": "Unacknowledged findOneAndUpdate with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged findOneAndUpdate with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-replaceOne-hint-clientError.json b/test/crud/v2/unacknowledged-replaceOne-hint-clientError.json new file mode 100644 index 0000000..c4add73 --- /dev/null +++ b/test/crud/v2/unacknowledged-replaceOne-hint-clientError.json @@ -0,0 +1,99 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "ReplaceOne_hint", + "tests": [ + { + "description": "Unacknowledged ReplaceOne with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged ReplaceOne with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "replaceOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "replacement": { + "x": 111 + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-updateMany-hint-clientError.json b/test/crud/v2/unacknowledged-updateMany-hint-clientError.json new file mode 100644 index 0000000..eaf3efd --- /dev/null +++ b/test/crud/v2/unacknowledged-updateMany-hint-clientError.json @@ -0,0 +1,115 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "Updatemany_hint", + "tests": [ + { + "description": "Unacknowledged updateMany with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "Unacknowledged updateMany with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/unacknowledged-updateOne-hint-clientError.json b/test/crud/v2/unacknowledged-updateOne-hint-clientError.json new file mode 100644 index 0000000..1f8f738 --- /dev/null +++ b/test/crud/v2/unacknowledged-updateOne-hint-clientError.json @@ -0,0 +1,103 @@ +{ + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "UpdateOne_hint", + "tests": [ + { + "description": "Unacknowledged updateOne with hint string fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "Unacknowledged updateOne with hint document fails with client-side error", + "operations": [ + { + "object": "collection", + "collectionOptions": { + "writeConcern": { + "w": 0 + } + }, + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateMany-hint-clientError.json b/test/crud/v2/updateMany-hint-clientError.json new file mode 100644 index 0000000..44ebddc --- /dev/null +++ b/test/crud/v2/updateMany-hint-clientError.json @@ -0,0 +1,110 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "test_updatemany_hint", + "tests": [ + { + "description": "UpdateMany with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateMany with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateMany-hint-serverError.json b/test/crud/v2/updateMany-hint-serverError.json new file mode 100644 index 0000000..86f2124 --- /dev/null +++ b/test/crud/v2/updateMany-hint-serverError.json @@ -0,0 +1,161 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.1.9" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "test_updatemany_hint", + "tests": [ + { + "description": "UpdateMany with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updatemany_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": "_id_" + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + }, + { + "description": "UpdateMany with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updatemany_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": { + "_id": 1 + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateMany-hint.json b/test/crud/v2/updateMany-hint.json new file mode 100644 index 0000000..4893489 --- /dev/null +++ b/test/crud/v2/updateMany-hint.json @@ -0,0 +1,168 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + }, + { + "_id": 3, + "x": 33 + } + ], + "collection_name": "test_updatemany_hint", + "tests": [ + { + "description": "UpdateMany with hint string", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updatemany_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": "_id_" + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 34 + } + ] + } + } + }, + { + "description": "UpdateMany with hint document", + "operations": [ + { + "object": "collection", + "name": "updateMany", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updatemany_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "multi": true, + "hint": { + "_id": 1 + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + }, + { + "_id": 3, + "x": 34 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateOne-hint-clientError.json b/test/crud/v2/updateOne-hint-clientError.json new file mode 100644 index 0000000..82bfe36 --- /dev/null +++ b/test/crud/v2/updateOne-hint-clientError.json @@ -0,0 +1,98 @@ +{ + "runOn": [ + { + "maxServerVersion": "3.3.99" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "test_updateone_hint", + "tests": [ + { + "description": "UpdateOne with hint string unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "UpdateOne with hint document unsupported (client-side error)", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateOne-hint-serverError.json b/test/crud/v2/updateOne-hint-serverError.json new file mode 100644 index 0000000..8e8037e --- /dev/null +++ b/test/crud/v2/updateOne-hint-serverError.json @@ -0,0 +1,147 @@ +{ + "runOn": [ + { + "minServerVersion": "3.4.0", + "maxServerVersion": "4.1.9" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "test_updateone_hint", + "tests": [ + { + "description": "UpdateOne with hint string unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updateone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + }, + { + "description": "UpdateOne with hint document unsupported (server-side error)", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "error": true + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updateone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateOne-hint.json b/test/crud/v2/updateOne-hint.json new file mode 100644 index 0000000..43f76da --- /dev/null +++ b/test/crud/v2/updateOne-hint.json @@ -0,0 +1,154 @@ +{ + "runOn": [ + { + "minServerVersion": "4.2.0" + } + ], + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 22 + } + ], + "collection_name": "test_updateone_hint", + "tests": [ + { + "description": "UpdateOne with hint string", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updateone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": "_id_" + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + } + ] + } + } + }, + { + "description": "UpdateOne with hint document", + "operations": [ + { + "object": "collection", + "name": "updateOne", + "arguments": { + "filter": { + "_id": { + "$gt": 1 + } + }, + "update": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test_updateone_hint", + "updates": [ + { + "q": { + "_id": { + "$gt": 1 + } + }, + "u": { + "$inc": { + "x": 1 + } + }, + "hint": { + "_id": 1 + } + } + ] + } + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 11 + }, + { + "_id": 2, + "x": 23 + } + ] + } + } + } + ] +} diff --git a/test/crud/v2/updateWithPipelines.json b/test/crud/v2/updateWithPipelines.json new file mode 100644 index 0000000..a310f28 --- /dev/null +++ b/test/crud/v2/updateWithPipelines.json @@ -0,0 +1,408 @@ +{ + "runOn": [ + { + "minServerVersion": "4.1.11" + } + ], + "data": [ + { + "_id": 1, + "x": 1, + "y": 1, + "t": { + "u": { + "v": 1 + } + } + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ], + "collection_name": "test", + "database_name": "crud-tests", + "tests": [ + { + "description": "UpdateOne using pipelines", + "operations": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + ] + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "u": { + "v": 1 + }, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + } + }, + { + "description": "UpdateMany using pipelines", + "operations": [ + { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true + } + ] + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + } + }, + { + "description": "FindOneAndUpdate using pipelines", + "operations": [ + { + "name": "findOneAndUpdate", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "findAndModify": "test", + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + }, + "command_name": "findAndModify", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + } + }, + { + "description": "UpdateOne in bulk write using pipelines", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateOne", + "arguments": { + "filter": { + "_id": 1 + }, + "update": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ] + }, + "result": { + "matchedCount": 1, + "modifiedCount": 1, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": { + "_id": 1 + }, + "u": [ + { + "$replaceRoot": { + "newRoot": "$t" + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + ] + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "u": { + "v": 1 + }, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "y": 1 + } + ] + } + } + }, + { + "description": "UpdateMany in bulk write using pipelines", + "operations": [ + { + "name": "bulkWrite", + "arguments": { + "requests": [ + { + "name": "updateMany", + "arguments": { + "filter": {}, + "update": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ] + } + } + ] + }, + "result": { + "matchedCount": 2, + "modifiedCount": 2, + "upsertedCount": 0 + } + } + ], + "expectations": [ + { + "command_started_event": { + "command": { + "update": "test", + "updates": [ + { + "q": {}, + "u": [ + { + "$project": { + "x": 1 + } + }, + { + "$addFields": { + "foo": 1 + } + } + ], + "multi": true + } + ] + }, + "command_name": "update", + "database_name": "crud-tests" + } + } + ], + "outcome": { + "collection": { + "data": [ + { + "_id": 1, + "x": 1, + "foo": 1 + }, + { + "_id": 2, + "x": 2, + "foo": 1 + } + ] + } + } + } + ] +} diff --git a/test/test_crud_v2.py b/test/test_crud_v2.py new file mode 100644 index 0000000..562e119 --- /dev/null +++ b/test/test_crud_v2.py @@ -0,0 +1,72 @@ +# Copyright 2019-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Test the collection module.""" + +import os +import sys + +sys.path[0:0] = [""] + +from test import unittest +from test.utils import TestCreator +from test.utils_spec_runner import SpecRunner + + +# Location of JSON test specifications. +_TEST_PATH = os.path.join( + os.path.dirname(os.path.realpath(__file__)), 'crud', 'v2') + +# Default test database and collection names. +TEST_DB = 'testdb' +TEST_COLLECTION = 'testcollection' + + +class TestSpec(SpecRunner): + def get_scenario_db_name(self, scenario_def): + """Crud spec says database_name is optional.""" + return scenario_def.get('database_name', TEST_DB) + + def get_scenario_coll_name(self, scenario_def): + """Crud spec says collection_name is optional.""" + return scenario_def.get('collection_name', TEST_COLLECTION) + + def get_object_name(self, op): + """Crud spec says object is optional and defaults to 'collection'.""" + return op.get('object', 'collection') + + def get_outcome_coll_name(self, outcome, collection): + """Crud spec says outcome has an optional 'collection.name'.""" + return outcome['collection'].get('name', collection.name) + + def setup_scenario(self, scenario_def): + """Allow specs to override a test's setup.""" + # PYTHON-1935 Only create the collection if there is data to insert. + if scenario_def['data']: + super(TestSpec, self).setup_scenario(scenario_def) + + +def create_test(scenario_def, test, name): + def run_scenario(self): + self.run_scenario(scenario_def, test) + + return run_scenario + + +test_creator = TestCreator(create_test, TestSpec, _TEST_PATH) +test_creator.create_tests() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/utils.py b/test/utils.py new file mode 100644 index 0000000..4e213d5 --- /dev/null +++ b/test/utils.py @@ -0,0 +1,910 @@ +# Copyright 2012-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities for testing pymongo +""" + +import collections +import contextlib +import functools +import os +import re +import shutil +import sys +import threading +import time +import warnings + +from collections import defaultdict +from functools import partial + +from bson import json_util, py3compat +from bson.objectid import ObjectId + +from pymongo import (MongoClient, + monitoring, read_preferences) +from pymongo.errors import ConfigurationError, OperationFailure +from pymongo.monitoring import _SENSITIVE_COMMANDS, ConnectionPoolListener +from pymongo.pool import PoolOptions +from pymongo.read_concern import ReadConcern +from pymongo.read_preferences import ReadPreference +from pymongo.server_selectors import (any_server_selector, + writable_server_selector) +from pymongo.server_type import SERVER_TYPE +from pymongo.write_concern import WriteConcern + +from test import (client_context, + db_user, + db_pwd) + +if sys.version_info[0] < 3: + # Python 2.7, use our backport. + from test.barrier import Barrier +else: + from threading import Barrier + + +IMPOSSIBLE_WRITE_CONCERN = WriteConcern(w=50) + + +class CMAPListener(ConnectionPoolListener): + def __init__(self): + self.events = [] + + def reset(self): + self.events = [] + + def add_event(self, event): + self.events.append(event) + + def event_count(self, event_type): + return len([event for event in self.events[:] + if isinstance(event, event_type)]) + + def connection_created(self, event): + self.add_event(event) + + def connection_ready(self, event): + self.add_event(event) + + def connection_closed(self, event): + self.add_event(event) + + def connection_check_out_started(self, event): + self.add_event(event) + + def connection_check_out_failed(self, event): + self.add_event(event) + + def connection_checked_out(self, event): + self.add_event(event) + + def connection_checked_in(self, event): + self.add_event(event) + + def pool_created(self, event): + self.add_event(event) + + def pool_cleared(self, event): + self.add_event(event) + + def pool_closed(self, event): + self.add_event(event) + + +class EventListener(monitoring.CommandListener): + + def __init__(self): + self.results = defaultdict(list) + + def started(self, event): + self.results['started'].append(event) + + def succeeded(self, event): + self.results['succeeded'].append(event) + + def failed(self, event): + self.results['failed'].append(event) + + def started_command_names(self): + """Return list of command names started.""" + return [event.command_name for event in self.results['started']] + + def reset(self): + """Reset the state of this listener.""" + self.results.clear() + + +class WhiteListEventListener(EventListener): + + def __init__(self, *commands): + self.commands = set(commands) + super(WhiteListEventListener, self).__init__() + + def started(self, event): + if event.command_name in self.commands: + super(WhiteListEventListener, self).started(event) + + def succeeded(self, event): + if event.command_name in self.commands: + super(WhiteListEventListener, self).succeeded(event) + + def failed(self, event): + if event.command_name in self.commands: + super(WhiteListEventListener, self).failed(event) + + +class OvertCommandListener(EventListener): + """A CommandListener that ignores sensitive commands.""" + def started(self, event): + if event.command_name.lower() not in _SENSITIVE_COMMANDS: + super(OvertCommandListener, self).started(event) + + def succeeded(self, event): + if event.command_name.lower() not in _SENSITIVE_COMMANDS: + super(OvertCommandListener, self).succeeded(event) + + def failed(self, event): + if event.command_name.lower() not in _SENSITIVE_COMMANDS: + super(OvertCommandListener, self).failed(event) + + +class ServerAndTopologyEventListener(monitoring.ServerListener, + monitoring.TopologyListener): + """Listens to all events.""" + + def __init__(self): + self.results = [] + + def opened(self, event): + self.results.append(event) + + def description_changed(self, event): + self.results.append(event) + + def closed(self, event): + self.results.append(event) + + def matching(self, matcher): + """Return the matching events.""" + results = self.results[:] + return [event for event in results if matcher(event)] + + def reset(self): + self.results = [] + + +class HeartbeatEventListener(monitoring.ServerHeartbeatListener): + """Listens to only server heartbeat events.""" + + def __init__(self): + self.results = [] + + def started(self, event): + self.results.append(event) + + def succeeded(self, event): + self.results.append(event) + + def failed(self, event): + self.results.append(event) + + +class MockSocketInfo(object): + def close(self): + pass + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + pass + + +class MockPool(object): + def __init__(self, *args, **kwargs): + self.generation = 0 + self._lock = threading.Lock() + self.opts = PoolOptions() + + def get_socket(self, all_credentials): + return MockSocketInfo() + + def return_socket(self, *args, **kwargs): + pass + + def _reset(self): + with self._lock: + self.generation += 1 + + def reset(self): + self._reset() + + def close(self): + self._reset() + + def update_is_writable(self, is_writable): + pass + + def remove_stale_sockets(self, *args, **kwargs): + pass + + +class ScenarioDict(dict): + """Dict that returns {} for any unknown key, recursively.""" + def __init__(self, data): + def convert(v): + if isinstance(v, collections.Mapping): + return ScenarioDict(v) + if isinstance(v, (py3compat.string_type, bytes)): + return v + if isinstance(v, collections.Sequence): + return [convert(item) for item in v] + return v + + dict.__init__(self, [(k, convert(v)) for k, v in data.items()]) + + def __getitem__(self, item): + try: + return dict.__getitem__(self, item) + except KeyError: + # Unlike a defaultdict, don't set the key, just return a dict. + return ScenarioDict({}) + + +class CompareType(object): + """Class that compares equal to any object of the given type.""" + def __init__(self, type): + self.type = type + + def __eq__(self, other): + return isinstance(other, self.type) + + def __ne__(self, other): + """Needed for Python 2.""" + return not self.__eq__(other) + + +class FunctionCallRecorder(object): + """Utility class to wrap a callable and record its invocations.""" + def __init__(self, function): + self._function = function + self._call_list = [] + + def __call__(self, *args, **kwargs): + self._call_list.append((args, kwargs)) + return self._function(*args, **kwargs) + + def reset(self): + """Wipes the call list.""" + self._call_list = [] + + def call_list(self): + """Returns a copy of the call list.""" + return self._call_list[:] + + @property + def call_count(self): + """Returns the number of times the function has been called.""" + return len(self._call_list) + + +class TestCreator(object): + """Class to create test cases from specifications.""" + def __init__(self, create_test, test_class, test_path): + """Create a TestCreator object. + + :Parameters: + - `create_test`: callback that returns a test case. The callback + must accept the following arguments - a dictionary containing the + entire test specification (the `scenario_def`), a dictionary + containing the specification for which the test case will be + generated (the `test_def`). + - `test_class`: the unittest.TestCase class in which to create the + test case. + - `test_path`: path to the directory containing the JSON files with + the test specifications. + """ + self._create_test = create_test + self._test_class = test_class + self.test_path = test_path + + def _ensure_min_max_server_version(self, scenario_def, method): + """Test modifier that enforces a version range for the server on a + test case.""" + if 'minServerVersion' in scenario_def: + min_ver = tuple( + int(elt) for + elt in scenario_def['minServerVersion'].split('.')) + if min_ver is not None: + method = client_context.require_version_min(*min_ver)(method) + + if 'maxServerVersion' in scenario_def: + max_ver = tuple( + int(elt) for + elt in scenario_def['maxServerVersion'].split('.')) + if max_ver is not None: + method = client_context.require_version_max(*max_ver)(method) + + return method + + @staticmethod + def valid_topology(run_on_req): + return client_context.is_topology_type( + run_on_req.get('topology', ['single', 'replicaset', 'sharded'])) + + @staticmethod + def min_server_version(run_on_req): + version = run_on_req.get('minServerVersion') + if version: + min_ver = tuple(int(elt) for elt in version.split('.')) + return client_context.version >= min_ver + return True + + @staticmethod + def max_server_version(run_on_req): + version = run_on_req.get('maxServerVersion') + if version: + max_ver = tuple(int(elt) for elt in version.split('.')) + return client_context.version <= max_ver + return True + + def should_run_on(self, scenario_def): + run_on = scenario_def.get('runOn', []) + if not run_on: + # Always run these tests. + return True + + for req in run_on: + if (self.valid_topology(req) and + self.min_server_version(req) and + self.max_server_version(req)): + return True + return False + + def ensure_run_on(self, scenario_def, method): + """Test modifier that enforces a 'runOn' on a test case.""" + return client_context._require( + lambda: self.should_run_on(scenario_def), + "runOn not satisfied", + method) + + def tests(self, scenario_def): + """Allow CMAP spec test to override the location of test.""" + return scenario_def['tests'] + + def create_tests(self): + for dirpath, _, filenames in os.walk(self.test_path): + dirname = os.path.split(dirpath)[-1] + + for filename in filenames: + with open(os.path.join(dirpath, filename)) as scenario_stream: + # Use tz_aware=False to match how CodecOptions decodes + # dates. + opts = json_util.JSONOptions(tz_aware=False) + scenario_def = ScenarioDict( + json_util.loads(scenario_stream.read(), + json_options=opts)) + + test_type = os.path.splitext(filename)[0] + + # Construct test from scenario. + for test_def in self.tests(scenario_def): + test_name = 'test_%s_%s_%s' % ( + dirname, + test_type.replace("-", "_").replace('.', '_'), + str(test_def['description'].replace(" ", "_").replace( + '.', '_'))) + + new_test = self._create_test( + scenario_def, test_def, test_name) + new_test = self._ensure_min_max_server_version( + scenario_def, new_test) + new_test = self.ensure_run_on( + scenario_def, new_test) + + new_test.__name__ = test_name + setattr(self._test_class, new_test.__name__, new_test) + + +def _connection_string(h, authenticate): + if h.startswith("mongodb://"): + return h + elif client_context.auth_enabled and authenticate: + return "mongodb://%s:%s@%s" % (db_user, db_pwd, str(h)) + else: + return "mongodb://%s" % (str(h),) + + +def _mongo_client(host, port, authenticate=True, directConnection=False, + **kwargs): + """Create a new client over SSL/TLS if necessary.""" + host = host or client_context.host + port = port or client_context.port + client_options = client_context.default_client_options.copy() + if client_context.replica_set_name and not directConnection: + client_options['replicaSet'] = client_context.replica_set_name + client_options.update(kwargs) + + client = MongoClient(_connection_string(host, authenticate), port, + **client_options) + + return client + + +def single_client_noauth(h=None, p=None, **kwargs): + """Make a direct connection. Don't authenticate.""" + return _mongo_client(h, p, authenticate=False, + directConnection=True, **kwargs) + + +def single_client(h=None, p=None, **kwargs): + """Make a direct connection, and authenticate if necessary.""" + return _mongo_client(h, p, directConnection=True, **kwargs) + + +def rs_client_noauth(h=None, p=None, **kwargs): + """Connect to the replica set. Don't authenticate.""" + return _mongo_client(h, p, authenticate=False, **kwargs) + + +def rs_client(h=None, p=None, **kwargs): + """Connect to the replica set and authenticate if necessary.""" + return _mongo_client(h, p, **kwargs) + + +def rs_or_single_client_noauth(h=None, p=None, **kwargs): + """Connect to the replica set if there is one, otherwise the standalone. + + Like rs_or_single_client, but does not authenticate. + """ + return _mongo_client(h, p, authenticate=False, **kwargs) + + +def rs_or_single_client(h=None, p=None, **kwargs): + """Connect to the replica set if there is one, otherwise the standalone. + + Authenticates if necessary. + """ + return _mongo_client(h, p, **kwargs) + + +def ensure_all_connected(client): + """Ensure that the client's connection pool has socket connections to all + members of a replica set. Raises ConfigurationError when called with a + non-replica set client. + + Depending on the use-case, the caller may need to clear any event listeners + that are configured on the client. + """ + ismaster = client.admin.command("isMaster") + if 'setName' not in ismaster: + raise ConfigurationError("cluster is not a replica set") + + target_host_list = set(ismaster['hosts']) + connected_host_list = set([ismaster['me']]) + admindb = client.get_database('admin') + + # Run isMaster until we have connected to each host at least once. + while connected_host_list != target_host_list: + ismaster = admindb.command("isMaster", + read_preference=ReadPreference.SECONDARY) + connected_host_list.update([ismaster["me"]]) + + +def one(s): + """Get one element of a set""" + return next(iter(s)) + + +def oid_generated_on_process(oid): + """Makes a determination as to whether the given ObjectId was generated + by the current process, based on the 5-byte random number in the ObjectId. + """ + return ObjectId._random() == oid.binary[4:9] + + +def delay(sec): + return '''function() { sleep(%f * 1000); return true; }''' % sec + + +def get_command_line(client): + command_line = client.admin.command('getCmdLineOpts') + assert command_line['ok'] == 1, "getCmdLineOpts() failed" + return command_line + + +def camel_to_snake(camel): + # Regex to convert CamelCase to snake_case. + snake = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', snake).lower() + + +def camel_to_upper_camel(camel): + return camel[0].upper() + camel[1:] + + +def camel_to_snake_args(arguments): + for arg_name in list(arguments): + c2s = camel_to_snake(arg_name) + arguments[c2s] = arguments.pop(arg_name) + return arguments + + +def parse_collection_options(opts): + if 'readPreference' in opts: + opts['read_preference'] = parse_read_preference( + opts.pop('readPreference')) + + if 'writeConcern' in opts: + opts['write_concern'] = WriteConcern( + **dict(opts.pop('writeConcern'))) + + if 'readConcern' in opts: + opts['read_concern'] = ReadConcern( + **dict(opts.pop('readConcern'))) + return opts + + +def server_started_with_option(client, cmdline_opt, config_opt): + """Check if the server was started with a particular option. + + :Parameters: + - `cmdline_opt`: The command line option (i.e. --nojournal) + - `config_opt`: The config file option (i.e. nojournal) + """ + command_line = get_command_line(client) + if 'parsed' in command_line: + parsed = command_line['parsed'] + if config_opt in parsed: + return parsed[config_opt] + argv = command_line['argv'] + return cmdline_opt in argv + + +def server_started_with_auth(client): + try: + command_line = get_command_line(client) + except OperationFailure as e: + msg = e.details.get('errmsg', '') + if e.code == 13 or 'unauthorized' in msg or 'login' in msg: + # Unauthorized. + return True + raise + + # MongoDB >= 2.0 + if 'parsed' in command_line: + parsed = command_line['parsed'] + # MongoDB >= 2.6 + if 'security' in parsed: + security = parsed['security'] + # >= rc3 + if 'authorization' in security: + return security['authorization'] == 'enabled' + # < rc3 + return security.get('auth', False) or bool(security.get('keyFile')) + return parsed.get('auth', False) or bool(parsed.get('keyFile')) + # Legacy + argv = command_line['argv'] + return '--auth' in argv or '--keyFile' in argv + + +def server_started_with_nojournal(client): + command_line = get_command_line(client) + + # MongoDB 2.6. + if 'parsed' in command_line: + parsed = command_line['parsed'] + if 'storage' in parsed: + storage = parsed['storage'] + if 'journal' in storage: + return not storage['journal']['enabled'] + + return server_started_with_option(client, '--nojournal', 'nojournal') + + +def server_is_master_with_slave(client): + command_line = get_command_line(client) + if 'parsed' in command_line: + return command_line['parsed'].get('master', False) + return '--master' in command_line['argv'] + + +def drop_collections(db): + # Drop all non-system collections in this database. + for coll in db.list_collection_names( + filter={"name": {"$regex": r"^(?!system\.)"}}): + db.drop_collection(coll) + + +def remove_all_users(db): + db.command("dropAllUsersFromDatabase", 1, + writeConcern={"w": client_context.w}) + + +def joinall(threads): + """Join threads with a 5-minute timeout, assert joins succeeded""" + for t in threads: + t.join(300) + assert not t.is_alive(), "Thread %s hung" % t + + +def connected(client): + """Convenience to wait for a newly-constructed client to connect.""" + with warnings.catch_warnings(): + # Ignore warning that "ismaster" is always routed to primary even + # if client's read preference isn't PRIMARY. + warnings.simplefilter("ignore", UserWarning) + client.admin.command('ismaster') # Force connection. + + return client + + +def wait_until(predicate, success_description, timeout=10): + """Wait up to 10 seconds (by default) for predicate to be true. + + E.g.: + + wait_until(lambda: client.primary == ('a', 1), + 'connect to the primary') + + If the lambda-expression isn't true after 10 seconds, we raise + AssertionError("Didn't ever connect to the primary"). + + Returns the predicate's first true value. + """ + start = time.time() + interval = min(float(timeout)/100, 0.1) + while True: + retval = predicate() + if retval: + return retval + + if time.time() - start > timeout: + raise AssertionError("Didn't ever %s" % success_description) + + time.sleep(interval) + + +def is_mongos(client): + res = client.admin.command('ismaster') + return res.get('msg', '') == 'isdbgrid' + + +def assertRaisesExactly(cls, fn, *args, **kwargs): + """ + Unlike the standard assertRaises, this checks that a function raises a + specific class of exception, and not a subclass. E.g., check that + MongoClient() raises ConnectionFailure but not its subclass, AutoReconnect. + """ + try: + fn(*args, **kwargs) + except Exception as e: + assert e.__class__ == cls, "got %s, expected %s" % ( + e.__class__.__name__, cls.__name__) + else: + raise AssertionError("%s not raised" % cls) + + +@contextlib.contextmanager +def _ignore_deprecations(): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + yield + + +def ignore_deprecations(wrapped=None): + """A context manager or a decorator.""" + if wrapped: + @functools.wraps(wrapped) + def wrapper(*args, **kwargs): + with _ignore_deprecations(): + return wrapped(*args, **kwargs) + + return wrapper + + else: + return _ignore_deprecations() + + +class DeprecationFilter(object): + + def __init__(self, action="ignore"): + """Start filtering deprecations.""" + self.warn_context = warnings.catch_warnings() + self.warn_context.__enter__() + warnings.simplefilter(action, DeprecationWarning) + + def stop(self): + """Stop filtering deprecations.""" + self.warn_context.__exit__() + self.warn_context = None + + +def get_pool(client): + """Get the standalone, primary, or mongos pool.""" + topology = client._get_topology() + server = topology.select_server(writable_server_selector) + return server.pool + + +def get_pools(client): + """Get all pools.""" + return [ + server.pool for server in + client._get_topology().select_servers(any_server_selector)] + + +# Constants for run_threads and lazy_client_trial. +NTRIALS = 5 +NTHREADS = 10 + + +def run_threads(collection, target): + """Run a target function in many threads. + + target is a function taking a Collection and an integer. + """ + threads = [] + for i in range(NTHREADS): + bound_target = partial(target, collection, i) + threads.append(threading.Thread(target=bound_target)) + + for t in threads: + t.start() + + for t in threads: + t.join(60) + assert not t.is_alive() + + +@contextlib.contextmanager +def frequent_thread_switches(): + """Make concurrency bugs more likely to manifest.""" + interval = None + if not sys.platform.startswith('java'): + if hasattr(sys, 'getswitchinterval'): + interval = sys.getswitchinterval() + sys.setswitchinterval(1e-6) + else: + interval = sys.getcheckinterval() + sys.setcheckinterval(1) + + try: + yield + finally: + if not sys.platform.startswith('java'): + if hasattr(sys, 'setswitchinterval'): + sys.setswitchinterval(interval) + else: + sys.setcheckinterval(interval) + + +def lazy_client_trial(reset, target, test, get_client): + """Test concurrent operations on a lazily-connecting client. + + `reset` takes a collection and resets it for the next trial. + + `target` takes a lazily-connecting collection and an index from + 0 to NTHREADS, and performs some operation, e.g. an insert. + + `test` takes the lazily-connecting collection and asserts a + post-condition to prove `target` succeeded. + """ + collection = client_context.client.pymongo_test.test + + with frequent_thread_switches(): + for i in range(NTRIALS): + reset(collection) + lazy_client = get_client() + lazy_collection = lazy_client.pymongo_test.test + run_threads(lazy_collection, target) + test(lazy_collection) + + +def gevent_monkey_patched(): + """Check if gevent's monkey patching is active.""" + # In Python 3.6 importing gevent.socket raises an ImportWarning. + with warnings.catch_warnings(): + warnings.simplefilter("ignore", ImportWarning) + try: + import socket + import gevent.socket + return socket.socket is gevent.socket.socket + except ImportError: + return False + + +def eventlet_monkey_patched(): + """Check if eventlet's monkey patching is active.""" + try: + import threading + import eventlet + return (threading.current_thread.__module__ == + 'eventlet.green.threading') + except ImportError: + return False + + +def is_greenthread_patched(): + return gevent_monkey_patched() or eventlet_monkey_patched() + + +def disable_replication(client): + """Disable replication on all secondaries, requires MongoDB 3.2.""" + for host, port in client.secondaries: + secondary = single_client(host, port) + secondary.admin.command('configureFailPoint', 'stopReplProducer', + mode='alwaysOn') + + +def enable_replication(client): + """Enable replication on all secondaries, requires MongoDB 3.2.""" + for host, port in client.secondaries: + secondary = single_client(host, port) + secondary.admin.command('configureFailPoint', 'stopReplProducer', + mode='off') + + +class ExceptionCatchingThread(threading.Thread): + """A thread that stores any exception encountered from run().""" + def __init__(self, *args, **kwargs): + self.exc = None + super(ExceptionCatchingThread, self).__init__(*args, **kwargs) + + def run(self): + try: + super(ExceptionCatchingThread, self).run() + except BaseException as exc: + self.exc = exc + raise + + +def parse_read_preference(pref): + # Make first letter lowercase to match read_pref's modes. + mode_string = pref.get('mode', 'primary') + mode_string = mode_string[:1].lower() + mode_string[1:] + mode = read_preferences.read_pref_mode_from_name(mode_string) + max_staleness = pref.get('maxStalenessSeconds', -1) + tag_sets = pref.get('tag_sets') + return read_preferences.make_read_preference( + mode, tag_sets=tag_sets, max_staleness=max_staleness) + + +def server_name_to_type(name): + """Convert a ServerType name to the corresponding value. For SDAM tests.""" + # Special case, some tests in the spec include the PossiblePrimary + # type, but only single-threaded drivers need that type. We call + # possible primaries Unknown. + if name == 'PossiblePrimary': + return SERVER_TYPE.Unknown + return getattr(SERVER_TYPE, name) + + +def cat_files(dest, *sources): + """Cat multiple files into dest.""" + with open(dest, 'wb') as fdst: + for src in sources: + with open(src, 'rb') as fsrc: + shutil.copyfileobj(fsrc, fdst) + + +@contextlib.contextmanager +def assertion_context(msg): + """A context manager that adds info to an assertion failure.""" + try: + yield + except AssertionError as exc: + msg = '%s (%s)' % (exc, msg) + py3compat.reraise(type(exc), msg, sys.exc_info()[2]) diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py new file mode 100644 index 0000000..68faa8c --- /dev/null +++ b/test/utils_spec_runner.py @@ -0,0 +1,694 @@ +# Copyright 2019-present MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Utilities for testing driver specs.""" + +import copy +import sys + + +from bson import decode, encode +from bson.binary import Binary, STANDARD +from bson.codec_options import CodecOptions +from bson.int64 import Int64 +from bson.py3compat import iteritems, abc, string_type, text_type +from bson.son import SON + +from gridfs import GridFSBucket + +from pymongo import (client_session, + helpers, + operations) +from pymongo.command_cursor import CommandCursor +from pymongo.cursor import Cursor +from pymongo.errors import (BulkWriteError, + OperationFailure, + PyMongoError) +from pymongo.read_concern import ReadConcern +from pymongo.read_preferences import ReadPreference +from pymongo.results import _WriteResult, BulkWriteResult +from pymongo.write_concern import WriteConcern + +from test import (client_context, + client_knobs, + IntegrationTest, + unittest) +from test.utils import (camel_to_snake, + camel_to_snake_args, + camel_to_upper_camel, + CompareType, + OvertCommandListener, + rs_client, parse_read_preference) + + +class SpecRunner(IntegrationTest): + + @classmethod + def setUpClass(cls): + super(SpecRunner, cls).setUpClass() + cls.mongos_clients = [] + + # Speed up the tests by decreasing the heartbeat frequency. + cls.knobs = client_knobs(min_heartbeat_interval=0.1) + cls.knobs.enable() + + @classmethod + def tearDownClass(cls): + cls.knobs.disable() + super(SpecRunner, cls).tearDownClass() + + def setUp(self): + super(SpecRunner, self).setUp() + self.listener = None + self.maxDiff = None + + def _set_fail_point(self, client, command_args): + cmd = SON([('configureFailPoint', 'failCommand')]) + cmd.update(command_args) + client.admin.command(cmd) + + def set_fail_point(self, command_args): + cmd = SON([('configureFailPoint', 'failCommand')]) + cmd.update(command_args) + clients = self.mongos_clients if self.mongos_clients else [self.client] + for client in clients: + self._set_fail_point(client, cmd) + + def targeted_fail_point(self, session, fail_point): + """Run the targetedFailPoint test operation. + + Enable the fail point on the session's pinned mongos. + """ + clients = {c.address: c for c in self.mongos_clients} + client = clients[session._pinned_address] + self._set_fail_point(client, fail_point) + self.addCleanup(self.set_fail_point, {'mode': 'off'}) + + def assert_session_pinned(self, session): + """Run the assertSessionPinned test operation. + + Assert that the given session is pinned. + """ + self.assertIsNotNone(session._transaction.pinned_address) + + def assert_session_unpinned(self, session): + """Run the assertSessionUnpinned test operation. + + Assert that the given session is not pinned. + """ + self.assertIsNone(session._pinned_address) + self.assertIsNone(session._transaction.pinned_address) + + def assert_collection_exists(self, database, collection): + """Run the assertCollectionExists test operation.""" + db = self.client[database] + self.assertIn(collection, db.list_collection_names()) + + def assert_collection_not_exists(self, database, collection): + """Run the assertCollectionNotExists test operation.""" + db = self.client[database] + self.assertNotIn(collection, db.list_collection_names()) + + def assert_index_exists(self, database, collection, index): + """Run the assertIndexExists test operation.""" + coll = self.client[database][collection] + self.assertIn(index, [doc['name'] for doc in coll.list_indexes()]) + + def assert_index_not_exists(self, database, collection, index): + """Run the assertIndexNotExists test operation.""" + coll = self.client[database][collection] + self.assertNotIn(index, [doc['name'] for doc in coll.list_indexes()]) + + def assertErrorLabelsContain(self, exc, expected_labels): + labels = [l for l in expected_labels if exc.has_error_label(l)] + self.assertEqual(labels, expected_labels) + + def assertErrorLabelsOmit(self, exc, omit_labels): + for label in omit_labels: + self.assertFalse( + exc.has_error_label(label), + msg='error labels should not contain %s' % (label,)) + + def kill_all_sessions(self): + clients = self.mongos_clients if self.mongos_clients else [self.client] + for client in clients: + try: + client.admin.command('killAllSessions', []) + except OperationFailure: + # "operation was interrupted" by killing the command's + # own session. + pass + + def check_command_result(self, expected_result, result): + # Only compare the keys in the expected result. + filtered_result = {} + for key in expected_result: + try: + filtered_result[key] = result[key] + except KeyError: + pass + self.assertEqual(filtered_result, expected_result) + + # TODO: factor the following function with test_crud.py. + def check_result(self, expected_result, result): + if isinstance(result, _WriteResult): + for res in expected_result: + prop = camel_to_snake(res) + # SPEC-869: Only BulkWriteResult has upserted_count. + if (prop == "upserted_count" + and not isinstance(result, BulkWriteResult)): + if result.upserted_id is not None: + upserted_count = 1 + else: + upserted_count = 0 + self.assertEqual(upserted_count, expected_result[res], prop) + elif prop == "inserted_ids": + # BulkWriteResult does not have inserted_ids. + if isinstance(result, BulkWriteResult): + self.assertEqual(len(expected_result[res]), + result.inserted_count) + else: + # InsertManyResult may be compared to [id1] from the + # crud spec or {"0": id1} from the retryable write spec. + ids = expected_result[res] + if isinstance(ids, dict): + ids = [ids[str(i)] for i in range(len(ids))] + self.assertEqual(ids, result.inserted_ids, prop) + elif prop == "upserted_ids": + # Convert indexes from strings to integers. + ids = expected_result[res] + expected_ids = {} + for str_index in ids: + expected_ids[int(str_index)] = ids[str_index] + self.assertEqual(expected_ids, result.upserted_ids, prop) + else: + self.assertEqual( + getattr(result, prop), expected_result[res], prop) + + return True + else: + self.assertEqual(result, expected_result) + + def get_object_name(self, op): + """Allow subclasses to override handling of 'object' + + Transaction spec says 'object' is required. + """ + return op['object'] + + @staticmethod + def parse_options(opts): + if 'readPreference' in opts: + opts['read_preference'] = parse_read_preference( + opts.pop('readPreference')) + + if 'writeConcern' in opts: + opts['write_concern'] = WriteConcern( + **dict(opts.pop('writeConcern'))) + + if 'readConcern' in opts: + opts['read_concern'] = ReadConcern( + **dict(opts.pop('readConcern'))) + + if 'maxTimeMS' in opts: + opts['max_time_ms'] = opts.pop('maxTimeMS') + + if 'maxCommitTimeMS' in opts: + opts['max_commit_time_ms'] = opts.pop('maxCommitTimeMS') + + if 'hint' in opts: + hint = opts.pop('hint') + if not isinstance(hint, string_type): + hint = list(iteritems(hint)) + opts['hint'] = hint + + # Properly format 'hint' arguments for the Bulk API tests. + if 'requests' in opts: + reqs = opts.pop('requests') + for req in reqs: + args = req.pop('arguments') + if 'hint' in args: + hint = args.pop('hint') + if not isinstance(hint, string_type): + hint = list(iteritems(hint)) + args['hint'] = hint + req['arguments'] = args + opts['requests'] = reqs + + return dict(opts) + + def run_operation(self, sessions, collection, operation): + original_collection = collection + name = camel_to_snake(operation['name']) + if name == 'run_command': + name = 'command' + elif name == 'download_by_name': + name = 'open_download_stream_by_name' + elif name == 'download': + name = 'open_download_stream' + + database = collection.database + collection = database.get_collection(collection.name) + if 'collectionOptions' in operation: + collection = collection.with_options( + **self.parse_options(operation['collectionOptions'])) + + object_name = self.get_object_name(operation) + if object_name == 'gridfsbucket': + # Only create the GridFSBucket when we need it (for the gridfs + # retryable reads tests). + obj = GridFSBucket( + database, bucket_name=collection.name, + disable_md5=True) + else: + objects = { + 'client': database.client, + 'database': database, + 'collection': collection, + 'testRunner': self + } + objects.update(sessions) + obj = objects[object_name] + + # Combine arguments with options and handle special cases. + arguments = operation.get('arguments', {}) + arguments.update(arguments.pop("options", {})) + self.parse_options(arguments) + + cmd = getattr(obj, name) + + for arg_name in list(arguments): + c2s = camel_to_snake(arg_name) + # PyMongo accepts sort as list of tuples. + if arg_name == "sort": + sort_dict = arguments[arg_name] + arguments[arg_name] = list(iteritems(sort_dict)) + # Named "key" instead not fieldName. + if arg_name == "fieldName": + arguments["key"] = arguments.pop(arg_name) + # Aggregate uses "batchSize", while find uses batch_size. + elif ((arg_name == "batchSize" or arg_name == "allowDiskUse") and + name == "aggregate"): + continue + # Requires boolean returnDocument. + elif arg_name == "returnDocument": + arguments[c2s] = arguments.pop(arg_name) == "After" + elif c2s == "requests": + # Parse each request into a bulk write model. + requests = [] + for request in arguments["requests"]: + bulk_model = camel_to_upper_camel(request["name"]) + bulk_class = getattr(operations, bulk_model) + bulk_arguments = camel_to_snake_args(request["arguments"]) + requests.append(bulk_class(**dict(bulk_arguments))) + arguments["requests"] = requests + elif arg_name == "session": + arguments['session'] = sessions[arguments['session']] + elif name == 'command' and arg_name == 'command': + # Ensure the first key is the command name. + ordered_command = SON([(operation['command_name'], 1)]) + ordered_command.update(arguments['command']) + arguments['command'] = ordered_command + elif name == 'open_download_stream' and arg_name == 'id': + arguments['file_id'] = arguments.pop(arg_name) + elif name != 'find' and c2s == 'max_time_ms': + # find is the only method that accepts snake_case max_time_ms. + # All other methods take kwargs which must use the server's + # camelCase maxTimeMS. See PYTHON-1855. + arguments['maxTimeMS'] = arguments.pop('max_time_ms') + elif name == 'with_transaction' and arg_name == 'callback': + callback_ops = arguments[arg_name]['operations'] + arguments['callback'] = lambda _: self.run_operations( + sessions, original_collection, copy.deepcopy(callback_ops), + in_with_transaction=True) + elif name == 'drop_collection' and arg_name == 'collection': + arguments['name_or_collection'] = arguments.pop(arg_name) + elif name == 'create_collection' and arg_name == 'collection': + arguments['name'] = arguments.pop(arg_name) + elif name == 'create_index' and arg_name == 'keys': + arguments['keys'] = list(arguments.pop(arg_name).items()) + elif name == 'drop_index' and arg_name == 'name': + arguments['index_or_name'] = arguments.pop(arg_name) + else: + arguments[c2s] = arguments.pop(arg_name) + + result = cmd(**dict(arguments)) + + if name == "aggregate": + if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: + # Read from the primary to ensure causal consistency. + out = collection.database.get_collection( + arguments["pipeline"][-1]["$out"], + read_preference=ReadPreference.PRIMARY) + return out.find() + if name == "map_reduce": + if isinstance(result, dict) and 'results' in result: + return result['results'] + if 'download' in name: + result = Binary(result.read()) + + if isinstance(result, Cursor) or isinstance(result, CommandCursor): + return list(result) + + return result + + def allowable_errors(self, op): + """Allow encryption spec to override expected error classes.""" + return (PyMongoError,) + + def run_operations(self, sessions, collection, ops, + in_with_transaction=False): + for op in ops: + expected_result = op.get('result') + if expect_error(op): + with self.assertRaises(self.allowable_errors(op), + msg=op['name']) as context: + self.run_operation(sessions, collection, op.copy()) + + if expect_error_message(expected_result): + if isinstance(context.exception, BulkWriteError): + errmsg = str(context.exception.details).lower() + else: + errmsg = str(context.exception).lower() + self.assertIn(expected_result['errorContains'].lower(), + errmsg) + if expect_error_code(expected_result): + self.assertEqual(expected_result['errorCodeName'], + context.exception.details.get('codeName')) + if expect_error_labels_contain(expected_result): + self.assertErrorLabelsContain( + context.exception, + expected_result['errorLabelsContain']) + if expect_error_labels_omit(expected_result): + self.assertErrorLabelsOmit( + context.exception, + expected_result['errorLabelsOmit']) + + # Reraise the exception if we're in the with_transaction + # callback. + if in_with_transaction: + raise context.exception + else: + result = self.run_operation(sessions, collection, op.copy()) + if 'result' in op: + if op['name'] == 'runCommand': + self.check_command_result(expected_result, result) + else: + self.check_result(expected_result, result) + + # TODO: factor with test_command_monitoring.py + def check_events(self, test, listener, session_ids): + res = listener.results + if not len(test['expectations']): + return + + # Give a nicer message when there are missing or extra events + cmds = decode_raw([event.command for event in res['started']]) + self.assertEqual( + len(res['started']), len(test['expectations']), cmds) + for i, expectation in enumerate(test['expectations']): + event_type = next(iter(expectation)) + event = res['started'][i] + + # The tests substitute 42 for any number other than 0. + if (event.command_name == 'getMore' + and event.command['getMore']): + event.command['getMore'] = Int64(42) + elif event.command_name == 'killCursors': + event.command['cursors'] = [Int64(42)] + elif event.command_name == 'update': + # TODO: remove this once PYTHON-1744 is done. + # Add upsert and multi fields back into expectations. + updates = expectation[event_type]['command']['updates'] + for update in updates: + update.setdefault('upsert', False) + update.setdefault('multi', False) + + # Replace afterClusterTime: 42 with actual afterClusterTime. + expected_cmd = expectation[event_type]['command'] + expected_read_concern = expected_cmd.get('readConcern') + if expected_read_concern is not None: + time = expected_read_concern.get('afterClusterTime') + if time == 42: + actual_time = event.command.get( + 'readConcern', {}).get('afterClusterTime') + if actual_time is not None: + expected_read_concern['afterClusterTime'] = actual_time + + recovery_token = expected_cmd.get('recoveryToken') + if recovery_token == 42: + expected_cmd['recoveryToken'] = CompareType(dict) + + # Replace lsid with a name like "session0" to match test. + if 'lsid' in event.command: + for name, lsid in session_ids.items(): + if event.command['lsid'] == lsid: + event.command['lsid'] = name + break + + for attr, expected in expectation[event_type].items(): + actual = getattr(event, attr) + expected = wrap_types(expected) + if isinstance(expected, dict): + for key, val in expected.items(): + if val is None: + if key in actual: + self.fail("Unexpected key [%s] in %r" % ( + key, actual)) + elif key not in actual: + self.fail("Expected key [%s] in %r" % ( + key, actual)) + else: + self.assertEqual(val, decode_raw(actual[key]), + "Key [%s] in %s" % (key, actual)) + else: + self.assertEqual(actual, expected) + + def maybe_skip_scenario(self, test): + if test.get('skipReason'): + raise unittest.SkipTest(test.get('skipReason')) + + def get_scenario_db_name(self, scenario_def): + """Allow subclasses to override a test's database name.""" + return scenario_def['database_name'] + + def get_scenario_coll_name(self, scenario_def): + """Allow subclasses to override a test's collection name.""" + return scenario_def['collection_name'] + + def get_outcome_coll_name(self, outcome, collection): + """Allow subclasses to override outcome collection.""" + return collection.name + + def run_test_ops(self, sessions, collection, test): + """Added to allow retryable writes spec to override a test's + operation.""" + self.run_operations(sessions, collection, test['operations']) + + def parse_client_options(self, opts): + """Allow encryption spec to override a clientOptions parsing.""" + # Convert test['clientOptions'] to dict to avoid a Jython bug using + # "**" with ScenarioDict. + return dict(opts) + + def setup_scenario(self, scenario_def): + """Allow specs to override a test's setup.""" + db_name = self.get_scenario_db_name(scenario_def) + coll_name = self.get_scenario_coll_name(scenario_def) + db = client_context.client.get_database( + db_name, write_concern=WriteConcern(w='majority')) + coll = db[coll_name] + coll.drop() + db.create_collection(coll_name) + if scenario_def['data']: + # Load data. + coll.insert_many(scenario_def['data']) + + def run_scenario(self, scenario_def, test): + self.maybe_skip_scenario(test) + listener = OvertCommandListener() + # Create a new client, to avoid interference from pooled sessions. + client_options = self.parse_client_options(test['clientOptions']) + # MMAPv1 does not support retryable writes. + if (client_options.get('retryWrites') is True and + client_context.storage_engine == 'mmapv1'): + self.skipTest("MMAPv1 does not support retryWrites=True") + use_multi_mongos = test['useMultipleMongoses'] + if client_context.is_mongos and use_multi_mongos: + client = rs_client(client_context.mongos_seeds(), + event_listeners=[listener], **client_options) + else: + client = rs_client(event_listeners=[listener], **client_options) + self.listener = listener + # Close the client explicitly to avoid having too many threads open. + self.addCleanup(client.close) + + # Kill all sessions before and after each test to prevent an open + # transaction (from a test failure) from blocking collection/database + # operations during test set up and tear down. + self.kill_all_sessions() + self.addCleanup(self.kill_all_sessions) + + database_name = self.get_scenario_db_name(scenario_def) + collection_name = self.get_scenario_coll_name(scenario_def) + self.setup_scenario(scenario_def) + + # SPEC-1245 workaround StaleDbVersion on distinct + for c in self.mongos_clients: + c[database_name][collection_name].distinct("x") + + # Create session0 and session1. + sessions = {} + session_ids = {} + for i in range(2): + # Don't attempt to create sessions if they are not supported by + # the running server version. + if not client_context.sessions_enabled: + break + session_name = 'session%d' % i + opts = camel_to_snake_args(test['sessionOptions'][session_name]) + if 'default_transaction_options' in opts: + txn_opts = self.parse_options( + opts['default_transaction_options']) + txn_opts = client_session.TransactionOptions(**txn_opts) + opts['default_transaction_options'] = txn_opts + + s = client.start_session(**dict(opts)) + + sessions[session_name] = s + # Store lsid so we can access it after end_session, in check_events. + session_ids[session_name] = s.session_id + + self.addCleanup(end_sessions, sessions) + + if 'failPoint' in test: + fp = test['failPoint'] + self.set_fail_point(fp) + self.addCleanup(self.set_fail_point, { + 'configureFailPoint': fp['configureFailPoint'], 'mode': 'off'}) + + listener.results.clear() + + collection = client[database_name][collection_name] + self.run_test_ops(sessions, collection, test) + + end_sessions(sessions) + + self.check_events(test, listener, session_ids) + + # Disable fail points. + if 'failPoint' in test: + fp = test['failPoint'] + self.set_fail_point({ + 'configureFailPoint': fp['configureFailPoint'], 'mode': 'off'}) + + # Assert final state is expected. + outcome = test['outcome'] + expected_c = outcome.get('collection') + if expected_c is not None: + outcome_coll_name = self.get_outcome_coll_name( + outcome, collection) + + # Read from the primary with local read concern to ensure causal + # consistency. + outcome_coll = client_context.client[ + collection.database.name].get_collection( + outcome_coll_name, + read_preference=ReadPreference.PRIMARY, + read_concern=ReadConcern('local')) + actual_data = list(outcome_coll.find(sort=[('_id', 1)])) + + # The expected data needs to be the left hand side here otherwise + # CompareType(Binary) doesn't work. + self.assertEqual(wrap_types(expected_c['data']), actual_data) + +def expect_any_error(op): + if isinstance(op, dict): + return op.get('error') + + return False + + +def expect_error_message(expected_result): + if isinstance(expected_result, dict): + return isinstance(expected_result['errorContains'], text_type) + + return False + + +def expect_error_code(expected_result): + if isinstance(expected_result, dict): + return expected_result['errorCodeName'] + + return False + + +def expect_error_labels_contain(expected_result): + if isinstance(expected_result, dict): + return expected_result['errorLabelsContain'] + + return False + + +def expect_error_labels_omit(expected_result): + if isinstance(expected_result, dict): + return expected_result['errorLabelsOmit'] + + return False + + +def expect_error(op): + expected_result = op.get('result') + return (expect_any_error(op) or + expect_error_message(expected_result) + or expect_error_code(expected_result) + or expect_error_labels_contain(expected_result) + or expect_error_labels_omit(expected_result)) + + +def end_sessions(sessions): + for s in sessions.values(): + # Aborts the transaction if it's open. + s.end_session() + + +OPTS = CodecOptions(document_class=dict, uuid_representation=STANDARD) + + +def decode_raw(val): + """Decode RawBSONDocuments in the given container.""" + if isinstance(val, (list, abc.Mapping)): + return decode(encode({'v': val}, codec_options=OPTS), OPTS)['v'] + return val + + +TYPES = { + 'binData': Binary, + 'long': Int64, +} + + +def wrap_types(val): + """Support $$type assertion in command results.""" + if isinstance(val, list): + return [wrap_types(v) for v in val] + if isinstance(val, abc.Mapping): + typ = val.get('$$type') + if typ: + return CompareType(TYPES[typ]) + d = {} + for key in val: + d[key] = wrap_types(val[key]) + return d + return val diff --git a/test/version.py b/test/version.py new file mode 100644 index 0000000..3348060 --- /dev/null +++ b/test/version.py @@ -0,0 +1,88 @@ +# Copyright 2009-2015 MongoDB, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Some tools for running tests based on MongoDB server version.""" + + +class Version(tuple): + + def __new__(cls, *version): + padded_version = cls._padded(version, 4) + return super(Version, cls).__new__(cls, tuple(padded_version)) + + @classmethod + def _padded(cls, iter, length, padding=0): + l = list(iter) + if len(l) < length: + for _ in range(length - len(l)): + l.append(padding) + return l + + @classmethod + def from_string(cls, version_string): + mod = 0 + bump_patch_level = False + if version_string.endswith("+"): + version_string = version_string[0:-1] + mod = 1 + elif version_string.endswith("-pre-"): + version_string = version_string[0:-5] + mod = -1 + elif version_string.endswith("-"): + version_string = version_string[0:-1] + mod = -1 + # Deal with '-rcX' substrings + if '-rc' in version_string: + version_string = version_string[0:version_string.find('-rc')] + mod = -1 + # Deal with git describe generated substrings + elif '-' in version_string: + version_string = version_string[0:version_string.find('-')] + mod = -1 + bump_patch_level = True + + + version = [int(part) for part in version_string.split(".")] + version = cls._padded(version, 3) + # Make from_string and from_version_array agree. For example: + # MongoDB Enterprise > db.runCommand('buildInfo').versionArray + # [ 3, 2, 1, -100 ] + # MongoDB Enterprise > db.runCommand('buildInfo').version + # 3.2.0-97-g1ef94fe + if bump_patch_level: + version[-1] += 1 + version.append(mod) + + return Version(*version) + + @classmethod + def from_version_array(cls, version_array): + version = list(version_array) + if version[-1] < 0: + version[-1] = -1 + version = cls._padded(version, 3) + return Version(*version) + + @classmethod + def from_client(cls, client): + info = client.server_info() + if 'versionArray' in info: + return cls.from_version_array(info['versionArray']) + return cls.from_string(info['version']) + + def at_least(self, *other_version): + return self >= Version(*other_version) + + def __str__(self): + return ".".join(map(str, self)) From a5975928db4836df663a1b5bd3184aa95b9883f5 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Mon, 20 Jul 2020 16:03:09 -0400 Subject: [PATCH 02/26] changes to make it work with pymongoexplain --- test/utils.py | 2 ++ test/utils_spec_runner.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/test/utils.py b/test/utils.py index 4e213d5..d077c91 100644 --- a/test/utils.py +++ b/test/utils.py @@ -48,6 +48,8 @@ db_user, db_pwd) +_SENSITIVE_COMMANDS.add("explain") + if sys.version_info[0] < 3: # Python 2.7, use our backport. from test.barrier import Barrier diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index 68faa8c..89f4fbf 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -1,4 +1,4 @@ -# Copyright 2019-present MongoDB, Inc. +# Copyright 2020-present MongoDB, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,6 +17,20 @@ import copy import sys +from pymongoexplain.explainable_collection import ExplainCollection, Document +from pymongo import monitoring + +class CommandLogger(monitoring.CommandListener): + def __init__(self): + self.cmd_payload = {} + def started(self, event): + self.cmd_payload = event.command + + def succeeded(self, event): + pass + + def failed(self, event): + pass from bson import decode, encode from bson.binary import Binary, STANDARD @@ -248,6 +262,12 @@ def parse_options(opts): return dict(opts) + def _compare_command_dicts(self, ours, theirs): + print(ours) + print(theirs) + for key in ours.keys(): + self.assertEqual(ours[key], theirs[key]) + def run_operation(self, sessions, collection, operation): original_collection = collection name = camel_to_snake(operation['name']) @@ -287,6 +307,9 @@ def run_operation(self, sessions, collection, operation): self.parse_options(arguments) cmd = getattr(obj, name) + wrapped_collection = ExplainCollection(obj) + explain_cmd = getattr(wrapped_collection, name) + for arg_name in list(arguments): c2s = camel_to_snake(arg_name) @@ -344,7 +367,10 @@ def run_operation(self, sessions, collection, operation): arguments[c2s] = arguments.pop(arg_name) result = cmd(**dict(arguments)) - + cmd_payload = self.command_logger.cmd_payload + explain_result = explain_cmd(**dict(arguments)) + self._compare_command_dicts(wrapped_collection.last_cmd_payload, + cmd_payload) if name == "aggregate": if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: # Read from the primary to ensure causal consistency. @@ -518,6 +544,7 @@ def setup_scenario(self, scenario_def): def run_scenario(self, scenario_def, test): self.maybe_skip_scenario(test) listener = OvertCommandListener() + self.command_logger = CommandLogger() # Create a new client, to avoid interference from pooled sessions. client_options = self.parse_client_options(test['clientOptions']) # MMAPv1 does not support retryable writes. @@ -527,9 +554,11 @@ def run_scenario(self, scenario_def, test): use_multi_mongos = test['useMultipleMongoses'] if client_context.is_mongos and use_multi_mongos: client = rs_client(client_context.mongos_seeds(), - event_listeners=[listener], **client_options) + event_listeners=[listener, self.command_logger], + **client_options) else: - client = rs_client(event_listeners=[listener], **client_options) + client = rs_client(event_listeners=[listener, self.command_logger], + **client_options) self.listener = listener # Close the client explicitly to avoid having too many threads open. self.addCleanup(client.close) From 59525cd22cb9738d433e2f2bef25f78f90d8e344 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 21 Jul 2020 15:55:36 -0400 Subject: [PATCH 03/26] got all of the CRUD tests to pass --- pymongoexplain/commands.py | 38 +++++++++++++++-- pymongoexplain/explainable_collection.py | 53 ++++++++++++++---------- test/utils_spec_runner.py | 14 ++++--- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 25e2ea1..1b080df 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -39,6 +39,8 @@ def command_name(self): def get_SON(self): cmd = SON([(self.command_name, self.collection)]) cmd.update(self.command_document) + if self.command_document == {}: + return {} return cmd @@ -51,8 +53,13 @@ def __init__(self, collection: Collection, filter, update, value = kwargs[key] if key == "bypass_document_validation": return_document[key] = value + elif key == "hint": + return_document["updates"][0]["hint"] = value if \ + isinstance(value, str) else SON(value) else: return_document["updates"][0][key] = value + if return_document["updates"][0].get("hint", True) == {}: + del return_document["updates"][0]["hint"] self.command_document = convert_to_camelcase(return_document) @property @@ -81,7 +88,12 @@ def __init__(self, collection: Collection, pipeline, session, super().__init__(collection.name) self.command_document = {"pipeline": pipeline, "cursor": cursor_options} for key, value in kwargs.items(): - self.command_document[key] = value + if key == "batchSize": + if value == 0: + continue + self.command_document["cursor"]["batchSize"] = value + else: + self.command_document[key] = value self.command_document = convert_to_camelcase( self.command_document, exclude_keys=exclude_keys) @@ -110,18 +122,30 @@ def __init__(self, collection: Collection, super().__init__(collection.name) for key, value in kwargs.items(): self.command_document[key] = value + if self.command_document["filter"] == {}: + self.command_document = {} self.command_document = convert_to_camelcase(self.command_document) @property def command_name(self): return "find" + class FindAndModifyCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): super().__init__(collection.name) + print(kwargs) for key, value in kwargs.items(): - self.command_document[key] = value + if key == "hint": + self.command_document["hint"] = value if \ + isinstance(value, str) else SON(value) + else: + self.command_document[key] = value + if "replacement" in self.command_document.keys(): + self.command_document["update"] = self.command_document[ + "replacement"] + del self.command_document["replacement"] self.command_document = convert_to_camelcase(self.command_document) @property @@ -133,9 +157,15 @@ class DeleteCommand(BaseCommand): def __init__(self, collection: Collection, filter, limit, collation, kwargs): super().__init__(collection.name) - self.command_document = {"deletes": [SON({"q": filter, "limit": limit})]} + self.command_document = {"deletes": [SON({"q": filter, "limit": + limit, "collation": collation})]} for key, value in kwargs.items(): - self.command_document[key] = value + if key == "hint": + self.command_document["deletes"][0]["hint"] = value if \ + isinstance(value, str) else SON(value) + else: + self.command_document[key] = value + self.command_document = convert_to_camelcase(self.command_document) @property diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index e736fd2..59b2cae 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -16,6 +16,7 @@ from typing import Union, List, Dict import pymongo +from pymongo.collection import Collection from bson.son import SON from .commands import AggregateCommand, FindCommand, CountCommand, \ @@ -23,17 +24,21 @@ Document = Union[dict, SON] + class ExplainCollection(): - def __init__(self, collection): - self.collection = collection + def __init__(self, collection_object): + self.collection_object = collection_object self.last_cmd_payload = None def _explain_command(self, command): command_son = command.get_SON() + if command_son == {}: + self.last_cmd_payload = {} + return {} explain_command = SON([("explain", command_son)]) explain_command["verbosity"] = "queryPlanner" self.last_cmd_payload = command_son - return self.collection.database.command(explain_command) + return self.collection_object.database.command(explain_command) def update_one(self, filter, update, upsert=False, bypass_document_validation=False, @@ -44,7 +49,7 @@ def update_one(self, filter, update, upsert=False, kwargs["multi"] = False if bypass_document_validation == False: del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection, filter, update, kwargs) + command = UpdateCommand(self.collection_object, filter, update, kwargs) return self._explain_command(command) def update_many(self, filter: Document, update: Document, upsert=False, @@ -54,28 +59,28 @@ def update_many(self, filter: Document, update: Document, upsert=False, kwargs["multi"] = True if bypass_document_validation == False: del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection, filter, update, kwargs) + command = UpdateCommand(self.collection_object, filter, update, kwargs) return self._explain_command(command) def distinct(self, key: str, filter: Document=None, session=None, **kwargs): - command = DistinctCommand(self.collection, key, filter, session, kwargs) + command = DistinctCommand(self.collection_object, key, filter, session, kwargs) return self._explain_command(command) def aggregate(self, pipeline: List[Document], session=None, **kwargs): - command = AggregateCommand(self.collection, pipeline, session, - {},kwargs) + command = AggregateCommand(self.collection_object, pipeline, session, + {}, kwargs) return self._explain_command(command) def estimated_document_count(self, **kwargs): - command = CountCommand(self.collection, None, kwargs) + command = CountCommand(self.collection_object, None, kwargs) return self._explain_command(command) def count_documents(self, filter: Document, session=None, **kwargs): - command = AggregateCommand(self.collection, [{'$match': filter}, + command = AggregateCommand(self.collection_object, [{'$match': filter}, {'$group': {'n': {'$sum': 1}, '_id': 1}}], session, {}, kwargs, exclude_keys=filter.keys()) @@ -84,7 +89,7 @@ def count_documents(self, filter: Document, session=None, def delete_one(self, filter: Document, collation=None, session=None, **kwargs): limit = 1 - command = DeleteCommand(self.collection, filter, limit, collation, + command = DeleteCommand(self.collection_object, filter, limit, collation, kwargs) return self._explain_command(command) @@ -94,7 +99,7 @@ def delete_many(self, filter: Document, collation=None, bool]]): limit = 0 kwargs["session"] = session - command = DeleteCommand(self.collection, filter, limit, collation, + command = DeleteCommand(self.collection_object, filter, limit, collation, kwargs) return self._explain_command(command) @@ -113,7 +118,7 @@ def watch(self, pipeline: Document = None, full_document: Document = None, else: pipeline = [{"$changeStream": change_stream_options}] - command = AggregateCommand(self.collection, pipeline, + command = AggregateCommand(self.collection_object, pipeline, session, {"batch_size":batch_size}, {"collation":collation, "max_await_time_ms": max_await_time_ms}) @@ -123,8 +128,9 @@ def find(self, filter: Document = None, **kwargs: Dict[str, Union[int, str,Document, bool]]): kwargs.update(locals()) del kwargs["self"], kwargs["kwargs"] - command = FindCommand(self.collection, + command = FindCommand(self.collection_object, kwargs) + return self._explain_command(command) def find_one(self, filter: Document = None, **kwargs: Dict[str, @@ -133,7 +139,7 @@ def find_one(self, filter: Document = None, **kwargs: Dict[str, kwargs.update(locals()) del kwargs["self"], kwargs["kwargs"] kwargs["limit"] = 1 - command = FindCommand(self.collection, kwargs) + command = FindCommand(self.collection_object, kwargs) return self._explain_command(command) def find_one_and_delete(self, filter: Document, projection: list = None, @@ -145,11 +151,12 @@ def find_one_and_delete(self, filter: Document, projection: list = None, kwargs["remove"] = True kwargs["session"] = session - command = FindAndModifyCommand(self.collection, + command = FindAndModifyCommand(self.collection_object, kwargs) return self._explain_command(command) - def find_one_and_replace(self, filter: Document, replacement: Document, + def find_one_and_replace(self, filter: Document, update: + Document={}, projection: list = None, sort=None, return_document=pymongo.ReturnDocument.BEFORE, session=None, **kwargs): @@ -157,13 +164,13 @@ def find_one_and_replace(self, filter: Document, replacement: Document, kwargs["fields"] = projection kwargs["sort"] = sort kwargs["new"] = False - kwargs["update"] = replacement + kwargs["update"] = update kwargs["session"] = session - command = FindAndModifyCommand(self.collection, + command = FindAndModifyCommand(self.collection_object, kwargs) return self._explain_command(command) - def find_one_and_update(self, filter: Document, replacement: Document, + def find_one_and_update(self, filter: Document, update: Document, projection: list = None, sort=None, return_document=pymongo.ReturnDocument.BEFORE, session=None, **kwargs): @@ -171,10 +178,10 @@ def find_one_and_update(self, filter: Document, replacement: Document, kwargs["fields"] = projection kwargs["sort"] = sort kwargs["upsert"] = False - kwargs["update"] = replacement + kwargs["update"] = update kwargs["session"] = session - command = FindAndModifyCommand(self.collection, + command = FindAndModifyCommand(self.collection_object, kwargs) return self._explain_command(command) @@ -188,7 +195,7 @@ def replace_one(self, filter: Document, replacement: Document, if not bypass_document_validation: del kwargs["bypass_document_validation"] update = replacement - command = UpdateCommand(self.collection, filter, update, kwargs) + command = UpdateCommand(self.collection_object, filter, update, kwargs) return self._explain_command(command) diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index 89f4fbf..cc36ade 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -307,9 +307,9 @@ def run_operation(self, sessions, collection, operation): self.parse_options(arguments) cmd = getattr(obj, name) - wrapped_collection = ExplainCollection(obj) - explain_cmd = getattr(wrapped_collection, name) - + if name != "bulk_write" and object_name == "collection": + wrapped_collection = ExplainCollection(obj) + explain_cmd = getattr(wrapped_collection, name) for arg_name in list(arguments): c2s = camel_to_snake(arg_name) @@ -368,9 +368,11 @@ def run_operation(self, sessions, collection, operation): result = cmd(**dict(arguments)) cmd_payload = self.command_logger.cmd_payload - explain_result = explain_cmd(**dict(arguments)) - self._compare_command_dicts(wrapped_collection.last_cmd_payload, - cmd_payload) + + if name != "bulk_write" and object_name == "collection": + explain_result = explain_cmd(**dict(arguments)) + self._compare_command_dicts(wrapped_collection.last_cmd_payload, + cmd_payload) if name == "aggregate": if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: # Read from the primary to ensure causal consistency. From ef6c5a189abfc2657fcc793ed1a9c76b9803b1b5 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 21 Jul 2020 16:27:02 -0400 Subject: [PATCH 04/26] make travis use release candidate --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d505aab..bf969b9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python services: - mongodb +install: python -m pip install https://github.com/mongodb/mongo-python-driver/archive/3.11.0rc0.tar.gz + python: - 3.5 - 3.6 From 803e27a9ae37c0a49b71fcfcce935f732486b871 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 21 Jul 2020 16:31:21 -0400 Subject: [PATCH 05/26] Hopefully fixes travis --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 030f831..02aed4e 100644 --- a/setup.py +++ b/setup.py @@ -26,8 +26,8 @@ 'console_scripts': [ 'pymongoexplain=pymongoexplain.cli_explain:cli_explain'], }, - tests_require=["pymongo==3.10.1"], - install_requires=['pymongo==3.10.1'], + tests_require=["pymongo==3.11.0rc0"], + install_requires=['pymongo==3.11.0rc0'], python_requires='>=3.5', license="Apache License, Version 2.0", classifiers=[ From b8ffa0f0948f457246335cc2c66e617fc2ce9a87 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 21 Jul 2020 16:39:46 -0400 Subject: [PATCH 06/26] Shane fixes --- pymongoexplain/commands.py | 2 +- pymongoexplain/explainable_collection.py | 42 ++++++++++++------------ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 1b080df..e786932 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -53,7 +53,7 @@ def __init__(self, collection: Collection, filter, update, value = kwargs[key] if key == "bypass_document_validation": return_document[key] = value - elif key == "hint": + elif key == "hint" and value != {}: return_document["updates"][0]["hint"] = value if \ isinstance(value, str) else SON(value) else: diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index 59b2cae..c5d84b4 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -26,8 +26,8 @@ class ExplainCollection(): - def __init__(self, collection_object): - self.collection_object = collection_object + def __init__(self, collection): + self.collection = collection self.last_cmd_payload = None def _explain_command(self, command): @@ -38,7 +38,7 @@ def _explain_command(self, command): explain_command = SON([("explain", command_son)]) explain_command["verbosity"] = "queryPlanner" self.last_cmd_payload = command_son - return self.collection_object.database.command(explain_command) + return self.collection.database.command(explain_command) def update_one(self, filter, update, upsert=False, bypass_document_validation=False, @@ -49,7 +49,7 @@ def update_one(self, filter, update, upsert=False, kwargs["multi"] = False if bypass_document_validation == False: del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection_object, filter, update, kwargs) + command = UpdateCommand(self.collection, filter, update, kwargs) return self._explain_command(command) def update_many(self, filter: Document, update: Document, upsert=False, @@ -59,29 +59,29 @@ def update_many(self, filter: Document, update: Document, upsert=False, kwargs["multi"] = True if bypass_document_validation == False: del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection_object, filter, update, kwargs) + command = UpdateCommand(self.collection, filter, update, kwargs) return self._explain_command(command) def distinct(self, key: str, filter: Document=None, session=None, **kwargs): - command = DistinctCommand(self.collection_object, key, filter, session, kwargs) + command = DistinctCommand(self.collection, key, filter, session, kwargs) return self._explain_command(command) def aggregate(self, pipeline: List[Document], session=None, **kwargs): - command = AggregateCommand(self.collection_object, pipeline, session, + command = AggregateCommand(self.collection, pipeline, session, {}, kwargs) return self._explain_command(command) def estimated_document_count(self, **kwargs): - command = CountCommand(self.collection_object, None, kwargs) + command = CountCommand(self.collection, None, kwargs) return self._explain_command(command) def count_documents(self, filter: Document, session=None, **kwargs): - command = AggregateCommand(self.collection_object, [{'$match': filter}, - {'$group': {'n': {'$sum': 1}, '_id': 1}}], + command = AggregateCommand(self.collection, [{'$match': filter}, + {'$group': {'n': {'$sum': 1}, '_id': 1}}], session, {}, kwargs, exclude_keys=filter.keys()) return self._explain_command(command) @@ -89,7 +89,7 @@ def count_documents(self, filter: Document, session=None, def delete_one(self, filter: Document, collation=None, session=None, **kwargs): limit = 1 - command = DeleteCommand(self.collection_object, filter, limit, collation, + command = DeleteCommand(self.collection, filter, limit, collation, kwargs) return self._explain_command(command) @@ -99,8 +99,8 @@ def delete_many(self, filter: Document, collation=None, bool]]): limit = 0 kwargs["session"] = session - command = DeleteCommand(self.collection_object, filter, limit, collation, - kwargs) + command = DeleteCommand(self.collection, filter, limit, collation, + kwargs) return self._explain_command(command) def watch(self, pipeline: Document = None, full_document: Document = None, @@ -118,7 +118,7 @@ def watch(self, pipeline: Document = None, full_document: Document = None, else: pipeline = [{"$changeStream": change_stream_options}] - command = AggregateCommand(self.collection_object, pipeline, + command = AggregateCommand(self.collection, pipeline, session, {"batch_size":batch_size}, {"collation":collation, "max_await_time_ms": max_await_time_ms}) @@ -128,8 +128,8 @@ def find(self, filter: Document = None, **kwargs: Dict[str, Union[int, str,Document, bool]]): kwargs.update(locals()) del kwargs["self"], kwargs["kwargs"] - command = FindCommand(self.collection_object, - kwargs) + command = FindCommand(self.collection, + kwargs) return self._explain_command(command) @@ -139,7 +139,7 @@ def find_one(self, filter: Document = None, **kwargs: Dict[str, kwargs.update(locals()) del kwargs["self"], kwargs["kwargs"] kwargs["limit"] = 1 - command = FindCommand(self.collection_object, kwargs) + command = FindCommand(self.collection, kwargs) return self._explain_command(command) def find_one_and_delete(self, filter: Document, projection: list = None, @@ -151,7 +151,7 @@ def find_one_and_delete(self, filter: Document, projection: list = None, kwargs["remove"] = True kwargs["session"] = session - command = FindAndModifyCommand(self.collection_object, + command = FindAndModifyCommand(self.collection, kwargs) return self._explain_command(command) @@ -166,7 +166,7 @@ def find_one_and_replace(self, filter: Document, update: kwargs["new"] = False kwargs["update"] = update kwargs["session"] = session - command = FindAndModifyCommand(self.collection_object, + command = FindAndModifyCommand(self.collection, kwargs) return self._explain_command(command) @@ -181,7 +181,7 @@ def find_one_and_update(self, filter: Document, update: Document, kwargs["update"] = update kwargs["session"] = session - command = FindAndModifyCommand(self.collection_object, + command = FindAndModifyCommand(self.collection, kwargs) return self._explain_command(command) @@ -195,7 +195,7 @@ def replace_one(self, filter: Document, replacement: Document, if not bypass_document_validation: del kwargs["bypass_document_validation"] update = replacement - command = UpdateCommand(self.collection_object, filter, update, kwargs) + command = UpdateCommand(self.collection, filter, update, kwargs) return self._explain_command(command) From 16fe2a541228176eb85428c76069ea099337918a Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 21 Jul 2020 16:47:14 -0400 Subject: [PATCH 07/26] fix travis --- test/test_collection.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/test_collection.py b/test/test_collection.py index c92b0c8..2d7c42d 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -177,8 +177,7 @@ def test_replace_one(self): def test_estimated_document_count(self): self.collection.estimated_document_count() last_logger_payload = self.logger.cmd_payload - res = self.explain.estimated_document_count() - self.assertIn("queryPlanner", res) + self.explain.estimated_document_count() last_cmd_payload = self.explain.last_cmd_payload self._compare_command_dicts(last_cmd_payload, last_logger_payload) From c8f973b9255a25412bd665b455b3368f1a3359e2 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 22 Jul 2020 16:54:43 -0400 Subject: [PATCH 08/26] fixed wonky logic in constructing commands --- pymongoexplain/commands.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index e786932..233fc89 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -20,6 +20,7 @@ from bson.son import SON from pymongo.collection import Collection +from pymongo.helpers import _index_document from .utils import convert_to_camelcase @@ -48,18 +49,18 @@ class UpdateCommand(BaseCommand): def __init__(self, collection: Collection, filter, update, kwargs): super().__init__(collection.name) - return_document = {"updates":[{"q": filter, "u": update}]} - for key in kwargs: - value = kwargs[key] + return_document = { + "updates":[{"q": filter, "u": update}] + } + for key, value in kwargs.items(): if key == "bypass_document_validation": return_document[key] = value - elif key == "hint" and value != {}: - return_document["updates"][0]["hint"] = value if \ - isinstance(value, str) else SON(value) + elif key == "hint": + if value is not {} and value is not None: + return_document["updates"][0]["hint"] = value if \ + isinstance(value, str) else _index_document(value) else: return_document["updates"][0][key] = value - if return_document["updates"][0].get("hint", True) == {}: - del return_document["updates"][0]["hint"] self.command_document = convert_to_camelcase(return_document) @property @@ -135,17 +136,16 @@ class FindAndModifyCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): super().__init__(collection.name) - print(kwargs) for key, value in kwargs.items(): + if key == "update" and kwargs.get("replacement", None) is not None: + continue if key == "hint": self.command_document["hint"] = value if \ - isinstance(value, str) else SON(value) + isinstance(value, str) else _index_document(value) + elif key == "replacement": + self.command_document["update"] = value else: self.command_document[key] = value - if "replacement" in self.command_document.keys(): - self.command_document["update"] = self.command_document[ - "replacement"] - del self.command_document["replacement"] self.command_document = convert_to_camelcase(self.command_document) @property @@ -162,7 +162,7 @@ def __init__(self, collection: Collection, filter, for key, value in kwargs.items(): if key == "hint": self.command_document["deletes"][0]["hint"] = value if \ - isinstance(value, str) else SON(value) + isinstance(value, str) else _index_document(value) else: self.command_document[key] = value From 3ebe9b027d6b66c8bcb17aa5025dcfd0f3c79940 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 22 Jul 2020 17:14:41 -0400 Subject: [PATCH 09/26] added test for find --- test/test_collection.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/test_collection.py b/test/test_collection.py index 2d7c42d..c8e7de1 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -133,6 +133,13 @@ def test_find(self): last_cmd_payload = self.explain.last_cmd_payload self._compare_command_dicts(last_cmd_payload, last_logger_payload) + for _ in self.collection.find({}, limit=10): + pass + last_logger_payload = self.logger.cmd_payload + res = self.explain.find({}, limit=10) + last_cmd_payload = self.explain.last_cmd_payload + self._compare_command_dicts(last_cmd_payload, last_logger_payload) + def test_find_one(self): self.collection.find_one() last_logger_payload = self.logger.cmd_payload From e6aa28740b17a886498e64d9bb26efdca570e4a5 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 22 Jul 2020 17:17:16 -0400 Subject: [PATCH 10/26] fixed find test --- pymongoexplain/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 233fc89..d26b394 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -121,10 +121,10 @@ class FindCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): super().__init__(collection.name) + if kwargs["filter"] == {}: + self.command_document = {} for key, value in kwargs.items(): self.command_document[key] = value - if self.command_document["filter"] == {}: - self.command_document = {} self.command_document = convert_to_camelcase(self.command_document) @property From 6ed0717b5e657fb74013bf524227715c14361d42 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 22 Jul 2020 18:29:56 -0400 Subject: [PATCH 11/26] Fixed bugs --- pymongoexplain/commands.py | 4 +--- pymongoexplain/explainable_collection.py | 3 --- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index d26b394..56107f4 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -56,7 +56,7 @@ def __init__(self, collection: Collection, filter, update, if key == "bypass_document_validation": return_document[key] = value elif key == "hint": - if value is not {} and value is not None: + if value is not None: return_document["updates"][0]["hint"] = value if \ isinstance(value, str) else _index_document(value) else: @@ -121,8 +121,6 @@ class FindCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): super().__init__(collection.name) - if kwargs["filter"] == {}: - self.command_document = {} for key, value in kwargs.items(): self.command_document[key] = value self.command_document = convert_to_camelcase(self.command_document) diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index c5d84b4..bdd82c5 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -32,9 +32,6 @@ def __init__(self, collection): def _explain_command(self, command): command_son = command.get_SON() - if command_son == {}: - self.last_cmd_payload = {} - return {} explain_command = SON([("explain", command_son)]) explain_command["verbosity"] = "queryPlanner" self.last_cmd_payload = command_son From 8cc3923244ac67f641488f911cfc94f35cb6e9c8 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 12:28:25 -0400 Subject: [PATCH 12/26] moved collation to base command --- pymongoexplain/commands.py | 90 +++++++++++++++--------- pymongoexplain/explainable_collection.py | 36 ++++------ test/utils_spec_runner.py | 11 +-- 3 files changed, 78 insertions(+), 59 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 56107f4..fbcb20f 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -21,6 +21,7 @@ from bson.son import SON from pymongo.collection import Collection from pymongo.helpers import _index_document +from pymongo.collation import validate_collation_or_none from .utils import convert_to_camelcase @@ -28,8 +29,11 @@ class BaseCommand(): - def __init__(self, collection): + def __init__(self, collection, collation): self.command_document = {} + collation = validate_collation_or_none(collation) + if collation is not None: + self.command_document["collation"] = collation self.collection = collection @property @@ -40,28 +44,45 @@ def command_name(self): def get_SON(self): cmd = SON([(self.command_name, self.collection)]) cmd.update(self.command_document) - if self.command_document == {}: - return {} return cmd class UpdateCommand(BaseCommand): def __init__(self, collection: Collection, filter, update, - kwargs): - super().__init__(collection.name) - return_document = { + upsert=None, multi=None, collation=None, array_filters=None, + hint=None, ordered=None, write_concern=None, + bypass_document_validation=None, comment=None): + super().__init__(collection.name, collation) + self.command_document.update({ "updates":[{"q": filter, "u": update}] - } - for key, value in kwargs.items(): - if key == "bypass_document_validation": - return_document[key] = value - elif key == "hint": - if value is not None: - return_document["updates"][0]["hint"] = value if \ - isinstance(value, str) else _index_document(value) - else: - return_document["updates"][0][key] = value - self.command_document = convert_to_camelcase(return_document) + }) + if upsert is not None: + self.command_document["updates"][0]["upsert"] = upsert + + if multi is not None: + self.command_document["updates"][0]["multi"] = multi + + if array_filters is not None: + self.command_document["updates"][0]["array_filters"] = array_filters + + if hint is not None: + self.command_document["updates"][0]["hint"] = hint if \ + isinstance(hint, str) else _index_document(hint) + + if ordered is not None: + self.command_document["ordered"] = ordered + + if write_concern is not None: + self.command_document["write_concern"] = write_concern + + if bypass_document_validation is not None and \ + bypass_document_validation is not False: + self.command_document["bypass_document_validation"] = bypass_document_validation + + if comment is not None: + self.command_document["comment"] = comment + + self.command_document = convert_to_camelcase(self.command_document) @property def command_name(self): @@ -71,10 +92,11 @@ def command_name(self): class DistinctCommand(BaseCommand): def __init__(self, collection: Collection, key, filter, session, kwargs): - super().__init__(collection.name) - self.command_document = {"key": key, "query": filter} - for key, value in kwargs.items(): - self.command_document[key] = value + super().__init__(collection.name, kwargs.get("collation", None)) + self.command_document.update({"key": key, "query": filter}) + if kwargs.get("read_concern", None) is not None: + self.command_document["read_concern"] = kwargs["read_concern"] + self.command_document = convert_to_camelcase(self.command_document) @property @@ -85,9 +107,12 @@ def command_name(self): class AggregateCommand(BaseCommand): def __init__(self, collection: Collection, pipeline, session, cursor_options, - kwargs, exclude_keys = []): - super().__init__(collection.name) - self.command_document = {"pipeline": pipeline, "cursor": cursor_options} + kwargs): + + super().__init__(collection.name, kwargs.get("collation", None)) + self.command_document.update({"pipeline": pipeline, "cursor": + cursor_options}) + for key, value in kwargs.items(): if key == "batchSize": if value == 0: @@ -97,17 +122,18 @@ def __init__(self, collection: Collection, pipeline, session, self.command_document[key] = value self.command_document = convert_to_camelcase( - self.command_document, exclude_keys=exclude_keys) + self.command_document) @property def command_name(self): return "aggregate" + class CountCommand(BaseCommand): def __init__(self, collection: Collection, filter, kwargs): - super().__init__(collection.name) - self.command_document = {"query": filter} + super().__init__(collection.name, kwargs.get("collation", None)) + self.command_document.update({"query": filter}) for key, value in kwargs.items(): self.command_document[key] = value self.command_document = convert_to_camelcase(self.command_document) @@ -120,7 +146,7 @@ def command_name(self): class FindCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): - super().__init__(collection.name) + super().__init__(collection.name, kwargs.get("collation", None)) for key, value in kwargs.items(): self.command_document[key] = value self.command_document = convert_to_camelcase(self.command_document) @@ -133,7 +159,7 @@ def command_name(self): class FindAndModifyCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): - super().__init__(collection.name) + super().__init__(collection.name, kwargs.get("collation", None)) for key, value in kwargs.items(): if key == "update" and kwargs.get("replacement", None) is not None: continue @@ -154,9 +180,9 @@ def command_name(self): class DeleteCommand(BaseCommand): def __init__(self, collection: Collection, filter, limit, collation, kwargs): - super().__init__(collection.name) - self.command_document = {"deletes": [SON({"q": filter, "limit": - limit, "collation": collation})]} + super().__init__(collection.name, kwargs.get("collation", None)) + self.command_document.update({"deletes": [SON({"q": filter, "limit": + limit})]}) for key, value in kwargs.items(): if key == "hint": self.command_document["deletes"][0]["hint"] = value if \ diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index bdd82c5..52b1a2e 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -41,22 +41,19 @@ def update_one(self, filter, update, upsert=False, bypass_document_validation=False, collation=None, array_filters=None, hint=None, session=None, **kwargs): - kwargs.update(locals()) - del kwargs["self"], kwargs["kwargs"], kwargs["filter"], kwargs["update"] - kwargs["multi"] = False - if bypass_document_validation == False: - del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection, filter, update, kwargs) + command = UpdateCommand(self.collection, filter, update, + bypass_document_validation= + bypass_document_validation, + array_filters=array_filters, + collation=collation, hint=hint, + upsert=upsert, multi=False) return self._explain_command(command) def update_many(self, filter: Document, update: Document, upsert=False, - array_filters=None, bypass_document_validation=False, collation=None, session=None, **kwargs): - kwargs.update(locals()) - del kwargs["self"], kwargs["kwargs"], kwargs["filter"], kwargs["update"] - kwargs["multi"] = True - if bypass_document_validation == False: - del kwargs["bypass_document_validation"] - command = UpdateCommand(self.collection, filter, update, kwargs) + array_filters=None, bypass_document_validation=False, + collation=None, hint=None, session=None, **kwargs): + command = UpdateCommand(self.collection, filter, update, multi=True, + bypass_document_validation=bypass_document_validation, upsert=upsert, collation=collation, array_filters=array_filters, hint=hint) return self._explain_command(command) def distinct(self, key: str, filter: Document=None, session=None, **kwargs): @@ -184,15 +181,10 @@ def find_one_and_update(self, filter: Document, update: Document, def replace_one(self, filter: Document, replacement: Document, upsert=False, bypass_document_validation=False, - collation=None, session=None, **kwargs): - kwargs.update(locals()) - del kwargs["self"], kwargs["kwargs"], kwargs["filter"], kwargs[ - "replacement"] - kwargs["multi"] = False - if not bypass_document_validation: - del kwargs["bypass_document_validation"] - update = replacement - command = UpdateCommand(self.collection, filter, update, kwargs) + collation=None, hint=None, session=None, **kwargs): + command = UpdateCommand(self.collection, filter, update=replacement, + bypass_document_validation=bypass_document_validation, + hint=hint, collation=collation, multi=False, upsert=upsert) return self._explain_command(command) diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index cc36ade..cc2b022 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -306,6 +306,7 @@ def run_operation(self, sessions, collection, operation): arguments.update(arguments.pop("options", {})) self.parse_options(arguments) + cmd = getattr(obj, name) if name != "bulk_write" and object_name == "collection": wrapped_collection = ExplainCollection(obj) @@ -367,12 +368,7 @@ def run_operation(self, sessions, collection, operation): arguments[c2s] = arguments.pop(arg_name) result = cmd(**dict(arguments)) - cmd_payload = self.command_logger.cmd_payload - if name != "bulk_write" and object_name == "collection": - explain_result = explain_cmd(**dict(arguments)) - self._compare_command_dicts(wrapped_collection.last_cmd_payload, - cmd_payload) if name == "aggregate": if arguments["pipeline"] and "$out" in arguments["pipeline"][-1]: # Read from the primary to ensure causal consistency. @@ -389,6 +385,11 @@ def run_operation(self, sessions, collection, operation): if isinstance(result, Cursor) or isinstance(result, CommandCursor): return list(result) + cmd_payload = self.command_logger.cmd_payload + if name != "bulk_write" and object_name == "collection": + explain_cmd(**dict(arguments)) + self._compare_command_dicts(wrapped_collection.last_cmd_payload, + cmd_payload) return result def allowable_errors(self, op): From 4f230bb856fc0e07369b988da6cd8e78594f646f Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 12:46:47 -0400 Subject: [PATCH 13/26] accidentally removed exclude_keys --- pymongoexplain/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index fbcb20f..a6a2332 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -131,12 +131,13 @@ def command_name(self): class CountCommand(BaseCommand): def __init__(self, collection: Collection, filter, - kwargs): + kwarg, sxclude_keys=None, ): super().__init__(collection.name, kwargs.get("collation", None)) self.command_document.update({"query": filter}) for key, value in kwargs.items(): self.command_document[key] = value - self.command_document = convert_to_camelcase(self.command_document) + self.command_document = convert_to_camelcase(self.command_document, + exclude_keys=exclude_keys) @property def command_name(self): From e332c4ee2a82e837e831647678940148a5ad2345 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 12:52:23 -0400 Subject: [PATCH 14/26] exclude keys fixes --- pymongoexplain/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index a6a2332..d9f8d3c 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -107,7 +107,7 @@ def command_name(self): class AggregateCommand(BaseCommand): def __init__(self, collection: Collection, pipeline, session, cursor_options, - kwargs): + kwargs, exclude_keys=None): super().__init__(collection.name, kwargs.get("collation", None)) self.command_document.update({"pipeline": pipeline, "cursor": @@ -122,7 +122,7 @@ def __init__(self, collection: Collection, pipeline, session, self.command_document[key] = value self.command_document = convert_to_camelcase( - self.command_document) + self.command_document, exclude_keys=exclude_keys) @property def command_name(self): @@ -130,8 +130,8 @@ def command_name(self): class CountCommand(BaseCommand): - def __init__(self, collection: Collection, filter, - kwarg, sxclude_keys=None, ): + def __init__(self, collection: Collection, filter, kwargs, + sxclude_keys=None): super().__init__(collection.name, kwargs.get("collation", None)) self.command_document.update({"query": filter}) for key, value in kwargs.items(): From dd09b412fbde49ba919973f0750b3b557423c345 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 13:06:20 -0400 Subject: [PATCH 15/26] shane fixes --- pymongoexplain/commands.py | 33 ++++++++++-------------- pymongoexplain/explainable_collection.py | 3 +-- pymongoexplain/utils.py | 6 ----- 3 files changed, 15 insertions(+), 27 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index d9f8d3c..e1ad4fd 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -53,9 +53,7 @@ def __init__(self, collection: Collection, filter, update, hint=None, ordered=None, write_concern=None, bypass_document_validation=None, comment=None): super().__init__(collection.name, collation) - self.command_document.update({ - "updates":[{"q": filter, "u": update}] - }) + self.command_document["updates"] = [{"q": filter, "u": update}] if upsert is not None: self.command_document["updates"][0]["upsert"] = upsert @@ -90,12 +88,10 @@ def command_name(self): class DistinctCommand(BaseCommand): - def __init__(self, collection: Collection, key, filter, session, + def __init__(self, collection: Collection, key, filter, kwargs): - super().__init__(collection.name, kwargs.get("collation", None)) + super().__init__(collection.name, kwargs.pop("collation", None)) self.command_document.update({"key": key, "query": filter}) - if kwargs.get("read_concern", None) is not None: - self.command_document["read_concern"] = kwargs["read_concern"] self.command_document = convert_to_camelcase(self.command_document) @@ -105,11 +101,11 @@ def command_name(self): class AggregateCommand(BaseCommand): - def __init__(self, collection: Collection, pipeline, session, + def __init__(self, collection: Collection, pipeline, cursor_options, - kwargs, exclude_keys=None): + kwargs): - super().__init__(collection.name, kwargs.get("collation", None)) + super().__init__(collection.name, kwargs.pop("collation", None)) self.command_document.update({"pipeline": pipeline, "cursor": cursor_options}) @@ -122,7 +118,7 @@ def __init__(self, collection: Collection, pipeline, session, self.command_document[key] = value self.command_document = convert_to_camelcase( - self.command_document, exclude_keys=exclude_keys) + self.command_document) @property def command_name(self): @@ -132,12 +128,11 @@ def command_name(self): class CountCommand(BaseCommand): def __init__(self, collection: Collection, filter, kwargs, sxclude_keys=None): - super().__init__(collection.name, kwargs.get("collation", None)) + super().__init__(collection.name, kwargs.pop("collation", None)) self.command_document.update({"query": filter}) for key, value in kwargs.items(): self.command_document[key] = value - self.command_document = convert_to_camelcase(self.command_document, - exclude_keys=exclude_keys) + self.command_document = convert_to_camelcase(self.command_document) @property def command_name(self): @@ -147,7 +142,7 @@ def command_name(self): class FindCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): - super().__init__(collection.name, kwargs.get("collation", None)) + super().__init__(collection.name, kwargs.pop("collation", None)) for key, value in kwargs.items(): self.command_document[key] = value self.command_document = convert_to_camelcase(self.command_document) @@ -160,7 +155,7 @@ def command_name(self): class FindAndModifyCommand(BaseCommand): def __init__(self, collection: Collection, kwargs): - super().__init__(collection.name, kwargs.get("collation", None)) + super().__init__(collection.name, kwargs.pop("collation", None)) for key, value in kwargs.items(): if key == "update" and kwargs.get("replacement", None) is not None: continue @@ -181,9 +176,9 @@ def command_name(self): class DeleteCommand(BaseCommand): def __init__(self, collection: Collection, filter, limit, collation, kwargs): - super().__init__(collection.name, kwargs.get("collation", None)) - self.command_document.update({"deletes": [SON({"q": filter, "limit": - limit})]}) + super().__init__(collection.name, kwargs.pop("collation", None)) + self.command_document["deletes"] = [SON({"q": filter, "limit": + limit})] for key, value in kwargs.items(): if key == "hint": self.command_document["deletes"][0]["hint"] = value if \ diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index 52b1a2e..8e5fc8e 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -76,8 +76,7 @@ def count_documents(self, filter: Document, session=None, command = AggregateCommand(self.collection, [{'$match': filter}, {'$group': {'n': {'$sum': 1}, '_id': 1}}], - session, {}, kwargs, - exclude_keys=filter.keys()) + session, {}, kwargs) return self._explain_command(command) def delete_one(self, filter: Document, collation=None, session=None, diff --git a/pymongoexplain/utils.py b/pymongoexplain/utils.py index d956a15..a2c5bd0 100644 --- a/pymongoexplain/utils.py +++ b/pymongoexplain/utils.py @@ -30,12 +30,6 @@ def convert_to_camelcase(d, exclude_keys=[]): if "_" in key and key[0] != "_": new_key = key.split("_")[0] + ''.join( [i.capitalize() for i in key.split("_")[1:]]) - if isinstance(d[key], list): - ret[new_key] = [convert_to_camelcase( - i, exclude_keys=exclude_keys) for i in d[key]] - elif isinstance(d[key], dict): - ret[new_key] = convert_to_camelcase(d[key], - exclude_keys=exclude_keys) else: ret[new_key] = d[key] return ret From 3bd6bdf83ddc7a3c9591e70401036950179cfd4f Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 13:10:46 -0400 Subject: [PATCH 16/26] fixed errant sessions --- pymongoexplain/explainable_collection.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index 8e5fc8e..5d42488 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -57,11 +57,11 @@ def update_many(self, filter: Document, update: Document, upsert=False, return self._explain_command(command) def distinct(self, key: str, filter: Document=None, session=None, **kwargs): - command = DistinctCommand(self.collection, key, filter, session, kwargs) + command = DistinctCommand(self.collection, key, filter, kwargs) return self._explain_command(command) def aggregate(self, pipeline: List[Document], session=None, **kwargs): - command = AggregateCommand(self.collection, pipeline, session, + command = AggregateCommand(self.collection, pipeline, {}, kwargs) return self._explain_command(command) @@ -75,8 +75,8 @@ def count_documents(self, filter: Document, session=None, **kwargs): command = AggregateCommand(self.collection, [{'$match': filter}, - {'$group': {'n': {'$sum': 1}, '_id': 1}}], - session, {}, kwargs) + {'$group': {'n': {'$sum': 1}, '_id': 1}}] + , {}, kwargs) return self._explain_command(command) def delete_one(self, filter: Document, collation=None, session=None, @@ -91,7 +91,6 @@ def delete_many(self, filter: Document, collation=None, Document, bool]]): limit = 0 - kwargs["session"] = session command = DeleteCommand(self.collection, filter, limit, collation, kwargs) return self._explain_command(command) @@ -99,8 +98,7 @@ def delete_many(self, filter: Document, collation=None, def watch(self, pipeline: Document = None, full_document: Document = None, resume_after= None, max_await_time_ms: int = None, batch_size: int = None, - collation=None, start_at_operation_time=None, session: - pymongo.mongo_client.client_session.ClientSession=None, + collation=None, start_at_operation_time=None, session=None, start_after=None): change_stream_options = {"start_after":start_after, "resume_after":resume_after, @@ -112,7 +110,7 @@ def watch(self, pipeline: Document = None, full_document: Document = None, pipeline = [{"$changeStream": change_stream_options}] command = AggregateCommand(self.collection, pipeline, - session, {"batch_size":batch_size}, + {"batch_size":batch_size}, {"collation":collation, "max_await_time_ms": max_await_time_ms}) return self._explain_command(command) From 69ad0d92a255ecd5caba9224320eac9eb63111c5 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Thu, 23 Jul 2020 13:45:14 -0400 Subject: [PATCH 17/26] remove extraneous SON --- pymongoexplain/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index e1ad4fd..720d0ce 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -177,8 +177,8 @@ class DeleteCommand(BaseCommand): def __init__(self, collection: Collection, filter, limit, collation, kwargs): super().__init__(collection.name, kwargs.pop("collation", None)) - self.command_document["deletes"] = [SON({"q": filter, "limit": - limit})] + self.command_document["deletes"] = [{"q": filter, "limit": + limit}] for key, value in kwargs.items(): if key == "hint": self.command_document["deletes"][0]["hint"] = value if \ From 075c0c1c1170d1a4c64e463978a9ec91d59f1ad0 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Fri, 24 Jul 2020 11:16:37 -0400 Subject: [PATCH 18/26] shane fixes --- pymongoexplain/commands.py | 6 +++--- pymongoexplain/explainable_collection.py | 4 ++-- pymongoexplain/utils.py | 5 +---- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 720d0ce..22aea72 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -53,7 +53,8 @@ def __init__(self, collection: Collection, filter, update, hint=None, ordered=None, write_concern=None, bypass_document_validation=None, comment=None): super().__init__(collection.name, collation) - self.command_document["updates"] = [{"q": filter, "u": update}] + update_doc = {"q": filter, "u": update} + self.command_document["updates"] = [update_doc] if upsert is not None: self.command_document["updates"][0]["upsert"] = upsert @@ -126,8 +127,7 @@ def command_name(self): class CountCommand(BaseCommand): - def __init__(self, collection: Collection, filter, kwargs, - sxclude_keys=None): + def __init__(self, collection: Collection, filter, kwargs): super().__init__(collection.name, kwargs.pop("collation", None)) self.command_document.update({"query": filter}) for key, value in kwargs.items(): diff --git a/pymongoexplain/explainable_collection.py b/pymongoexplain/explainable_collection.py index 5d42488..bf227aa 100644 --- a/pymongoexplain/explainable_collection.py +++ b/pymongoexplain/explainable_collection.py @@ -146,7 +146,7 @@ def find_one_and_delete(self, filter: Document, projection: list = None, kwargs) return self._explain_command(command) - def find_one_and_replace(self, filter: Document, update: + def find_one_and_replace(self, filter: Document, replacement: Document={}, projection: list = None, sort=None, return_document=pymongo.ReturnDocument.BEFORE, @@ -155,7 +155,7 @@ def find_one_and_replace(self, filter: Document, update: kwargs["fields"] = projection kwargs["sort"] = sort kwargs["new"] = False - kwargs["update"] = update + kwargs["update"] = replacement kwargs["session"] = session command = FindAndModifyCommand(self.collection, kwargs) diff --git a/pymongoexplain/utils.py b/pymongoexplain/utils.py index a2c5bd0..12c00d0 100644 --- a/pymongoexplain/utils.py +++ b/pymongoexplain/utils.py @@ -16,16 +16,13 @@ """Utility functions""" -def convert_to_camelcase(d, exclude_keys=[]): +def convert_to_camelcase(d): if not isinstance(d, dict): return d ret = dict() for key in d.keys(): if d[key] is None: continue - if key in exclude_keys: - ret[key] = d[key] - continue new_key = key if "_" in key and key[0] != "_": new_key = key.split("_")[0] + ''.join( From 618ae9413d16b7e29cea88e6678682a4d5d77f94 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Fri, 24 Jul 2020 11:28:03 -0400 Subject: [PATCH 19/26] add tests for projection --- pymongoexplain/commands.py | 15 +++++++++++++-- test/test_collection.py | 13 +++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 22aea72..ca574ec 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -20,7 +20,7 @@ from bson.son import SON from pymongo.collection import Collection -from pymongo.helpers import _index_document +from pymongo.helpers import _index_document, _fields_list_to_dict from pymongo.collation import validate_collation_or_none from .utils import convert_to_camelcase @@ -144,7 +144,15 @@ def __init__(self, collection: Collection, kwargs): super().__init__(collection.name, kwargs.pop("collation", None)) for key, value in kwargs.items(): - self.command_document[key] = value + if key == "projection" and value is not None: + self.command_document["projection"] = _fields_list_to_dict( + value, "projection") + elif key == "sort": + self.command_document["sort"] = _index_document( + value) + else: + self.command_document[key] = value + self.command_document = convert_to_camelcase(self.command_document) @property @@ -164,6 +172,9 @@ def __init__(self, collection: Collection, isinstance(value, str) else _index_document(value) elif key == "replacement": self.command_document["update"] = value + elif key == "sort" and value is not None: + self.command_document["sort"] = _index_document( + value) else: self.command_document[key] = value self.command_document = convert_to_camelcase(self.command_document) diff --git a/test/test_collection.py b/test/test_collection.py index c8e7de1..44e22ab 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -140,10 +140,19 @@ def test_find(self): last_cmd_payload = self.explain.last_cmd_payload self._compare_command_dicts(last_cmd_payload, last_logger_payload) + + def test_find_one(self): - self.collection.find_one() + self.collection.find_one(projection=['a', 'b.c']) + last_logger_payload = self.logger.cmd_payload + res = self.explain.find_one(projection=['a', 'b.c']) + self.assertIn("queryPlanner", res) + last_cmd_payload = self.explain.last_cmd_payload + self._compare_command_dicts(last_cmd_payload, last_logger_payload) + + self.collection.find_one(projection={'a': 1, 'b.c': 1}) last_logger_payload = self.logger.cmd_payload - res = self.explain.find_one() + res = self.explain.find_one(projection={'a': 1, 'b.c': 1}) self.assertIn("queryPlanner", res) last_cmd_payload = self.explain.last_cmd_payload self._compare_command_dicts(last_cmd_payload, last_logger_payload) From 756c4fc8eb899a5ea76c9e293a11a996c1d25480 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Fri, 24 Jul 2020 12:37:51 -0400 Subject: [PATCH 20/26] removed replacement --- pymongoexplain/commands.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index ca574ec..eb75d71 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -165,13 +165,9 @@ def __init__(self, collection: Collection, kwargs): super().__init__(collection.name, kwargs.pop("collation", None)) for key, value in kwargs.items(): - if key == "update" and kwargs.get("replacement", None) is not None: - continue if key == "hint": self.command_document["hint"] = value if \ isinstance(value, str) else _index_document(value) - elif key == "replacement": - self.command_document["update"] = value elif key == "sort" and value is not None: self.command_document["sort"] = _index_document( value) From 9248101117a5a04f567bf8b61eb579ab549b124d Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 28 Jul 2020 08:53:12 -0400 Subject: [PATCH 21/26] shane fixes --- pymongoexplain/commands.py | 62 ++++++++++++++++++++++++++++++++++---- setup.py | 4 +-- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index eb75d71..21260a2 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -19,8 +19,9 @@ from typing import Union from bson.son import SON +from bson.py3compat import abc, iteritems, string_type + from pymongo.collection import Collection -from pymongo.helpers import _index_document, _fields_list_to_dict from pymongo.collation import validate_collation_or_none from .utils import convert_to_camelcase @@ -28,6 +29,55 @@ Document = Union[dict, SON] +def _index_document(index_list): + """Helper to generate an index specifying document. + + Takes a list of (key, direction) pairs. + """ + if isinstance(index_list, abc.Mapping): + raise TypeError("passing a dict to sort/create_index/hint is not " + "allowed - use a list of tuples instead. did you " + "mean %r?" % list(iteritems(index_list))) + elif not isinstance(index_list, (list, tuple)): + raise TypeError("must use a list of (key, direction) pairs, " + "not: " + repr(index_list)) + if not len(index_list): + raise ValueError("key_or_list must not be the empty list") + + index = SON() + for (key, value) in index_list: + if not isinstance(key, string_type): + raise TypeError("first item in each key pair must be a string") + if not isinstance(value, (string_type, int, abc.Mapping)): + raise TypeError("second item in each key pair must be 1, -1, " + "'2d', 'geoHaystack', or another valid MongoDB " + "index specifier.") + index[key] = value + return index + + +def _fields_list_to_dict(fields, option_name): + """Takes a sequence of field names and returns a matching dictionary. + + ["a", "b"] becomes {"a": 1, "b": 1} + + and + + ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} + """ + if isinstance(fields, abc.Mapping): + return fields + + if isinstance(fields, (abc.Sequence, abc.Set)): + if not all(isinstance(field, string_type) for field in fields): + raise TypeError("%s must be a list of key names, each an " + "instance of %s" % (option_name, + string_type.__name__)) + return dict.fromkeys(fields, 1) + + raise TypeError("%s must be a mapping or " + "list of key names" % (option_name,)) + class BaseCommand(): def __init__(self, collection, collation): self.command_document = {} @@ -54,19 +104,19 @@ def __init__(self, collection: Collection, filter, update, bypass_document_validation=None, comment=None): super().__init__(collection.name, collation) update_doc = {"q": filter, "u": update} - self.command_document["updates"] = [update_doc] if upsert is not None: - self.command_document["updates"][0]["upsert"] = upsert + update_doc["upsert"] = upsert if multi is not None: - self.command_document["updates"][0]["multi"] = multi + update_doc["multi"] = multi if array_filters is not None: - self.command_document["updates"][0]["array_filters"] = array_filters + update_doc["array_filters"] = array_filters if hint is not None: - self.command_document["updates"][0]["hint"] = hint if \ + update_doc["hint"] = hint if \ isinstance(hint, str) else _index_document(hint) + self.command_document["updates"] = [update_doc] if ordered is not None: self.command_document["ordered"] = ordered diff --git a/setup.py b/setup.py index 02aed4e..857c21b 100644 --- a/setup.py +++ b/setup.py @@ -26,8 +26,8 @@ 'console_scripts': [ 'pymongoexplain=pymongoexplain.cli_explain:cli_explain'], }, - tests_require=["pymongo==3.11.0rc0"], - install_requires=['pymongo==3.11.0rc0'], + tests_require=["pymongo>=3.11"], + install_requires=['pymongo>=3.11'], python_requires='>=3.5', license="Apache License, Version 2.0", classifiers=[ From 12ea3c0eda1f513d4a5871947d6f6eb3527db61b Mon Sep 17 00:00:00 2001 From: Julius Park Date: Tue, 28 Jul 2020 13:59:31 -0400 Subject: [PATCH 22/26] change version numbers for requires --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 857c21b..8282943 100644 --- a/setup.py +++ b/setup.py @@ -26,8 +26,8 @@ 'console_scripts': [ 'pymongoexplain=pymongoexplain.cli_explain:cli_explain'], }, - tests_require=["pymongo>=3.11"], - install_requires=['pymongo>=3.11'], + tests_require=["pymongo>=3.10"], + install_requires=['pymongo>=3.10'], python_requires='>=3.5', license="Apache License, Version 2.0", classifiers=[ From f7b03b68d47e45d6f600c2c1749e4c269a94bbe8 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 29 Jul 2020 10:31:21 -0400 Subject: [PATCH 23/26] removed py3compat --- pymongoexplain/commands.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 21260a2..4f02098 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -19,7 +19,6 @@ from typing import Union from bson.son import SON -from bson.py3compat import abc, iteritems, string_type from pymongo.collection import Collection from pymongo.collation import validate_collation_or_none @@ -34,10 +33,10 @@ def _index_document(index_list): Takes a list of (key, direction) pairs. """ - if isinstance(index_list, abc.Mapping): + if isinstance(index_list, dict): raise TypeError("passing a dict to sort/create_index/hint is not " "allowed - use a list of tuples instead. did you " - "mean %r?" % list(iteritems(index_list))) + "mean %r?" % list(index_list.items())) elif not isinstance(index_list, (list, tuple)): raise TypeError("must use a list of (key, direction) pairs, " "not: " + repr(index_list)) @@ -46,9 +45,9 @@ def _index_document(index_list): index = SON() for (key, value) in index_list: - if not isinstance(key, string_type): + if not isinstance(key, str): raise TypeError("first item in each key pair must be a string") - if not isinstance(value, (string_type, int, abc.Mapping)): + if not isinstance(value, (str, int, Document)): raise TypeError("second item in each key pair must be 1, -1, " "'2d', 'geoHaystack', or another valid MongoDB " "index specifier.") @@ -65,14 +64,14 @@ def _fields_list_to_dict(fields, option_name): ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} """ - if isinstance(fields, abc.Mapping): + if isinstance(fields, Document): return fields - if isinstance(fields, (abc.Sequence, abc.Set)): - if not all(isinstance(field, string_type) for field in fields): + if isinstance(fields, (list, set)): + if not all(isinstance(field, str) for field in fields): raise TypeError("%s must be a list of key names, each an " "instance of %s" % (option_name, - string_type.__name__)) + str.__name__)) return dict.fromkeys(fields, 1) raise TypeError("%s must be a mapping or " From 12aaf0598dacf0bcb4f84eaeb1d72d7af4e8ccbf Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 29 Jul 2020 10:39:34 -0400 Subject: [PATCH 24/26] fixed type checking --- pymongoexplain/commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 4f02098..2a7f5ab 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -47,7 +47,7 @@ def _index_document(index_list): for (key, value) in index_list: if not isinstance(key, str): raise TypeError("first item in each key pair must be a string") - if not isinstance(value, (str, int, Document)): + if not isinstance(value, (str, int, dict, SON)): raise TypeError("second item in each key pair must be 1, -1, " "'2d', 'geoHaystack', or another valid MongoDB " "index specifier.") @@ -64,7 +64,7 @@ def _fields_list_to_dict(fields, option_name): ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} """ - if isinstance(fields, Document): + if isinstance(fields, (dict, SON)): return fields if isinstance(fields, (list, set)): From 5a419d9d8801194847c4aa77d22a5a045fdc73e9 Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 29 Jul 2020 14:35:22 -0400 Subject: [PATCH 25/26] restored references to abc --- pymongoexplain/commands.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pymongoexplain/commands.py b/pymongoexplain/commands.py index 2a7f5ab..4344ef7 100644 --- a/pymongoexplain/commands.py +++ b/pymongoexplain/commands.py @@ -19,6 +19,7 @@ from typing import Union from bson.son import SON +from collections import abc from pymongo.collection import Collection from pymongo.collation import validate_collation_or_none @@ -33,7 +34,7 @@ def _index_document(index_list): Takes a list of (key, direction) pairs. """ - if isinstance(index_list, dict): + if isinstance(index_list, abc.Mapping): raise TypeError("passing a dict to sort/create_index/hint is not " "allowed - use a list of tuples instead. did you " "mean %r?" % list(index_list.items())) @@ -47,7 +48,7 @@ def _index_document(index_list): for (key, value) in index_list: if not isinstance(key, str): raise TypeError("first item in each key pair must be a string") - if not isinstance(value, (str, int, dict, SON)): + if not isinstance(value, (str, int, abc.Mapping)): raise TypeError("second item in each key pair must be 1, -1, " "'2d', 'geoHaystack', or another valid MongoDB " "index specifier.") @@ -64,10 +65,10 @@ def _fields_list_to_dict(fields, option_name): ["a.b.c", "d", "a.c"] becomes {"a.b.c": 1, "d": 1, "a.c": 1} """ - if isinstance(fields, (dict, SON)): + if isinstance(fields, abc.Mapping): return fields - if isinstance(fields, (list, set)): + if isinstance(fields, (abc.Sequence, abc.Set)): if not all(isinstance(field, str) for field in fields): raise TypeError("%s must be a list of key names, each an " "instance of %s" % (option_name, @@ -77,6 +78,7 @@ def _fields_list_to_dict(fields, option_name): raise TypeError("%s must be a mapping or " "list of key names" % (option_name,)) + class BaseCommand(): def __init__(self, collection, collation): self.command_document = {} From 83063cba5d5c319f9e5f3a036ce90e7a79a5652d Mon Sep 17 00:00:00 2001 From: Julius Park Date: Wed, 29 Jul 2020 15:00:39 -0400 Subject: [PATCH 26/26] remove v1 tests --- test/crud/v1/read/aggregate-collation.json | 38 - test/crud/v1/read/aggregate-out.json | 121 --- test/crud/v1/read/aggregate.json | 53 -- test/crud/v1/read/count-collation.json | 47 -- test/crud/v1/read/count-empty.json | 39 - test/crud/v1/read/count.json | 112 --- test/crud/v1/read/distinct-collation.json | 33 - test/crud/v1/read/distinct.json | 55 -- test/crud/v1/read/find-collation.json | 34 - test/crud/v1/read/find.json | 105 --- .../crud/v1/write/bulkWrite-arrayFilters.json | 111 --- test/crud/v1/write/bulkWrite-collation.json | 217 ----- test/crud/v1/write/bulkWrite.json | 778 ------------------ test/crud/v1/write/deleteMany-collation.json | 47 -- test/crud/v1/write/deleteMany.json | 76 -- test/crud/v1/write/deleteOne-collation.json | 51 -- test/crud/v1/write/deleteOne.json | 96 --- .../v1/write/findOneAndDelete-collation.json | 59 -- test/crud/v1/write/findOneAndDelete.json | 127 --- .../v1/write/findOneAndReplace-collation.json | 58 -- .../v1/write/findOneAndReplace-upsert.json | 201 ----- test/crud/v1/write/findOneAndReplace.json | 273 ------ .../write/findOneAndUpdate-arrayFilters.json | 203 ----- .../v1/write/findOneAndUpdate-collation.json | 67 -- test/crud/v1/write/findOneAndUpdate.json | 379 --------- test/crud/v1/write/insertMany.json | 159 ---- test/crud/v1/write/insertOne.json | 39 - test/crud/v1/write/replaceOne-collation.json | 53 -- test/crud/v1/write/replaceOne.json | 205 ----- .../v1/write/updateMany-arrayFilters.json | 185 ----- test/crud/v1/write/updateMany-collation.json | 62 -- test/crud/v1/write/updateMany.json | 183 ---- .../crud/v1/write/updateOne-arrayFilters.json | 395 --------- test/crud/v1/write/updateOne-collation.json | 54 -- test/crud/v1/write/updateOne.json | 167 ---- 35 files changed, 4882 deletions(-) delete mode 100644 test/crud/v1/read/aggregate-collation.json delete mode 100644 test/crud/v1/read/aggregate-out.json delete mode 100644 test/crud/v1/read/aggregate.json delete mode 100644 test/crud/v1/read/count-collation.json delete mode 100644 test/crud/v1/read/count-empty.json delete mode 100644 test/crud/v1/read/count.json delete mode 100644 test/crud/v1/read/distinct-collation.json delete mode 100644 test/crud/v1/read/distinct.json delete mode 100644 test/crud/v1/read/find-collation.json delete mode 100644 test/crud/v1/read/find.json delete mode 100644 test/crud/v1/write/bulkWrite-arrayFilters.json delete mode 100644 test/crud/v1/write/bulkWrite-collation.json delete mode 100644 test/crud/v1/write/bulkWrite.json delete mode 100644 test/crud/v1/write/deleteMany-collation.json delete mode 100644 test/crud/v1/write/deleteMany.json delete mode 100644 test/crud/v1/write/deleteOne-collation.json delete mode 100644 test/crud/v1/write/deleteOne.json delete mode 100644 test/crud/v1/write/findOneAndDelete-collation.json delete mode 100644 test/crud/v1/write/findOneAndDelete.json delete mode 100644 test/crud/v1/write/findOneAndReplace-collation.json delete mode 100644 test/crud/v1/write/findOneAndReplace-upsert.json delete mode 100644 test/crud/v1/write/findOneAndReplace.json delete mode 100644 test/crud/v1/write/findOneAndUpdate-arrayFilters.json delete mode 100644 test/crud/v1/write/findOneAndUpdate-collation.json delete mode 100644 test/crud/v1/write/findOneAndUpdate.json delete mode 100644 test/crud/v1/write/insertMany.json delete mode 100644 test/crud/v1/write/insertOne.json delete mode 100644 test/crud/v1/write/replaceOne-collation.json delete mode 100644 test/crud/v1/write/replaceOne.json delete mode 100644 test/crud/v1/write/updateMany-arrayFilters.json delete mode 100644 test/crud/v1/write/updateMany-collation.json delete mode 100644 test/crud/v1/write/updateMany.json delete mode 100644 test/crud/v1/write/updateOne-arrayFilters.json delete mode 100644 test/crud/v1/write/updateOne-collation.json delete mode 100644 test/crud/v1/write/updateOne.json diff --git a/test/crud/v1/read/aggregate-collation.json b/test/crud/v1/read/aggregate-collation.json deleted file mode 100644 index 85662a4..0000000 --- a/test/crud/v1/read/aggregate-collation.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "Aggregate with collation", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$match": { - "x": "PING" - } - } - ], - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": "ping" - } - ] - } - } - ] -} diff --git a/test/crud/v1/read/aggregate-out.json b/test/crud/v1/read/aggregate-out.json deleted file mode 100644 index 205cf76..0000000 --- a/test/crud/v1/read/aggregate-out.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "Aggregate with $out", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$out": "other_test_collection" - } - ], - "batchSize": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "collection": { - "name": "other_test_collection", - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "Aggregate with $out and batch size of 0", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - }, - { - "$out": "other_test_collection" - } - ], - "batchSize": 0 - } - }, - "outcome": { - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "collection": { - "name": "other_test_collection", - "data": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/read/aggregate.json b/test/crud/v1/read/aggregate.json deleted file mode 100644 index 797a922..0000000 --- a/test/crud/v1/read/aggregate.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Aggregate with multiple stages", - "operation": { - "name": "aggregate", - "arguments": { - "pipeline": [ - { - "$sort": { - "x": 1 - } - }, - { - "$match": { - "_id": { - "$gt": 1 - } - } - } - ], - "batchSize": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - ] -} diff --git a/test/crud/v1/read/count-collation.json b/test/crud/v1/read/count-collation.json deleted file mode 100644 index 6f75282..0000000 --- a/test/crud/v1/read/count-collation.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "PING" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "Count documents with collation", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": { - "x": "ping" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": 1 - } - }, - { - "description": "Deprecated count with collation", - "operation": { - "name": "count", - "arguments": { - "filter": { - "x": "ping" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": 1 - } - } - ] -} diff --git a/test/crud/v1/read/count-empty.json b/test/crud/v1/read/count-empty.json deleted file mode 100644 index 2b8627e..0000000 --- a/test/crud/v1/read/count-empty.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": [], - "tests": [ - { - "description": "Estimated document count with empty collection", - "operation": { - "name": "estimatedDocumentCount", - "arguments": {} - }, - "outcome": { - "result": 0 - } - }, - { - "description": "Count documents with empty collection", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 0 - } - }, - { - "description": "Deprecated count with empty collection", - "operation": { - "name": "count", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 0 - } - } - ] -} diff --git a/test/crud/v1/read/count.json b/test/crud/v1/read/count.json deleted file mode 100644 index 9642b2f..0000000 --- a/test/crud/v1/read/count.json +++ /dev/null @@ -1,112 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Estimated document count", - "operation": { - "name": "estimatedDocumentCount", - "arguments": {} - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Count documents without a filter", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Count documents with a filter", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Count documents with skip and limit", - "operation": { - "name": "countDocuments", - "arguments": { - "filter": {}, - "skip": 1, - "limit": 3 - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Deprecated count without a filter", - "operation": { - "name": "count", - "arguments": { - "filter": {} - } - }, - "outcome": { - "result": 3 - } - }, - { - "description": "Deprecated count with a filter", - "operation": { - "name": "count", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": 2 - } - }, - { - "description": "Deprecated count with skip and limit", - "operation": { - "name": "count", - "arguments": { - "filter": {}, - "skip": 1, - "limit": 3 - } - }, - "outcome": { - "result": 2 - } - } - ] -} diff --git a/test/crud/v1/read/distinct-collation.json b/test/crud/v1/read/distinct-collation.json deleted file mode 100644 index 0af0c67..0000000 --- a/test/crud/v1/read/distinct-collation.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "string": "PING" - }, - { - "_id": 2, - "string": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "Distinct with a collation", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "string", - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - "PING" - ] - } - } - ] -} diff --git a/test/crud/v1/read/distinct.json b/test/crud/v1/read/distinct.json deleted file mode 100644 index a57ee36..0000000 --- a/test/crud/v1/read/distinct.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "Distinct without a filter", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "x", - "filter": {} - } - }, - "outcome": { - "result": [ - 11, - 22, - 33 - ] - } - }, - { - "description": "Distinct with a filter", - "operation": { - "name": "distinct", - "arguments": { - "fieldName": "x", - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": [ - 22, - 33 - ] - } - } - ] -} diff --git a/test/crud/v1/read/find-collation.json b/test/crud/v1/read/find-collation.json deleted file mode 100644 index 53d0e94..0000000 --- a/test/crud/v1/read/find-collation.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "Find with a collation", - "operation": { - "name": "find", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": "ping" - } - ] - } - } - ] -} diff --git a/test/crud/v1/read/find.json b/test/crud/v1/read/find.json deleted file mode 100644 index 3597e37..0000000 --- a/test/crud/v1/read/find.json +++ /dev/null @@ -1,105 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - }, - { - "_id": 5, - "x": 55 - } - ], - "tests": [ - { - "description": "Find with filter", - "operation": { - "name": "find", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": 11 - } - ] - } - }, - { - "description": "Find with filter, sort, skip, and limit", - "operation": { - "name": "find", - "arguments": { - "filter": { - "_id": { - "$gt": 2 - } - }, - "sort": { - "_id": 1 - }, - "skip": 2, - "limit": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 5, - "x": 55 - } - ] - } - }, - { - "description": "Find with limit, sort, and batchsize", - "operation": { - "name": "find", - "arguments": { - "filter": {}, - "sort": { - "_id": 1 - }, - "limit": 4, - "batchSize": 2 - } - }, - "outcome": { - "result": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - ] -} diff --git a/test/crud/v1/write/bulkWrite-arrayFilters.json b/test/crud/v1/write/bulkWrite-arrayFilters.json deleted file mode 100644 index 99e73f5..0000000 --- a/test/crud/v1/write/bulkWrite-arrayFilters.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "BulkWrite with arrayFilters", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 3, - "modifiedCount": 3, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 2 - } - ] - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/bulkWrite-collation.json b/test/crud/v1/write/bulkWrite-collation.json deleted file mode 100644 index 8e9d1bc..0000000 --- a/test/crud/v1/write/bulkWrite-collation.json +++ /dev/null @@ -1,217 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - }, - { - "_id": 4, - "x": "pong" - }, - { - "_id": 5, - "x": "pONg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "BulkWrite with delete operations and collation", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": "PONG" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 4, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "BulkWrite with update operations and collation", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateMany", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 3 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "x": "ping" - }, - "replacement": { - "_id": 6, - "x": "ping" - }, - "upsert": true, - "collation": { - "locale": "en_US", - "strength": 3 - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": "pong" - }, - "update": { - "$set": { - "x": "PONG" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 6, - "modifiedCount": 4, - "upsertedCount": 1, - "upsertedIds": { - "2": 6 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "PONG" - }, - { - "_id": 3, - "x": "PONG" - }, - { - "_id": 4, - "x": "PONG" - }, - { - "_id": 5, - "x": "PONG" - }, - { - "_id": 6, - "x": "ping" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/bulkWrite.json b/test/crud/v1/write/bulkWrite.json deleted file mode 100644 index dc00da2..0000000 --- a/test/crud/v1/write/bulkWrite.json +++ /dev/null @@ -1,778 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "BulkWrite with deleteOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 3 - } - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 2 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "BulkWrite with deleteMany operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$lt": 11 - } - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 2, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [] - } - } - }, - { - "description": "BulkWrite with insertOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 2, - "insertedIds": { - "0": 3, - "1": 4 - }, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite with replaceOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "x": 33 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "x": 12 - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "x": 33 - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "2": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with updateOne operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 0 - }, - "update": { - "$set": { - "x": 0 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$set": { - "x": 11 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "x": 33 - } - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 2, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "3": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with updateMany operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lt": 11 - } - }, - "update": { - "$set": { - "x": 0 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - }, - "update": { - "$unset": { - "y": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "x": { - "$lte": 22 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "x": 33 - } - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 0, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 4, - "modifiedCount": 2, - "upsertedCount": 1, - "upsertedIds": { - "3": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite with mixed ordered operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "updateMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - }, - { - "name": "deleteMany", - "arguments": { - "filter": { - "x": { - "$nin": [ - 24, - 34 - ] - } - } - } - }, - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "upsert": true - } - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "deletedCount": 2, - "insertedCount": 2, - "insertedIds": { - "0": 3, - "3": 4 - }, - "matchedCount": 3, - "modifiedCount": 3, - "upsertedCount": 1, - "upsertedIds": { - "5": 4 - } - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 24 - }, - { - "_id": 3, - "x": 34 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite with mixed unordered operations", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 3 - }, - "replacement": { - "_id": 3, - "x": 33 - }, - "upsert": true - } - }, - { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 1 - } - } - }, - { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "result": { - "deletedCount": 1, - "insertedCount": 0, - "insertedIds": {}, - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 1, - "upsertedIds": { - "0": 3 - } - }, - "collection": { - "data": [ - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "BulkWrite continue-on-error behavior with unordered (preexisting duplicate key)", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "BulkWrite continue-on-error behavior with unordered (duplicate key in requests)", - "operation": { - "name": "bulkWrite", - "arguments": { - "requests": [ - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 3, - "x": 33 - } - } - }, - { - "name": "insertOne", - "arguments": { - "document": { - "_id": 4, - "x": 44 - } - } - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/deleteMany-collation.json b/test/crud/v1/write/deleteMany-collation.json deleted file mode 100644 index d17bf3b..0000000 --- a/test/crud/v1/write/deleteMany-collation.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "DeleteMany when many documents match with collation", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/deleteMany.json b/test/crud/v1/write/deleteMany.json deleted file mode 100644 index 7eee85e..0000000 --- a/test/crud/v1/write/deleteMany.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "DeleteMany when many documents match", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": { - "deletedCount": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - } - ] - } - } - }, - { - "description": "DeleteMany when no document matches", - "operation": { - "name": "deleteMany", - "arguments": { - "filter": { - "_id": 4 - } - } - }, - "outcome": { - "result": { - "deletedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/deleteOne-collation.json b/test/crud/v1/write/deleteOne-collation.json deleted file mode 100644 index 2f7f921..0000000 --- a/test/crud/v1/write/deleteOne-collation.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "DeleteOne when many documents matches with collation", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "x": "PING" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/deleteOne.json b/test/crud/v1/write/deleteOne.json deleted file mode 100644 index a1106de..0000000 --- a/test/crud/v1/write/deleteOne.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "DeleteOne when many documents match", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - } - } - }, - { - "description": "DeleteOne when one document matches", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 2 - } - } - }, - "outcome": { - "result": { - "deletedCount": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "DeleteOne when no documents match", - "operation": { - "name": "deleteOne", - "arguments": { - "filter": { - "_id": 4 - } - } - }, - "outcome": { - "result": { - "deletedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndDelete-collation.json b/test/crud/v1/write/findOneAndDelete-collation.json deleted file mode 100644 index 1ff37d2..0000000 --- a/test/crud/v1/write/findOneAndDelete-collation.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "FindOneAndDelete when one document matches with collation", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 2, - "x": "PING" - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "ping" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndDelete.json b/test/crud/v1/write/findOneAndDelete.json deleted file mode 100644 index e424e2a..0000000 --- a/test/crud/v1/write/findOneAndDelete.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndDelete when many documents match", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndDelete when one document matches", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 2 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndDelete when no documents match", - "operation": { - "name": "findOneAndDelete", - "arguments": { - "filter": { - "_id": 4 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndReplace-collation.json b/test/crud/v1/write/findOneAndReplace-collation.json deleted file mode 100644 index babb2f7..0000000 --- a/test/crud/v1/write/findOneAndReplace-collation.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "FindOneAndReplace when one document matches with collation returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "x": "PING" - }, - "replacement": { - "x": "pong" - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "pong" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndReplace-upsert.json b/test/crud/v1/write/findOneAndReplace-upsert.json deleted file mode 100644 index 0f07bf9..0000000 --- a/test/crud/v1/write/findOneAndReplace-upsert.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match without id specified with upsert returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 44 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match with id specified with upsert returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 44 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 44 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndReplace.json b/test/crud/v1/write/findOneAndReplace.json deleted file mode 100644 index 70e5c3d..0000000 --- a/test/crud/v1/write/findOneAndReplace.json +++ /dev/null @@ -1,273 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndReplace when many documents match returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when many documents match returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 32 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when one document matches returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 2 - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when one document matches returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 2 - }, - "replacement": { - "x": 32 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 32 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 32 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match returning the document before modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndReplace when no documents match returning the document after modification", - "operation": { - "name": "findOneAndReplace", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 44 - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndUpdate-arrayFilters.json b/test/crud/v1/write/findOneAndUpdate-arrayFilters.json deleted file mode 100644 index 1aa13b8..0000000 --- a/test/crud/v1/write/findOneAndUpdate-arrayFilters.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "FindOneAndUpdate when no document matches arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when multiple documents match arrayFilters", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndUpdate-collation.json b/test/crud/v1/write/findOneAndUpdate-collation.json deleted file mode 100644 index 04c1fe7..0000000 --- a/test/crud/v1/write/findOneAndUpdate-collation.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "FindOneAndUpdate when many documents match with collation returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "x": "PING" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "_id": 1 - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "x": "ping" - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - }, - { - "_id": 3, - "x": "pINg" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/findOneAndUpdate.json b/test/crud/v1/write/findOneAndUpdate.json deleted file mode 100644 index 6da8325..0000000 --- a/test/crud/v1/write/findOneAndUpdate.json +++ /dev/null @@ -1,379 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "tests": [ - { - "description": "FindOneAndUpdate when many documents match returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when many documents match returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 23 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 22 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when one document matches returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 2 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": { - "x": 23 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match with upsert returning the document before modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "upsert": true - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - } - } - }, - "outcome": { - "result": null, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "FindOneAndUpdate when no documents match with upsert returning the document after modification", - "operation": { - "name": "findOneAndUpdate", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "projection": { - "x": 1, - "_id": 0 - }, - "returnDocument": "After", - "sort": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "x": 1 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/insertMany.json b/test/crud/v1/write/insertMany.json deleted file mode 100644 index 6a2e526..0000000 --- a/test/crud/v1/write/insertMany.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertMany with non-existing documents", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": true - } - } - }, - "outcome": { - "result": { - "insertedIds": { - "0": 2, - "1": 3 - } - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany continue-on-error behavior with unordered (preexisting duplicate key)", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "InsertMany continue-on-error behavior with unordered (duplicate key in requests)", - "operation": { - "name": "insertMany", - "arguments": { - "documents": [ - { - "_id": 2, - "x": 22 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "options": { - "ordered": false - } - } - }, - "outcome": { - "error": true, - "result": { - "deletedCount": 0, - "insertedCount": 2, - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0, - "upsertedIds": {} - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/insertOne.json b/test/crud/v1/write/insertOne.json deleted file mode 100644 index 525de75..0000000 --- a/test/crud/v1/write/insertOne.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - } - ], - "tests": [ - { - "description": "InsertOne with a non-existing document", - "operation": { - "name": "insertOne", - "arguments": { - "document": { - "_id": 2, - "x": 22 - } - } - }, - "outcome": { - "result": { - "insertedId": 2 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/replaceOne-collation.json b/test/crud/v1/write/replaceOne-collation.json deleted file mode 100644 index a668fe7..0000000 --- a/test/crud/v1/write/replaceOne-collation.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "ReplaceOne when one document matches with collation", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "x": "PING" - }, - "replacement": { - "_id": 2, - "x": "pong" - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/replaceOne.json b/test/crud/v1/write/replaceOne.json deleted file mode 100644 index 101af25..0000000 --- a/test/crud/v1/write/replaceOne.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "ReplaceOne when many documents match", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "replacement": { - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - }, - { - "description": "ReplaceOne when one document matches", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 1 - }, - "replacement": { - "_id": 1, - "x": 111 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 111 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "ReplaceOne when no documents match", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 1 - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "ReplaceOne with upsert when no documents match without an id specified", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - }, - { - "description": "ReplaceOne with upsert when no documents match with an id specified", - "operation": { - "name": "replaceOne", - "arguments": { - "filter": { - "_id": 4 - }, - "replacement": { - "_id": 4, - "x": 1 - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateMany-arrayFilters.json b/test/crud/v1/write/updateMany-arrayFilters.json deleted file mode 100644 index ae4c123..0000000 --- a/test/crud/v1/write/updateMany-arrayFilters.json +++ /dev/null @@ -1,185 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "UpdateMany when no documents match arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "UpdateMany when one document matches arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - } - ] - } - } - }, - { - "description": "UpdateMany when multiple documents match arrayFilters", - "operation": { - "name": "updateMany", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 2 - } - ] - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateMany-collation.json b/test/crud/v1/write/updateMany-collation.json deleted file mode 100644 index 3cb49f2..0000000 --- a/test/crud/v1/write/updateMany-collation.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - }, - { - "_id": 3, - "x": "pINg" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "UpdateMany when many documents match with collation", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "x": "ping" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - }, - { - "_id": 3, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateMany.json b/test/crud/v1/write/updateMany.json deleted file mode 100644 index a3c3399..0000000 --- a/test/crud/v1/write/updateMany.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "UpdateMany when many documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 2, - "modifiedCount": 2, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 23 - }, - { - "_id": 3, - "x": 34 - } - ] - } - } - }, - { - "description": "UpdateMany when one document matches", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateMany when no documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateMany with upsert when no documents match", - "operation": { - "name": "updateMany", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateOne-arrayFilters.json b/test/crud/v1/write/updateOne-arrayFilters.json deleted file mode 100644 index 087ed4b..0000000 --- a/test/crud/v1/write/updateOne-arrayFilters.json +++ /dev/null @@ -1,395 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ], - "minServerVersion": "3.5.6", - "tests": [ - { - "description": "UpdateOne when no document matches arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 4 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when one document matches arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 2 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when multiple documents match arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": {}, - "update": { - "$set": { - "y.$[i].b": 2 - } - }, - "arrayFilters": [ - { - "i.b": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 2 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when no documents match multiple arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "y.$[i].c.$[j].d": 0 - } - }, - "arrayFilters": [ - { - "i.b": 5 - }, - { - "j.d": 3 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 1 - } - ] - } - ] - } - ] - } - } - }, - { - "description": "UpdateOne when one document matches multiple arrayFilters", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 3 - }, - "update": { - "$set": { - "y.$[i].c.$[j].d": 0 - } - }, - "arrayFilters": [ - { - "i.b": 5 - }, - { - "j.d": 1 - } - ] - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "y": [ - { - "b": 3 - }, - { - "b": 1 - } - ] - }, - { - "_id": 2, - "y": [ - { - "b": 0 - }, - { - "b": 1 - } - ] - }, - { - "_id": 3, - "y": [ - { - "b": 5, - "c": [ - { - "d": 2 - }, - { - "d": 0 - } - ] - } - ] - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateOne-collation.json b/test/crud/v1/write/updateOne-collation.json deleted file mode 100644 index c49112d..0000000 --- a/test/crud/v1/write/updateOne-collation.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "ping" - } - ], - "minServerVersion": "3.4", - "tests": [ - { - "description": "UpdateOne when one document matches with collation", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "x": "PING" - }, - "update": { - "$set": { - "x": "pong" - } - }, - "collation": { - "locale": "en_US", - "strength": 2 - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": "pong" - } - ] - } - } - } - ] -} diff --git a/test/crud/v1/write/updateOne.json b/test/crud/v1/write/updateOne.json deleted file mode 100644 index 76d2086..0000000 --- a/test/crud/v1/write/updateOne.json +++ /dev/null @@ -1,167 +0,0 @@ -{ - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ], - "minServerVersion": "2.6", - "tests": [ - { - "description": "UpdateOne when many documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": { - "$gt": 1 - } - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - } - } - }, - { - "description": "UpdateOne when one document matches", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 1 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 1, - "modifiedCount": 1, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 12 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateOne when no documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - } - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 0 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - } - ] - } - } - }, - { - "description": "UpdateOne with upsert when no documents match", - "operation": { - "name": "updateOne", - "arguments": { - "filter": { - "_id": 4 - }, - "update": { - "$inc": { - "x": 1 - } - }, - "upsert": true - } - }, - "outcome": { - "result": { - "matchedCount": 0, - "modifiedCount": 0, - "upsertedCount": 1, - "upsertedId": 4 - }, - "collection": { - "data": [ - { - "_id": 1, - "x": 11 - }, - { - "_id": 2, - "x": 22 - }, - { - "_id": 3, - "x": 33 - }, - { - "_id": 4, - "x": 1 - } - ] - } - } - } - ] -}