def test_close(self): proc = psutil.Process() ip_addresses = { info[4][0] for info in socket.getaddrinfo('example.com', 80, socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) } self.assertGreater(len(ip_addresses), 0) protocol = Protocol( config=Configuration(service_endpoint='http://example.com', credentials=Credentials('A', 'B'), auth_type=NOAUTH, version=Version(Build(15, 1)), retry_policy=FailFast())) session = protocol.get_session() session.get('http://example.com') self.assertEqual( len({ p.raddr[0] for p in proc.connections() if p.raddr[0] in ip_addresses }), 1) protocol.release_session(session) protocol.close() self.assertEqual( len({ p.raddr[0] for p in proc.connections() if p.raddr[0] in ip_addresses }), 0)
def test_unsupported_fields(self): # Create a field that is not supported by any current versions. Test that we fail when using this field class UnsupportedProp(ExtendedProperty): property_set_id = 'deadcafe-beef-beef-beef-deadcafebeef' property_name = 'Unsupported Property' property_type = 'String' attr_name = 'unsupported_property' self.ITEM_CLASS.register(attr_name=attr_name, attr_cls=UnsupportedProp) try: for f in self.ITEM_CLASS.FIELDS: if f.name == attr_name: f.supported_from = Build(99, 99, 99, 99) with self.assertRaises(ValueError): self.test_folder.get(**{attr_name: 'XXX'}) with self.assertRaises(ValueError): list(self.test_folder.filter(**{attr_name: 'XXX'})) with self.assertRaises(ValueError): list(self.test_folder.all().only(attr_name)) with self.assertRaises(ValueError): list(self.test_folder.all().values(attr_name)) with self.assertRaises(ValueError): list(self.test_folder.all().values_list(attr_name)) finally: self.ITEM_CLASS.deregister(attr_name=attr_name)
def test_decrease_poolsize(self): # Test increasing and decreasing the pool size max_connections = 3 protocol = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(get_random_string(8), get_random_string( 8)), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast(), max_connections=max_connections, )) self.assertEqual(protocol._session_pool.qsize(), 0) self.assertEqual(protocol.session_pool_size, 0) protocol.increase_poolsize() protocol.increase_poolsize() protocol.increase_poolsize() with self.assertRaises(SessionPoolMaxSizeReached): protocol.increase_poolsize() self.assertEqual(protocol._session_pool.qsize(), max_connections) self.assertEqual(protocol.session_pool_size, max_connections) protocol.decrease_poolsize() protocol.decrease_poolsize() with self.assertRaises(SessionPoolMinSizeReached): protocol.decrease_poolsize() self.assertEqual(protocol._session_pool.qsize(), 1)
def test_protocol_instance_caching(self, m): # Verify that we get the same Protocol instance for the same combination of (endpoint, credentials) user, password = get_random_string(8), get_random_string(8) config = Configuration(service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(user, password), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast()) # Test CachingProtocol.__getitem__ with self.assertRaises(KeyError): _ = Protocol[config] base_p = Protocol(config=config) self.assertEqual(base_p, Protocol[config][0]) # Make sure we always return the same item when creating a Protocol with the same endpoint and creds for _ in range(10): p = Protocol(config=config) self.assertEqual(base_p, p) self.assertEqual(id(base_p), id(p)) self.assertEqual(hash(base_p), hash(p)) self.assertEqual(id(base_p._session_pool), id(p._session_pool)) # Test CachingProtocol.__delitem__ del Protocol[config] with self.assertRaises(KeyError): _ = Protocol[config] # Make sure we get a fresh instance after we cleared the cache p = Protocol(config=config) self.assertNotEqual(base_p, p) Protocol.clear_cache()
def test_api_version(self): self.assertEqual(Build(8, 0).api_version(), 'Exchange2007') self.assertEqual(Build(8, 1).api_version(), 'Exchange2007_SP1') self.assertEqual(Build(8, 2).api_version(), 'Exchange2007_SP1') self.assertEqual(Build(8, 3).api_version(), 'Exchange2007_SP1') self.assertEqual(Build(15, 0, 1, 1).api_version(), 'Exchange2013') self.assertEqual(Build(15, 0, 1, 1).api_version(), 'Exchange2013') self.assertEqual( Build(15, 0, 847, 0).api_version(), 'Exchange2013_SP1') with self.assertRaises(ValueError): Build(16, 0).api_version() with self.assertRaises(ValueError): Build(15, 4).api_version()
def test_protocol_instance_caching(self, m): # Verify that we get the same Protocol instance for the same combination of (endpoint, credentials) m.get('https://example.com/EWS/types.xsd', status_code=200) base_p = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials('A', 'B'), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) for i in range(10): p = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials('A', 'B'), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) self.assertEqual(base_p, p) self.assertEqual(id(base_p), id(p)) self.assertEqual(hash(base_p), hash(p)) self.assertEqual(id(base_p._session_pool), id(p._session_pool))
def test_protocol_instance_caching(self, m): # Verify that we get the same Protocol instance for the same combination of (endpoint, credentials) user, password = get_random_string(8), get_random_string(8) base_p = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(user, password), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) for _ in range(10): p = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(user, password), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) self.assertEqual(base_p, p) self.assertEqual(id(base_p), id(p)) self.assertEqual(hash(base_p), hash(p)) self.assertEqual(id(base_p._session_pool), id(p._session_pool)) Protocol.clear_cache()
def test_session(self, m): protocol = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(get_random_string(8), get_random_string(8)), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) session = protocol.create_session() new_session = protocol.renew_session(session) self.assertNotEqual(id(session), id(new_session))
def test_session(self, m): m.get('https://example.com/EWS/types.xsd', status_code=200) protocol = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials('A', 'B'), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) session = protocol.create_session() new_session = protocol.renew_session(session) self.assertNotEqual(id(session), id(new_session))
def test_invalid_build_args(self): with self.assertRaises(TypeError) as e: Build("XXX", 2, 3, 4) self.assertEqual( e.exception.args[0], "'major_version' 'XXX' must be of type <class 'int'>") with self.assertRaises(TypeError) as e: Build(1, "XXX", 3, 4) self.assertEqual( e.exception.args[0], "'minor_version' 'XXX' must be of type <class 'int'>") with self.assertRaises(TypeError) as e: Build(1, 2, "XXX", 4) self.assertEqual(e.exception.args[0], "'major_build' 'XXX' must be of type <class 'int'>") with self.assertRaises(TypeError) as e: Build(1, 2, 3, "XXX") self.assertEqual(e.exception.args[0], "'minor_build' 'XXX' must be of type <class 'int'>")
def test_hardcode_all(self, m): # Test that we can hardcode everything without having a working server. This is useful if neither tasting or # guessing missing values works. Configuration( server="example.com", credentials=Credentials(get_random_string(8), get_random_string(8)), auth_type=NTLM, version=Version(build=Build(15, 1, 2, 3), api_version="foo"), )
def test_magic(self): config = Configuration( server="example.com", credentials=Credentials(get_random_string(8), get_random_string(8)), auth_type=NTLM, version=Version(build=Build(15, 1, 2, 3), api_version="foo"), ) # Just test that these work str(config) repr(config)
def test_q(self): version = Version(build=EXCHANGE_2007) account = mock_account(version=version, protocol=mock_protocol(version=version, service_endpoint='example.com')) root = Root(account=account) tz = zoneinfo.ZoneInfo('Europe/Copenhagen') start = datetime.datetime(2020, 9, 26, 8, 0, 0, tzinfo=tz) end = datetime.datetime(2020, 9, 26, 11, 0, 0, tzinfo=tz) result = '''\ <m:Restriction xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages"> <t:And xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Or> <t:Contains ContainmentMode="Substring" ContainmentComparison="Exact"> <t:FieldURI FieldURI="item:Categories"/> <t:Constant Value="FOO"/> </t:Contains> <t:Contains ContainmentMode="Substring" ContainmentComparison="Exact"> <t:FieldURI FieldURI="item:Categories"/> <t:Constant Value="BAR"/> </t:Contains> </t:Or> <t:IsGreaterThan> <t:FieldURI FieldURI="calendar:End"/> <t:FieldURIOrConstant> <t:Constant Value="2020-09-26T08:00:00+02:00"/> </t:FieldURIOrConstant> </t:IsGreaterThan> <t:IsLessThan> <t:FieldURI FieldURI="calendar:Start"/> <t:FieldURIOrConstant> <t:Constant Value="2020-09-26T11:00:00+02:00"/> </t:FieldURIOrConstant> </t:IsLessThan> </t:And> </m:Restriction>''' q = Q(Q(categories__contains='FOO') | Q(categories__contains='BAR'), start__lt=end, end__gt=start) r = Restriction(q, folders=[Calendar(root=root)], applies_to=Restriction.ITEMS) self.assertEqual(str(r), ''.join(s.lstrip() for s in result.split('\n'))) # Test empty Q q = Q() self.assertEqual(q.to_xml(folders=[Calendar()], version=version, applies_to=Restriction.ITEMS), None) with self.assertRaises(ValueError): Restriction(q, folders=[Calendar(root=root)], applies_to=Restriction.ITEMS) # Test validation with self.assertRaises(ValueError): Q(datetime_created__range=(1,)) # Must have exactly 2 args with self.assertRaises(ValueError): Q(datetime_created__range=(1, 2, 3)) # Must have exactly 2 args with self.assertRaises(TypeError): Q(datetime_created=Build(15, 1)).clean(version=Version(build=EXCHANGE_2007)) # Must be serializable with self.assertRaises(ValueError): # Must be a timezone-aware datetime Q(datetime_created=datetime.datetime(2017, 1, 1)).clean(version=Version(build=EXCHANGE_2007)) with self.assertRaises(ValueError): Q(categories__contains=[[1, 2], [3, 4]]).clean(version=Version(build=EXCHANGE_2007)) # Must be single value
def test_pickle(self): # Test that we can pickle, repr and str Protocols o = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials(get_random_string(8), get_random_string(8)), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) pickled_o = pickle.dumps(o) unpickled_o = pickle.loads(pickled_o) self.assertIsInstance(unpickled_o, type(o)) self.assertEqual(repr(o), repr(unpickled_o)) self.assertEqual(str(o), str(unpickled_o))
def get_test_protocol(**kwargs): return Protocol(config=Configuration( server=kwargs.get("server"), service_endpoint=kwargs.get( "service_endpoint", f"https://{get_random_hostname()}/Foo.asmx"), credentials=kwargs.get( "credentials", Credentials(get_random_string(8), get_random_string(8))), auth_type=kwargs.get("auth_type", NTLM), version=kwargs.get("version", Version(Build(15, 1))), retry_policy=kwargs.get("retry_policy", FailFast()), max_connections=kwargs.get("max_connections"), ))
def test_close(self): # Don't use example.com here - it does not resolve or answer on all ISPs proc = psutil.Process() ip_addresses = { info[4][0] for info in socket.getaddrinfo('httpbin.org', 80, socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_IP) } def conn_count(): return len( [p for p in proc.connections() if p.raddr[0] in ip_addresses]) self.assertGreater(len(ip_addresses), 0) protocol = Protocol(config=Configuration( service_endpoint='http://httpbin.org', credentials=Credentials(get_random_string(8), get_random_string( 8)), auth_type=NOAUTH, version=Version(Build(15, 1)), retry_policy=FailFast(), max_connections=3)) # Merely getting a session should not create conections session = protocol.get_session() self.assertEqual(conn_count(), 0) # Open one URL - we have 1 connection session.get('http://httpbin.org') self.assertEqual(conn_count(), 1) # Open the same URL - we should still have 1 connection session.get('http://httpbin.org') self.assertEqual(conn_count(), 1) # Open some more connections s2 = protocol.get_session() s2.get('http://httpbin.org') s3 = protocol.get_session() s3.get('http://httpbin.org') self.assertEqual(conn_count(), 3) # Releasing the sessions does not close the connections protocol.release_session(session) protocol.release_session(s2) protocol.release_session(s3) self.assertEqual(conn_count(), 3) # But closing explicitly does protocol.close() self.assertEqual(conn_count(), 0)
def test_decrease_poolsize(self): protocol = Protocol(config=Configuration( service_endpoint='https://example.com/Foo.asmx', credentials=Credentials('A', 'B'), auth_type=NTLM, version=Version(Build(15, 1)), retry_policy=FailFast() )) self.assertEqual(protocol._session_pool.qsize(), Protocol.SESSION_POOLSIZE) protocol.decrease_poolsize() self.assertEqual(protocol._session_pool.qsize(), 3) protocol.decrease_poolsize() self.assertEqual(protocol._session_pool.qsize(), 2) protocol.decrease_poolsize() self.assertEqual(protocol._session_pool.qsize(), 1) with self.assertRaises(SessionPoolMinSizeReached): protocol.decrease_poolsize() self.assertEqual(protocol._session_pool.qsize(), 1)
def test_invalid_version_args(self): with self.assertRaises(TypeError) as e: Version(build="XXX") self.assertEqual( e.exception.args[0], "'build' 'XXX' must be of type <class 'exchangelib.version.Build'>" ) with self.assertRaises(TypeError) as e: Version(build="XXX", api_version="XXX") self.assertEqual( e.exception.args[0], "'build' 'XXX' must be of type <class 'exchangelib.version.Build'>" ) with self.assertRaises(TypeError) as e: Version(build=Build(15, 1, 2, 3), api_version=999) self.assertEqual(e.exception.args[0], "'api_version' 999 must be of type <class 'str'>")
def test_from_response(self, m): # Test fallback to suggested api_version value when there is a version mismatch and response version is fishy version = Version.from_soap_header( 'Exchange2007', to_xml(b'''\ <s:Header> <h:ServerVersionInfo MajorBuildNumber="845" MajorVersion="15" MinorBuildNumber="22" MinorVersion="1" Version="V2016_10_10" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"/> </s:Header>''')) self.assertEqual(version.api_version, EXCHANGE_2007.api_version()) self.assertEqual(version.api_version, 'Exchange2007') self.assertEqual(version.build, Build(15, 1, 845, 22)) # Test that override the suggested version if the response version is not fishy version = Version.from_soap_header( 'Exchange2013', to_xml(b'''\ <s:Header> <h:ServerVersionInfo MajorBuildNumber="845" MajorVersion="15" MinorBuildNumber="22" MinorVersion="1" Version="HELLO_FROM_EXCHANGELIB" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"/> </s:Header>''')) self.assertEqual(version.api_version, 'HELLO_FROM_EXCHANGELIB') # Test that we override the suggested version with the version deduced from the build number if a version is not # present in the response version = Version.from_soap_header( 'Exchange2013', to_xml(b'''\ <s:Header> <h:ServerVersionInfo MajorBuildNumber="845" MajorVersion="15" MinorBuildNumber="22" MinorVersion="1" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"/> </s:Header>''')) self.assertEqual(version.api_version, 'Exchange2016') # Test that we use the version deduced from the build number when a version is not present in the response and # there was no suggested version. version = Version.from_soap_header( None, to_xml(b'''\ <s:Header> <h:ServerVersionInfo MajorBuildNumber="845" MajorVersion="15" MinorBuildNumber="22" MinorVersion="1" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"/> </s:Header>''')) self.assertEqual(version.api_version, 'Exchange2016') # Test various parse failures with self.assertRaises(TransportError) as e: Version.from_soap_header( 'Exchange2013', to_xml(b'''\ <s:Header> <foo/> </s:Header>''')) self.assertIn('No ServerVersionInfo in header', e.exception.args[0]) with self.assertRaises(TransportError) as e: Version.from_soap_header( 'Exchange2013', to_xml(b'''\ <s:Header> <h:ServerVersionInfo MajorBuildNumber="845" MajorVersion="15" Version="V2016_10_10" xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types"/> </s:Header>''')) self.assertIn('Bad ServerVersionInfo in response', e.exception.args[0])
def test_default_api_version(self): # Test that a version gets a reasonable api_version value if we don't set one explicitly version = Version(build=Build(15, 1, 2, 3)) self.assertEqual(version.api_version, 'Exchange2016')
def test_comparison(self): self.assertEqual(Version(Build(15, 1, 2, 3)), Version(Build(15, 1, 2, 3))) self.assertNotEqual(Version(Build(15, 1, 2, 3)), Version(Build(15, 1))) self.assertNotEqual(Version(Build(15, 1, 2, 3), api_version="XXX"), Version(None, api_version="XXX")) self.assertNotEqual(Version(None, api_version="XXX"), Version(Build(15, 1, 2), api_version="XXX")) self.assertEqual(Version(Build(15, 1, 2, 3), "XXX"), Version(Build(15, 1, 2, 3), "XXX")) self.assertNotEqual(Version(Build(15, 1, 2, 3), "XXX"), Version(Build(15, 1, 2, 3), "YYY")) self.assertNotEqual(Version(Build(15, 1, 2, 3), "XXX"), Version(Build(99, 88), "XXX")) self.assertNotEqual(Version(Build(15, 1, 2, 3), "XXX"), Version(Build(99, 88), "YYY"))
def test_version_guess(self, m): protocol = self.get_test_protocol() # Test that we can get the version even on error responses m.post( protocol.service_endpoint, status_code=200, content=b"""\ <?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <h:ServerVersionInfo xmlns:h="http://schemas.microsoft.com/exchange/services/2006/types" MajorVersion="15" MinorVersion="1" MajorBuildNumber="2345" MinorBuildNumber="6789" Version="V2017_07_11"/> </s:Header> <s:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Error"> <m:MessageText>Multiple results were found.</m:MessageText> <m:ResponseCode>ErrorNameResolutionMultipleResults</m:ResponseCode> <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </s:Body> </s:Envelope>""", ) Version.guess(protocol) self.assertEqual(protocol.version.build, Build(15, 1, 2345, 6789)) # Test exception when there are no version headers m.post( protocol.service_endpoint, status_code=200, content=b"""\ <?xml version='1.0' encoding='utf-8'?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> </s:Header> <s:Body> <m:ResolveNamesResponse xmlns:m="http://schemas.microsoft.com/exchange/services/2006/messages" xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <m:ResponseMessages> <m:ResolveNamesResponseMessage ResponseClass="Error"> <m:MessageText>.</m:MessageText> <m:ResponseCode>ErrorNameResolutionMultipleResults</m:ResponseCode> <m:DescriptiveLinkKey>0</m:DescriptiveLinkKey> </m:ResolveNamesResponseMessage> </m:ResponseMessages> </m:ResolveNamesResponse> </s:Body> </s:Envelope>""", ) with self.assertRaises(TransportError) as e: Version.guess(protocol) self.assertEqual( e.exception.args[0], "No valid version headers found in response (ErrorNameResolutionMultipleResults('.'))" )
def test_compare(self): self.assertEqual(Build(15, 0, 1, 2), Build(15, 0, 1, 2)) self.assertNotEqual(Build(15, 0, 1, 2), Build(15, 0, 1, 3)) self.assertLess(Build(15, 0, 1, 2), Build(15, 0, 1, 3)) self.assertLess(Build(15, 0, 1, 2), Build(15, 0, 2, 2)) self.assertLess(Build(15, 0, 1, 2), Build(15, 1, 1, 2)) self.assertLess(Build(15, 0, 1, 2), Build(16, 0, 1, 2)) self.assertLessEqual(Build(15, 0, 1, 2), Build(15, 0, 1, 2)) self.assertGreater(Build(15, 0, 1, 2), Build(15, 0, 1, 1)) self.assertGreater(Build(15, 0, 1, 2), Build(15, 0, 0, 2)) self.assertGreater(Build(15, 1, 1, 2), Build(15, 0, 1, 2)) self.assertGreater(Build(15, 0, 1, 2), Build(14, 0, 1, 2)) self.assertGreaterEqual(Build(15, 0, 1, 2), Build(15, 0, 1, 2))
def test_magic(self): with self.assertRaises(ValueError): Build(7, 0) self.assertEqual(str(Build(9, 8, 7, 6)), '9.8.7.6')