Skip to content

Commit 00cfe6f

Browse files
authored
PYTHON-5773 Coverage increase for uri_parser_shared.py (#2758)
1 parent e42298b commit 00cfe6f

1 file changed

Lines changed: 143 additions & 4 deletions

File tree

test/test_uri_parser.py

Lines changed: 143 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@
3131
from pymongo.errors import ConfigurationError, InvalidURI
3232
from pymongo.synchronous.uri_parser import parse_uri
3333
from pymongo.uri_parser_shared import (
34+
_unquoted_percent,
35+
parse_host,
36+
parse_ipv6_literal_host,
3437
parse_userinfo,
3538
split_hosts,
3639
split_options,
@@ -462,7 +465,6 @@ def test_tlsinsecure_simple(self):
462465
"tlsInsecure": True,
463466
"tlsDisableOCSPEndpointCheck": True,
464467
}
465-
print(parse_uri(uri)["options"])
466468
self.assertEqual(res, parse_uri(uri)["options"])
467469

468470
def test_normalize_options(self):
@@ -555,9 +557,146 @@ def test_port_with_whitespace(self):
555557
with self.assertRaisesRegex(ValueError, r"Port contains whitespace character: '\\n'"):
556558
parse_uri("mongodb://localhost:27\n017")
557559

558-
def test_parse_uri_options_type(self):
559-
opts = parse_uri("mongodb://localhost:27017")["options"]
560-
self.assertIsInstance(opts, dict)
560+
def test_parse_uri_options_returns_dict(self):
561+
# Regression: PYTHON-5421 changed the return type from
562+
# _CaseInsensitiveDictionary to plain dict.
563+
self.assertIsInstance(parse_uri("mongodb://localhost:27017")["options"], dict)
564+
565+
def test_unquoted_percent(self):
566+
self.assertFalse(_unquoted_percent(""))
567+
self.assertFalse(_unquoted_percent("no_percent_here"))
568+
self.assertFalse(_unquoted_percent("%25")) # %25 decodes to literal "%"
569+
self.assertFalse(_unquoted_percent("%40")) # %40 decodes to "@"
570+
self.assertFalse(_unquoted_percent("user%40domain.com"))
571+
self.assertFalse(_unquoted_percent("%2B")) # %2B decodes to "+"
572+
self.assertFalse(_unquoted_percent("%E2%85%A8")) # multi-byte sequence
573+
self.assertFalse(_unquoted_percent("%2525")) # double-encoded: %25 -> %
574+
self.assertTrue(_unquoted_percent("%foo")) # 'o' is not a hex digit
575+
self.assertTrue(_unquoted_percent("50%off")) # 'o' is not a hex digit
576+
self.assertTrue(_unquoted_percent("100%")) # trailing bare %
577+
578+
def test_parse_ipv6_literal_host_direct(self):
579+
self.assertEqual(("::1", 27017), parse_ipv6_literal_host("[::1]", 27017))
580+
self.assertEqual(("::1", None), parse_ipv6_literal_host("[::1]", None))
581+
# IPv6 with explicit port returns port as a string (int conversion
582+
# happens later in parse_host).
583+
host, port = parse_ipv6_literal_host("[::1]:27018", 27017)
584+
self.assertEqual("::1", host)
585+
self.assertEqual("27018", port)
586+
full_ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
587+
host, port = parse_ipv6_literal_host(f"[{full_ipv6}]", 27017)
588+
self.assertEqual(full_ipv6, host)
589+
self.assertEqual(27017, port)
590+
self.assertRaises(ValueError, parse_ipv6_literal_host, "[::1", 27017)
591+
592+
def test_parse_host_case_normalization(self):
593+
self.assertEqual(("localhost", 27017), parse_host("LOCALHOST:27017"))
594+
self.assertEqual(("example.com", 27017), parse_host("Example.COM"))
595+
self.assertEqual(("example.com", 27017), parse_host("EXAMPLE.COM:27017"))
596+
self.assertEqual(("192.168.1.1", 27017), parse_host("192.168.1.1:27017"))
597+
self.assertEqual(("::1", 27017), parse_host("[::1]:27017"))
598+
599+
def test_parse_host_port_boundaries(self):
600+
self.assertEqual(("localhost", 1), parse_host("localhost:1"))
601+
self.assertEqual(("localhost", 65535), parse_host("localhost:65535"))
602+
self.assertRaises(ValueError, parse_host, "localhost:0")
603+
self.assertRaises(ValueError, parse_host, "localhost:65536")
604+
605+
def test_tls_option_conflicts(self):
606+
self.assertRaises(
607+
InvalidURI, split_options, "tlsInsecure=true&tlsAllowInvalidCertificates=true"
608+
)
609+
# The conflict is based on presence, not value.
610+
self.assertRaises(
611+
InvalidURI, split_options, "tlsInsecure=true&tlsAllowInvalidHostnames=false"
612+
)
613+
self.assertRaises(
614+
InvalidURI, split_options, "tlsInsecure=true&tlsDisableOCSPEndpointCheck=true"
615+
)
616+
self.assertRaises(
617+
InvalidURI,
618+
split_options,
619+
"tlsAllowInvalidCertificates=true&tlsDisableOCSPEndpointCheck=true",
620+
)
621+
self.assertRaises(InvalidURI, split_options, "ssl=true&tls=false")
622+
self.assertRaises(InvalidURI, split_options, "ssl=false&tls=true")
623+
self.assertEqual(split_options("ssl=true&tls=true"), {"tls": True})
624+
self.assertEqual(split_options("ssl=false&tls=false"), {"tls": False})
625+
626+
def test_split_options_mixed_delimiters(self):
627+
self.assertRaises(InvalidURI, split_options, "ssl=true&tls=true;appname=foo")
628+
self.assertRaises(InvalidURI, split_options, "appname=foo;ssl=true&tls=true")
629+
630+
def test_split_options_duplicate_warning(self):
631+
with warnings.catch_warnings(record=True) as w:
632+
warnings.simplefilter("always")
633+
split_options("appname=foo&appname=bar")
634+
self.assertEqual(1, len(w))
635+
self.assertIn("Duplicate URI option", str(w[0].message))
636+
637+
def test_split_options_empty_authsource(self):
638+
self.assertRaises(InvalidURI, split_options, "authSource=")
639+
640+
def test_check_options_conflicts(self):
641+
self.assertRaises(
642+
ConfigurationError,
643+
parse_uri,
644+
"mongodb://host1,host2/?directConnection=true",
645+
)
646+
self.assertRaises(
647+
ConfigurationError,
648+
parse_uri,
649+
"mongodb://host1,host2/?loadBalanced=true",
650+
)
651+
self.assertRaises(
652+
ConfigurationError,
653+
parse_uri,
654+
"mongodb://localhost/?directConnection=true&loadBalanced=true",
655+
)
656+
self.assertRaises(
657+
ConfigurationError,
658+
parse_uri,
659+
"mongodb://localhost/?loadBalanced=true&replicaSet=rs0",
660+
)
661+
662+
def test_validate_uri_edge_cases(self):
663+
self.assertRaises(InvalidURI, parse_uri, "mongodb://")
664+
self.assertRaises(
665+
ConfigurationError,
666+
parse_uri,
667+
"mongodb://localhost/?srvServiceName=myService",
668+
)
669+
self.assertRaises(
670+
ConfigurationError,
671+
parse_uri,
672+
"mongodb://localhost/?srvMaxHosts=1",
673+
)
674+
self.assertRaises(InvalidURI, parse_uri, "mongodb://localhost/%24db")
675+
self.assertRaises(InvalidURI, parse_uri, "mongodb://localhost/my%20db")
676+
677+
def test_validate_uri_srv_structure(self):
678+
with patch("pymongo.uri_parser_shared._have_dnspython", return_value=True):
679+
self.assertRaises(
680+
InvalidURI,
681+
parse_uri,
682+
"mongodb+srv://host1.example.com,host2.example.com",
683+
)
684+
self.assertRaises(
685+
InvalidURI,
686+
parse_uri,
687+
"mongodb+srv://host1.example.com:27017",
688+
)
689+
self.assertRaises(
690+
ConfigurationError,
691+
parse_uri,
692+
"mongodb+srv://host1.example.com/?directConnection=true",
693+
)
694+
with patch("pymongo.uri_parser_shared._have_dnspython", return_value=False):
695+
self.assertRaises(
696+
ConfigurationError,
697+
parse_uri,
698+
"mongodb+srv://host1.example.com",
699+
)
561700

562701

563702
if __name__ == "__main__":

0 commit comments

Comments
 (0)