PYTHON-959 - Connection string spec compliance.

This commit is contained in:
aherlihy 2015-08-06 13:14:31 -04:00
parent b9baa8ae8a
commit 193915e636
14 changed files with 1663 additions and 79 deletions

View File

@ -107,8 +107,6 @@ class ClientOptions(object):
def __init__(self, username, password, database, options):
self.__options = options
options = dict([validate(opt, val) for opt, val in iteritems(options)])
self.__codec_options = _parse_codec_options(options)
self.__credentials = _parse_credentials(
username, password, database, options)

View File

@ -16,11 +16,12 @@
"""Functions and classes common to multiple pymongo modules."""
import collections
import warnings
from bson.binary import (STANDARD, PYTHON_LEGACY,
JAVA_LEGACY, CSHARP_LEGACY)
from bson.codec_options import CodecOptions
from bson.py3compat import string_type, integer_types
from bson.py3compat import string_type, integer_types, iteritems
from pymongo.auth import MECHANISMS
from pymongo.errors import ConfigurationError
from pymongo.read_preferences import (read_pref_mode_from_name,
@ -74,6 +75,7 @@ COMMAND_NOT_FOUND_CODES = (59, 13390, None)
# Error codes to ignore if GridFS calls createIndex on a secondary
UNAUTHORIZED_CODES = (13, 16547, 16548)
def partition_node(node):
"""Split a host:port string into (host, int(port)) pair."""
host = node
@ -340,15 +342,16 @@ def validate_auth_mechanism_properties(option, value):
for opt in value.split(','):
try:
key, val = opt.split(':')
if key not in _MECHANISM_PROPS:
raise ValueError("%s is not a supported auth "
"mechanism property. Must be one of "
"%s." % (key, tuple(_MECHANISM_PROPS)))
props[key] = val
except ValueError:
raise ValueError("auth mechanism properties must be "
"key:value pairs like SERVICE_NAME:"
"mongodb, not %s." % (opt,))
if key not in _MECHANISM_PROPS:
raise ValueError("%s is not a supported auth "
"mechanism property. Must be one of "
"%s." % (key, tuple(_MECHANISM_PROPS)))
props[key] = val
return props
@ -456,6 +459,23 @@ def validate(option, value):
return lower, value
def get_validated_options(options):
"""Validate each entry in options and raise a warning if it is not valid.
Returns a copy of options with invalid entries removed
"""
validated_options = {}
for opt, value in iteritems(options):
lower = opt.lower()
try:
validator = VALIDATORS.get(lower, raise_config_error)
value = validator(opt, value)
except (ValueError, ConfigurationError) as exc:
warnings.warn(str(exc))
else:
validated_options[lower] = value
return validated_options
WRITE_CONCERN_OPTIONS = frozenset([
'w',
'wtimeout',
@ -516,4 +536,3 @@ class BaseObject(object):
The :attr:`read_preference` attribute is now read only.
"""
return self.__read_preference

View File

@ -300,7 +300,7 @@ class MongoClient(common.BaseObject):
for entity in host:
if "://" in entity:
if entity.startswith("mongodb://"):
res = uri_parser.parse_uri(entity, port, False)
res = uri_parser.parse_uri(entity, port, warn=True)
seeds.update(res["nodelist"])
username = res["username"] or username
password = res["password"] or password
@ -321,10 +321,14 @@ class MongoClient(common.BaseObject):
monitor_class = kwargs.pop('_monitor_class', None)
condition_class = kwargs.pop('_condition_class', None)
opts['document_class'] = document_class
opts['tz_aware'] = tz_aware
opts['connect'] = connect
opts.update(kwargs)
keyword_opts = kwargs
keyword_opts['document_class'] = document_class
keyword_opts['tz_aware'] = tz_aware
keyword_opts['connect'] = connect
# Validate all keyword options.
keyword_opts = dict(common.validate(k, v)
for k, v in keyword_opts.items())
opts.update(keyword_opts)
self.__options = options = ClientOptions(
username, password, dbase, opts)
@ -792,6 +796,9 @@ class MongoClient(common.BaseObject):
else:
return 'document_class=%s.%s' % (value.__module__,
value.__name__)
if "ms" in option:
return "%s='%s'" % (option, int(value * 1000))
return '%s=%r' % (option, value)
# Host first...

View File

@ -14,6 +14,7 @@
"""Tools to parse and validate a MongoDB URI."""
import warnings
from bson.py3compat import PY3, iteritems, string_type
@ -22,7 +23,7 @@ if PY3:
else:
from urllib import unquote_plus
from pymongo.common import validate as _validate
from pymongo.common import (validate as _validate, get_validated_options)
from pymongo.errors import ConfigurationError, InvalidURI
@ -129,6 +130,8 @@ def parse_host(entity, default_port=DEFAULT_PORT):
port = default_port
if entity[0] == '[':
host, port = parse_ipv6_literal_host(entity, default_port)
elif entity.endswith(".sock"):
return entity, default_port
elif entity.find(':') != -1:
if entity.count(':') > 1:
raise ValueError("Reserved characters such as ':' must be "
@ -137,8 +140,9 @@ def parse_host(entity, default_port=DEFAULT_PORT):
"and ']' according to RFC 2732.")
host, port = host.split(':', 1)
if isinstance(port, string_type):
if not port.isdigit():
raise ValueError("Port number must be an integer.")
if not port.isdigit() or int(port) > 65535 or int(port) <= 0:
raise ValueError("Port must be an integer between 0 and 65535: %s"
% (port,))
port = int(port)
# Normalize hostname to lowercase, since DNS is case-insensitive:
@ -148,15 +152,23 @@ def parse_host(entity, default_port=DEFAULT_PORT):
return host.lower(), port
def validate_options(opts):
def validate_options(opts, warn=False):
"""Validates and normalizes options passed in a MongoDB URI.
Returns a new dictionary of validated and normalized options.
Returns a new dictionary of validated and normalized options. If warn is
False then errors will be thrown for invalid options, otherwise they will
be ignored and a warning will be issued.
:Parameters:
- `opts`: A dict of MongoDB URI options.
- `warn` (optional): If ``True`` then warnigns will be logged and
invalid options will be ignored. Otherwise invalid options will
cause errors.
"""
return dict([_validate(opt, val) for opt, val in iteritems(opts)])
if warn:
return get_validated_options(opts)
else:
return dict([_validate(opt, val) for opt, val in iteritems(opts)])
def _parse_options(opts, delim):
@ -172,11 +184,21 @@ def _parse_options(opts, delim):
# str(option) to ensure that a unicode URI results in plain 'str'
# option names. 'normalized' is then suitable to be passed as
# kwargs in all Python versions.
options[str(key)] = val
if str(key) in options:
warnings.warn("Duplicate URI option %s" % (str(key),))
options[str(key)] = unquote_plus(val)
# Special case for deprecated options
if "wtimeout" in options:
if "wtimeoutMS" in options:
options.pop("wtimeout")
warnings.warn("Option wtimeout is deprecated, use 'wtimeoutMS'"
" instead")
return options
def split_options(opts, validate=True):
def split_options(opts, validate=True, warn=False):
"""Takes the options portion of a MongoDB URI, validates each option
and returns the options in a dictionary.
@ -202,7 +224,7 @@ def split_options(opts, validate=True):
raise InvalidURI("MongoDB URI options are key=value pairs.")
if validate:
return validate_options(options)
return validate_options(options, warn)
return options
@ -232,7 +254,7 @@ def split_hosts(hosts, default_port=DEFAULT_PORT):
return nodes
def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
def parse_uri(uri, default_port=DEFAULT_PORT, validate=True, warn=False):
"""Parse and validate a MongoDB URI.
Returns a dict of the form::
@ -252,6 +274,13 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
for a host in the URI.
- `validate`: If ``True`` (the default), validate and normalize all
options.
- `warn` (optional): When validating, if ``True`` then will warn
the user then ignore any invalid options or values. If ``False``,
validation will error when options are unsupported or values are
invalid.
.. versionchanged:: 3.1
``warn`` added so invalid options can be ignored.
"""
if not uri.startswith(SCHEME):
raise InvalidURI("Invalid URI scheme: URI "
@ -262,7 +291,6 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
if not scheme_free:
raise InvalidURI("Must provide at least one hostname or IP.")
nodes = None
user = None
passwd = None
dbase = None
@ -272,11 +300,14 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
# Check for unix domain sockets in the uri
if '.sock' in scheme_free:
host_part, _, path_part = _rpartition(scheme_free, '/')
try:
parse_uri('%s%s' % (SCHEME, host_part))
except (ConfigurationError, InvalidURI):
host_part = scheme_free
if not host_part:
host_part = path_part
path_part = ""
if '/' in host_part:
raise InvalidURI("Any '/' in a unix domain socket must be"
" URL encoded: %s" % host_part)
host_part = unquote_plus(host_part)
path_part = unquote_plus(path_part)
else:
host_part, _, path_part = _partition(scheme_free, '/')
@ -302,7 +333,12 @@ def parse_uri(uri, default_port=DEFAULT_PORT, validate=True):
dbase, collection = dbase.split('.', 1)
if opts:
options = split_options(opts, validate)
options = split_options(opts, validate, warn)
if dbase is not None:
dbase = unquote_plus(dbase)
if collection is not None:
collection = unquote_plus(collection)
return {
'nodelist': nodes,

View File

@ -0,0 +1,220 @@
{
"tests": [
{
"auth": null,
"description": "Empty string",
"hosts": null,
"options": null,
"uri": "",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid scheme",
"hosts": null,
"options": null,
"uri": "mongo://localhost:27017",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Missing host",
"hosts": null,
"options": null,
"uri": "mongodb://",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Double colon in host identifier",
"hosts": null,
"options": null,
"uri": "mongodb://localhost::27017",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Double colon in host identifier and trailing slash",
"hosts": null,
"options": null,
"uri": "mongodb://localhost::27017/",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Double colon in host identifier with missing host and port",
"hosts": null,
"options": null,
"uri": "mongodb://::",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Double colon in host identifier with missing port",
"hosts": null,
"options": null,
"uri": "mongodb://localhost,localhost::",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Double colon in host identifier and second host",
"hosts": null,
"options": null,
"uri": "mongodb://localhost::27017,abc",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (negative number) with hostname",
"hosts": null,
"options": null,
"uri": "mongodb://localhost:-1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (zero) with hostname",
"hosts": null,
"options": null,
"uri": "mongodb://localhost:0/",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (positive number) with hostname",
"hosts": null,
"options": null,
"uri": "mongodb://localhost:65536",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (positive number) with hostname and trailing slash",
"hosts": null,
"options": null,
"uri": "mongodb://localhost:65536/",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (non-numeric string) with hostname",
"hosts": null,
"options": null,
"uri": "mongodb://localhost:foo",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (negative number) with IP literal",
"hosts": null,
"options": null,
"uri": "mongodb://[::1]:-1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (zero) with IP literal",
"hosts": null,
"options": null,
"uri": "mongodb://[::1]:0/",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (positive number) with IP literal",
"hosts": null,
"options": null,
"uri": "mongodb://[::1]:65536",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (positive number) with IP literal and trailing slash",
"hosts": null,
"options": null,
"uri": "mongodb://[::1]:65536/",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Invalid port (non-numeric string) with IP literal",
"hosts": null,
"options": null,
"uri": "mongodb://[::1]:foo",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Missing delimiting slash between hosts and options",
"hosts": null,
"options": null,
"uri": "mongodb://example.com?w=1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Incomplete key value pair for option",
"hosts": null,
"options": null,
"uri": "mongodb://example.com/?w",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Username with password containing an unescaped colon",
"hosts": null,
"options": null,
"uri": "mongodb://alice:foo:bar@127.0.0.1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Username containing an unescaped at-sign",
"hosts": null,
"options": null,
"uri": "mongodb://alice@@127.0.0.1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Username with password containing an unescaped at-sign",
"hosts": null,
"options": null,
"uri": "mongodb://alice@foo:bar@127.0.0.1",
"valid": false,
"warning": null
},
{
"auth": null,
"description": "Host with unescaped slash",
"hosts": null,
"options": null,
"uri": "mongodb:///tmp/mongodb-27017.sock/",
"valid": false,
"warning": null
}
]
}

View File

@ -0,0 +1,330 @@
{
"tests": [
{
"auth": {
"db": null,
"password": "foo",
"username": "alice"
},
"description": "User info for single IPv4 host without database",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://alice:foo@127.0.0.1",
"valid": true,
"warning": false
},
{
"auth": {
"db": "test",
"password": "foo",
"username": "alice"
},
"description": "User info for single IPv4 host with database",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://alice:foo@127.0.0.1/test",
"valid": true,
"warning": false
},
{
"auth": {
"db": "t\u0000est",
"password": "f\u0000oo",
"username": "a\u0000lice"
},
"description": "User info for single IPv4 host with database (escaped null bytes)",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://a%00lice:f%00oo@127.0.0.1/t%00est",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": "bar",
"username": "bob"
},
"description": "User info for single IP literal host without database",
"hosts": [
{
"host": "::1",
"port": 27018,
"type": "ip_literal"
}
],
"options": null,
"uri": "mongodb://bob:bar@[::1]:27018",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "bar",
"username": "bob"
},
"description": "User info for single IP literal host with database",
"hosts": [
{
"host": "::1",
"port": 27018,
"type": "ip_literal"
}
],
"options": null,
"uri": "mongodb://bob:bar@[::1]:27018/admin",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": "baz",
"username": "eve"
},
"description": "User info for single hostname without database",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://eve:baz@example.com",
"valid": true,
"warning": false
},
{
"auth": {
"db": "db2",
"password": "baz",
"username": "eve"
},
"description": "User info for single hostname with database",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://eve:baz@example.com/db2",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": "secret",
"username": "alice"
},
"description": "User info for multiple hosts without database",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
},
{
"host": "example.com",
"port": 27018,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://alice:secret@127.0.0.1,example.com:27018",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "secret",
"username": "alice"
},
"description": "User info for multiple hosts with database",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
},
{
"host": "::1",
"port": 27019,
"type": "ip_literal"
}
],
"options": null,
"uri": "mongodb://alice:secret@example.com,[::1]:27019/admin",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": null,
"username": "alice"
},
"description": "Username without password",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://alice@127.0.0.1",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": "",
"username": "alice"
},
"description": "Username with empty password",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://alice:@127.0.0.1",
"valid": true,
"warning": false
},
{
"auth": {
"db": "my=db",
"password": null,
"username": "@l:ce"
},
"description": "Escaped username and database without password",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://%40l%3Ace@example.com/my%3Ddb",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin?",
"password": "f:zzb@zz",
"username": "$am"
},
"description": "Escaped user info and database (MONGODB-CR)",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": {
"authmechanism": "MONGODB-CR"
},
"uri": "mongodb://%24am:f%3Azzb%40zz@127.0.0.1/admin%3F?authMechanism=MONGODB-CR",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": null,
"username": "CN=myName,OU=myOrgUnit,O=myOrg,L=myLocality,ST=myState,C=myCountry"
},
"description": "Escaped username (MONGODB-X509)",
"hosts": [
{
"host": "localhost",
"port": null,
"type": "hostname"
}
],
"options": {
"authmechanism": "MONGODB-X509"
},
"uri": "mongodb://CN%3DmyName%2COU%3DmyOrgUnit%2CO%3DmyOrg%2CL%3DmyLocality%2CST%3DmyState%2CC%3DmyCountry@localhost/?authMechanism=MONGODB-X509",
"valid": true,
"warning": false
},
{
"auth": {
"db": null,
"password": "secret",
"username": "user@EXAMPLE.COM"
},
"description": "Escaped username (GSSAPI)",
"hosts": [
{
"host": "localhost",
"port": null,
"type": "hostname"
}
],
"options": {
"authmechanism": "GSSAPI",
"authmechanismproperties": {
"CANONICALIZE_HOST_NAME": true,
"SERVICE_NAME": "other"
}
},
"uri": "mongodb://user%40EXAMPLE.COM:secret@localhost/?authMechanismProperties=SERVICE_NAME:other,CANONICALIZE_HOST_NAME:true&authMechanism=GSSAPI",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "secret",
"username": "alice"
},
"description": "At-signs in options aren't part of the userinfo",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": {
"replicaset": "my@replicaset"
},
"uri": "mongodb://alice:secret@example.com/admin?replicaset=my@replicaset",
"valid": true,
"warning": false
}
]
}

View File

@ -0,0 +1,154 @@
{
"tests": [
{
"auth": null,
"description": "Single IPv4 host without port",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://127.0.0.1",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single IPv4 host with port",
"hosts": [
{
"host": "127.0.0.1",
"port": 27018,
"type": "ipv4"
}
],
"options": null,
"uri": "mongodb://127.0.0.1:27018",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single IP literal host without port",
"hosts": [
{
"host": "::1",
"port": null,
"type": "ip_literal"
}
],
"options": null,
"uri": "mongodb://[::1]",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single IP literal host with port",
"hosts": [
{
"host": "::1",
"port": 27019,
"type": "ip_literal"
}
],
"options": null,
"uri": "mongodb://[::1]:27019",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single hostname without port",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://example.com",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single hostname with port",
"hosts": [
{
"host": "example.com",
"port": 27020,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://example.com:27020",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Single hostname (resembling IPv4) without port",
"hosts": [
{
"host": "256.0.0.1",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://256.0.0.1",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple hosts (mixed formats)",
"hosts": [
{
"host": "127.0.0.1",
"port": null,
"type": "ipv4"
},
{
"host": "::1",
"port": 27018,
"type": "ip_literal"
},
{
"host": "example.com",
"port": 27019,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://127.0.0.1,[::1]:27018,example.com:27019",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "UTF-8 hosts",
"hosts": [
{
"host": "b\u00fccher.example.com",
"port": null,
"type": "hostname"
},
{
"host": "uml\u00e4ut.example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://b\u00fccher.example.com,uml\u00e4ut.example.com/",
"valid": true,
"warning": false
}
]
}

View File

@ -0,0 +1,42 @@
{
"tests": [
{
"auth": {
"db": "admin",
"password": "secret",
"username": "alice"
},
"description": "Option names are normalized to lowercase",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": {
"authmechanism": "MONGODB-CR"
},
"uri": "mongodb://alice:secret@example.com/admin?AUTHMechanism=MONGODB-CR",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Option key and value (escaped null bytes)",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": {
"replicaset": "my\u0000rs"
},
"uri": "mongodb://example.com/?replicaSet=my%00rs",
"valid": true,
"warning": false
}
]
}

View File

@ -0,0 +1,251 @@
{
"tests": [
{
"auth": null,
"description": "Unix domain socket (absolute path with trailing slash)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock/",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket (absolute path without trailing slash)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket (absolute path with spaces in path)",
"hosts": [
{
"host": "/tmp/ /mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2F %2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple Unix domain sockets (absolute paths)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple hosts (absolute path and ipv4)",
"hosts": [
{
"host": "127.0.0.1",
"port": 27017,
"type": "ipv4"
},
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://127.0.0.1:27017,%2Ftmp%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple hosts (absolute path and hostname resembling relative path)",
"hosts": [
{
"host": "mongodb-27017.sock",
"port": null,
"type": "hostname"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://mongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "foo",
"username": "alice"
},
"description": "Unix domain socket with auth database (absolute path)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://alice:foo@%2Ftmp%2Fmongodb-27017.sock/admin",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket with path resembling socket file (absolute path with trailing slash)",
"hosts": [
{
"host": "/tmp/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket with path resembling socket file (absolute path without trailing slash)",
"hosts": [
{
"host": "/tmp/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "bar",
"username": "bob"
},
"description": "Unix domain socket with path resembling socket file and auth (absolute path)",
"hosts": [
{
"host": "/tmp/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://bob:bar@%2Ftmp%2Fpath.to.sock%2Fmongodb-27017.sock/admin",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin.sock",
"password": null,
"username": null
},
"description": "Multiple Unix domain sockets and auth DB resembling a socket (absolute path)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin.shoe",
"password": null,
"username": null
},
"description": "Multiple Unix domain sockets with auth DB resembling a path (absolute path)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin.shoe",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "bar",
"username": "bob"
},
"description": "Multiple Unix domain sockets with auth and query string (absolute path)",
"hosts": [
{
"host": "/tmp/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": {
"w": 1
},
"uri": "mongodb://bob:bar@%2Ftmp%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock/admin?w=1",
"valid": true,
"warning": false
}
]
}

View File

@ -0,0 +1,271 @@
{
"tests": [
{
"auth": null,
"description": "Unix domain socket (relative path with trailing slash)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock/",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket (relative path without trailing slash)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket (relative path with spaces)",
"hosts": [
{
"host": "rel/ /mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2F %2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple Unix domain sockets (relative paths)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "rel/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple Unix domain sockets (relative and absolute paths)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "/tmp/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,%2Ftmp%2Fmongodb-27018.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple hosts (relative path and ipv4)",
"hosts": [
{
"host": "127.0.0.1",
"port": 27017,
"type": "ipv4"
},
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://127.0.0.1:27017,rel%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Multiple hosts (relative path and hostname resembling relative path)",
"hosts": [
{
"host": "mongodb-27017.sock",
"port": null,
"type": "hostname"
},
{
"host": "rel/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://mongodb-27017.sock,rel%2Fmongodb-27018.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "foo",
"username": "alice"
},
"description": "Unix domain socket with auth database (relative path)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://alice:foo@rel%2Fmongodb-27017.sock/admin",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket with path resembling socket file (relative path with trailing slash)",
"hosts": [
{
"host": "rel/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock/",
"valid": true,
"warning": false
},
{
"auth": null,
"description": "Unix domain socket with path resembling socket file (relative path without trailing slash)",
"hosts": [
{
"host": "rel/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fpath.to.sock%2Fmongodb-27017.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "bar",
"username": "bob"
},
"description": "Unix domain socket with path resembling socket file and auth (relative path)",
"hosts": [
{
"host": "rel/path.to.sock/mongodb-27017.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://bob:bar@rel%2Fpath.to.sock%2Fmongodb-27017.sock/admin",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin.sock",
"password": null,
"username": null
},
"description": "Multiple Unix domain sockets and auth DB resembling a socket (relative path)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "rel/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.sock",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin.shoe",
"password": null,
"username": null
},
"description": "Multiple Unix domain sockets with auth DB resembling a path (relative path)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "rel/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": null,
"uri": "mongodb://rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin.shoe",
"valid": true,
"warning": false
},
{
"auth": {
"db": "admin",
"password": "bar",
"username": "bob"
},
"description": "Multiple Unix domain sockets with auth and query string (relative path)",
"hosts": [
{
"host": "rel/mongodb-27017.sock",
"port": null,
"type": "unix"
},
{
"host": "rel/mongodb-27018.sock",
"port": null,
"type": "unix"
}
],
"options": {
"w": 1
},
"uri": "mongodb://bob:bar@rel%2Fmongodb-27017.sock,rel%2Fmongodb-27018.sock/admin?w=1",
"valid": true,
"warning": false
}
]
}

View File

@ -0,0 +1,68 @@
{
"tests": [
{
"auth": null,
"description": "Unrecognized option keys are ignored",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://example.com/?foo=bar",
"valid": true,
"warning": true
},
{
"auth": null,
"description": "Unsupported option values are ignored",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": null,
"uri": "mongodb://example.com/?fsync=ifPossible",
"valid": true,
"warning": true
},
{
"auth": null,
"description": "Repeated option keys",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": {
"replicaset": "test"
},
"uri": "mongodb://example.com/?replicaSet=test&replicaSet=test",
"valid": true,
"warning": true
},
{
"auth": null,
"description": "Deprecated (or unknown) options are ignored if replacement exists",
"hosts": [
{
"host": "example.com",
"port": null,
"type": "hostname"
}
],
"options": {
"wtimeoutms": 10
},
"uri": "mongodb://example.com/?wtimeout=5&wtimeoutMS=10",
"valid": true,
"warning": true
}
]
}

View File

@ -244,7 +244,7 @@ class TestClient(IntegrationTest):
self.assertFalse(client_context.rs_or_standalone_client != c)
def test_host_w_port(self):
with self.assertRaises(AutoReconnect):
with self.assertRaises(ValueError):
connected(MongoClient("%s:1234567" % host, connectTimeoutMS=1,
serverSelectionTimeoutMS=10))
@ -264,8 +264,8 @@ class TestClient(IntegrationTest):
"tz_aware=False, "
"connect=False, ",
the_repr)
self.assertIn("connectTimeoutMS='12345'", the_repr)
self.assertIn("replicaSet=", the_repr)
self.assertIn("connecttimeoutms='12345'", the_repr)
self.assertIn("replicaset=", the_repr)
self.assertEqual(eval(the_repr), client)
@ -400,13 +400,14 @@ class TestClient(IntegrationTest):
raise SkipTest("UNIX-sockets are not supported on this system")
mongodb_socket = '/tmp/mongodb-27017.sock'
encoded_socket = '%2Ftmp%2Fmongodb-27017.sock'
if not os.access(mongodb_socket, os.R_OK):
raise SkipTest("Socket file is not accessible")
if client_context.auth_enabled:
uri = "mongodb://%s:%s@%s" % (db_user, db_pwd, mongodb_socket)
uri = "mongodb://%s:%s@%s" % (db_user, db_pwd, encoded_socket)
else:
uri = "mongodb://%s" % mongodb_socket
uri = "mongodb://%s" % encoded_socket
# Confirm we can do operations via the socket.
client = MongoClient(uri)
@ -417,7 +418,7 @@ class TestClient(IntegrationTest):
# Confirm it fails with a missing socket.
self.assertRaises(
ConnectionFailure,
connected, MongoClient("mongodb:///tmp/non-existent.sock",
connected, MongoClient("mongodb://%2Ftmp%2Fnon-existent.sock",
serverSelectionTimeoutMS=100))
def test_fork(self):
@ -482,7 +483,6 @@ class TestClient(IntegrationTest):
self.assertEqual(SON, c.codec_options.document_class)
self.assertTrue(isinstance(db.test.find_one(), SON))
def test_timeouts(self):
client = rs_or_single_client(connectTimeoutMS=10500)
self.assertEqual(10.5, get_pool(client).opts.connect_timeout)
@ -547,12 +547,14 @@ class TestClient(IntegrationTest):
'mongodb://localhost/?serverSelectionTimeoutMS=0', connect=False)
self.assertAlmostEqual(0, client.server_selection_timeout)
self.assertRaises(ValueError, MongoClient,
'mongodb://localhost/?serverSelectionTimeoutMS=-1',
connect=False)
self.assertRaises(ValueError, MongoClient,
'mongodb://localhost/?serverSelectionTimeoutMS=',
connect=False)
# Test invalid timeout in URI ignored and set to default.
client = MongoClient(
'mongodb://localhost/?serverSelectionTimeoutMS=-1', connect=False)
self.assertAlmostEqual(30, client.server_selection_timeout)
client = MongoClient(
'mongodb://localhost/?serverSelectionTimeoutMS=', connect=False)
self.assertAlmostEqual(30, client.server_selection_timeout)
def test_waitQueueTimeoutMS(self):
client = rs_or_single_client(waitQueueTimeoutMS=2000)

View File

@ -16,6 +16,7 @@
import copy
import sys
import warnings
sys.path[0:0] = [""]
@ -94,42 +95,94 @@ class TestURI(unittest.TestCase):
def test_split_options(self):
self.assertRaises(ConfigurationError, split_options, 'foo')
self.assertRaises(ConfigurationError, split_options, 'foo=bar')
self.assertRaises(ConfigurationError, split_options, 'foo=bar;foo')
self.assertTrue(split_options('ssl=true'))
self.assertTrue(split_options('ssl_match_hostname=true'))
# Test Invalid URI options that should throw warnings.
with warnings.catch_warnings():
warnings.filterwarnings('error')
self.assertRaises(Warning, split_options,
'foo=bar', warn=True)
self.assertRaises(Warning, split_options,
'socketTimeoutMS=foo', warn=True)
self.assertRaises(Warning, split_options,
'socketTimeoutMS=0.0', warn=True)
self.assertRaises(Warning, split_options,
'connectTimeoutMS=foo', warn=True)
self.assertRaises(Warning, split_options,
'connectTimeoutMS=0.0', warn=True)
self.assertRaises(Warning, split_options,
'connectTimeoutMS=1e100000', warn=True)
self.assertRaises(Warning, split_options,
'connectTimeoutMS=-1e100000', warn=True)
self.assertRaises(Warning, split_options,
'ssl=foo', warn=True)
self.assertRaises(Warning, split_options,
'ssl_match_hostname=foo', warn=True)
# On most platforms float('inf') and float('-inf') represent
# +/- infinity, although on Python 2.4 and 2.5 on Windows those
# expressions are invalid
if not (sys.platform == "win32" and sys.version_info <= (2, 5)):
self.assertRaises(Warning, split_options,
'connectTimeoutMS=inf', warn=True)
self.assertRaises(Warning, split_options,
'connectTimeoutMS=-inf', warn=True)
self.assertRaises(Warning, split_options, 'wtimeoutms=foo',
warn=True)
self.assertRaises(Warning, split_options, 'wtimeoutms=5.5',
warn=True)
self.assertRaises(Warning, split_options, 'fsync=foo',
warn=True)
self.assertRaises(Warning, split_options, 'fsync=5.5',
warn=True)
self.assertRaises(Warning,
split_options, 'authMechanism=foo',
warn=True)
# Test invalid options with warn=False.
self.assertRaises(ConfigurationError, split_options, 'foo=bar')
self.assertRaises(ValueError, split_options, 'socketTimeoutMS=foo')
self.assertRaises(ValueError, split_options, 'socketTimeoutMS=0.0')
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=foo')
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=0.0')
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=1e100000')
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=-1e100000')
self.assertRaises(ValueError, split_options,
'connectTimeoutMS=1e100000')
self.assertRaises(ValueError, split_options,
'connectTimeoutMS=-1e100000')
self.assertRaises(ValueError, split_options, 'ssl=foo')
self.assertTrue(split_options('ssl=true'))
self.assertRaises(ValueError, split_options, 'ssl_match_hostname=foo')
self.assertTrue(split_options('ssl_match_hostname=true'))
# On most platforms float('inf') and float('-inf') represent
# +/- infinity, although on Python 2.4 and 2.5 on Windows those
# expressions are invalid
if not (sys.platform == "win32" and sys.version_info <= (2, 5)):
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=inf')
self.assertRaises(ValueError, split_options, 'connectTimeoutMS=-inf')
self.assertRaises(ValueError, split_options,
'connectTimeoutMS=inf')
self.assertRaises(ValueError, split_options,
'connectTimeoutMS=-inf')
self.assertRaises(ValueError, split_options, 'wtimeoutms=foo')
self.assertRaises(ValueError, split_options, 'wtimeoutms=5.5')
self.assertRaises(ValueError, split_options, 'fsync=foo')
self.assertRaises(ValueError, split_options, 'fsync=5.5')
self.assertRaises(ValueError,
split_options, 'authMechanism=foo')
# Test splitting options works when valid.
self.assertTrue(split_options('socketTimeoutMS=300'))
self.assertTrue(split_options('connectTimeoutMS=300'))
self.assertEqual({'sockettimeoutms': 0.3}, split_options('socketTimeoutMS=300'))
self.assertEqual({'sockettimeoutms': 0.0001}, split_options('socketTimeoutMS=0.1'))
self.assertEqual({'connecttimeoutms': 0.3}, split_options('connectTimeoutMS=300'))
self.assertEqual({'connecttimeoutms': 0.0001}, split_options('connectTimeoutMS=0.1'))
self.assertEqual({'sockettimeoutms': 0.3},
split_options('socketTimeoutMS=300'))
self.assertEqual({'sockettimeoutms': 0.0001},
split_options('socketTimeoutMS=0.1'))
self.assertEqual({'connecttimeoutms': 0.3},
split_options('connectTimeoutMS=300'))
self.assertEqual({'connecttimeoutms': 0.0001},
split_options('connectTimeoutMS=0.1'))
self.assertTrue(split_options('connectTimeoutMS=300'))
self.assertTrue(isinstance(split_options('w=5')['w'], int))
self.assertTrue(isinstance(split_options('w=5.5')['w'], string_type))
self.assertTrue(split_options('w=foo'))
self.assertTrue(split_options('w=majority'))
self.assertRaises(ValueError, split_options, 'wtimeoutms=foo')
self.assertRaises(ValueError, split_options, 'wtimeoutms=5.5')
self.assertTrue(split_options('wtimeoutms=500'))
self.assertRaises(ValueError, split_options, 'fsync=foo')
self.assertRaises(ValueError, split_options, 'fsync=5.5')
self.assertEqual({'fsync': True}, split_options('fsync=true'))
self.assertEqual({'fsync': False}, split_options('fsync=false'))
self.assertEqual({'authmechanism': 'GSSAPI'},
@ -138,9 +191,8 @@ class TestURI(unittest.TestCase):
split_options('authMechanism=MONGODB-CR'))
self.assertEqual({'authmechanism': 'SCRAM-SHA-1'},
split_options('authMechanism=SCRAM-SHA-1'))
self.assertRaises(ValueError,
split_options, 'authMechanism=foo')
self.assertEqual({'authsource': 'foobar'}, split_options('authSource=foobar'))
self.assertEqual({'authsource': 'foobar'},
split_options('authSource=foobar'))
self.assertEqual({'maxpoolsize': 50}, split_options('maxpoolsize=50'))
def test_parse_uri(self):
@ -204,16 +256,17 @@ class TestURI(unittest.TestCase):
parse_uri("mongodb://example1.com:27017,example2.com"
":27017/test.yield_historical.in"))
res = copy.deepcopy(orig)
res['nodelist'] = [("/tmp/mongodb-27017.sock", None)]
self.assertEqual(res, parse_uri("mongodb:///tmp/mongodb-27017.sock"))
# Test socket path without escaped characters.
self.assertRaises(InvalidURI, parse_uri,
"mongodb:///tmp/mongodb-27017.sock")
# Test with escaped characters.
res = copy.deepcopy(orig)
res['nodelist'] = [("example2.com", 27017),
("/tmp/mongodb-27017.sock", None)]
self.assertEqual(res,
parse_uri("mongodb://example2.com,"
"/tmp/mongodb-27017.sock"))
"%2Ftmp%2Fmongodb-27017.sock"))
res = copy.deepcopy(orig)
res['nodelist'] = [("shoe.sock.pants.co.uk", 27017),
@ -221,14 +274,14 @@ class TestURI(unittest.TestCase):
res['database'] = "nethers_db"
self.assertEqual(res,
parse_uri("mongodb://shoe.sock.pants.co.uk,"
"/tmp/mongodb-27017.sock/nethers_db"))
"%2Ftmp%2Fmongodb-27017.sock/nethers_db"))
res = copy.deepcopy(orig)
res['nodelist'] = [("/tmp/mongodb-27017.sock", None),
("example2.com", 27017)]
res.update({'database': 'test', 'collection': 'yield_historical.in'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock,"
parse_uri("mongodb://%2Ftmp%2Fmongodb-27017.sock,"
"example2.com:27017"
"/test.yield_historical.in"))
@ -237,9 +290,9 @@ class TestURI(unittest.TestCase):
("example2.com", 27017)]
res.update({'database': 'test', 'collection': 'yield_historical.sock'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock,"
"example2.com:27017"
"/test.yield_historical.sock"))
parse_uri("mongodb://%2Ftmp%2Fmongodb-27017.sock,"
"example2.com:27017/test.yield_historical"
".sock"))
res = copy.deepcopy(orig)
res['nodelist'] = [("example2.com", 27017)]
@ -252,7 +305,7 @@ class TestURI(unittest.TestCase):
res['nodelist'] = [("/tmp/mongodb-27017.sock", None)]
res.update({'database': 'test', 'collection': 'mongodb-27017.sock'})
self.assertEqual(res,
parse_uri("mongodb:///tmp/mongodb-27017.sock"
parse_uri("mongodb://%2Ftmp%2Fmongodb-27017.sock"
"/test.mongodb-27017.sock"))
res = copy.deepcopy(orig)
@ -261,8 +314,8 @@ class TestURI(unittest.TestCase):
("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 27018),
("192.168.0.212", 27019),
("localhost", 27018)]
self.assertEqual(res, parse_uri("mongodb:///tmp/mongodb-27020.sock,"
"[::1]:27017,[2001:0db8:"
self.assertEqual(res, parse_uri("mongodb://%2Ftmp%2Fmongodb-27020.sock"
",[::1]:27017,[2001:0db8:"
"85a3:0000:0000:8a2e:0370:7334],"
"192.168.0.212:27019,localhost",
27018))
@ -276,8 +329,8 @@ class TestURI(unittest.TestCase):
res = copy.deepcopy(orig)
res['options'] = {'readpreference': ReadPreference.SECONDARY.mode}
self.assertEqual(res,
parse_uri("mongodb://localhost/?readPreference=secondary"))
self.assertEqual(res, parse_uri(
"mongodb://localhost/?readPreference=secondary"))
# Various authentication tests
res = copy.deepcopy(orig)
@ -371,6 +424,12 @@ class TestURI(unittest.TestCase):
"@localhost/foo?uuidrepresentation="
"javaLegacy"))
with warnings.catch_warnings():
warnings.filterwarnings('error')
self.assertRaises(Warning, parse_uri,
"mongodb://user%40domain.com:password"
"@localhost/foo?uuidrepresentation=notAnOption",
warn=True)
self.assertRaises(ValueError, parse_uri,
"mongodb://user%40domain.com:password"
"@localhost/foo?uuidrepresentation=notAnOption")

127
test/test_uri_spec.py Normal file
View File

@ -0,0 +1,127 @@
# Copyright 2011-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.
"""Test the pymongo uri_parser module is up to spec."""
import json
import os
import sys
import warnings
sys.path[0:0] = [""]
from pymongo.uri_parser import parse_uri
from test import unittest
# Location of JSON test specifications.
_TEST_PATH = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
os.path.join('connection_string', 'test'))
class TestAllScenarios(unittest.TestCase):
pass
def create_test(scenario_def):
def run_scenario(self):
self.assertTrue(scenario_def['tests'], "tests cannot be empty")
for test in scenario_def['tests']:
dsc = test['description']
warned = False
error = False
with warnings.catch_warnings():
warnings.filterwarnings('error')
try:
options = parse_uri(test['uri'], warn=True)
except Warning:
warned = True
except Exception:
error = True
self.assertEqual(not error, test['valid'],
"Test failure '%s'" % dsc)
if test.get("warning", False):
self.assertTrue(warned,
"Expected warning for test '%s'"
% (dsc,))
# Redo in the case there were warnings that were not expected.
if warned:
options = parse_uri(test['uri'], warn=True)
# Compare hosts and port.
if test['hosts'] is not None:
self.assertEqual(
len(test['hosts']), len(options['nodelist']),
"Incorrect number of hosts parsed from URI")
for exp, actual in zip(test['hosts'],
options['nodelist']):
self.assertEqual(exp['host'], actual[0],
"Expected host %s but got %s"
% (exp['host'], actual[0]))
if exp['port'] is not None:
self.assertEqual(exp['port'], actual[1],
"Expected port %s but got %s"
% (exp['port'], actual))
# Compare auth options.
auth = test['auth']
if auth is not None:
auth['database'] = auth.pop('db') # db == database
# Special case for PyMongo's collection parsing.
if options.get('collection') is not None:
options['database'] += "." + options['collection']
for elm in auth:
if auth[elm] is not None:
self.assertEqual(auth[elm], options[elm],
"Expected %s but got %s"
% (auth[elm], options[elm]))
# Compare URI options.
if test['options'] is not None:
for opt in test['options']:
if options.get(opt) is not None:
self.assertEqual(
options[opt], test['options'][opt],
"For option %s expected %s but got %s"
% (opt, options[opt],
test['options'][opt]))
return run_scenario
def create_tests():
for dirpath, _, filenames in os.walk(_TEST_PATH):
dirname = os.path.split(dirpath)
dirname = os.path.split(dirname[-2])[-1] + '_' + dirname[-1]
for filename in filenames:
with open(os.path.join(dirpath, filename)) as scenario_stream:
scenario_def = json.load(scenario_stream)
# Construct test from scenario.
new_test = create_test(scenario_def)
test_name = 'test_%s_%s' % (
dirname, os.path.splitext(filename)[0])
new_test.__name__ = test_name
setattr(TestAllScenarios, new_test.__name__, new_test)
create_tests()
if __name__ == "__main__":
unittest.main()