# -*- coding: utf-8 -*- import pytest from requests3.structures import CaseInsensitiveDict, LookupDict, HTTPHeaderDict from urllib3._collections import HTTPHeaderDict as U3HeaderDict class TestCaseInsensitiveDict: @pytest.fixture(autouse=True) def setup(self): """CaseInsensitiveDict instance with "Accept" header.""" self.case_insensitive_dict = CaseInsensitiveDict() self.case_insensitive_dict['Accept'] = 'application/json' def test_list(self): assert list(self.case_insensitive_dict) == ['Accept'] possible_keys = pytest.mark.parametrize( 'key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept') ) @possible_keys def test_getitem(self, key): assert self.case_insensitive_dict[key] == 'application/json' @possible_keys def test_delitem(self, key): del self.case_insensitive_dict[key] assert key not in self.case_insensitive_dict def test_lower_items(self): assert list(self.case_insensitive_dict.lower_items()) == [ ('accept', 'application/json') ] def test_repr(self): assert repr( self.case_insensitive_dict ) == "{'Accept': 'application/json'}" def test_copy(self): copy = self.case_insensitive_dict.copy() assert copy is not self.case_insensitive_dict assert copy == self.case_insensitive_dict @pytest.mark.parametrize( 'other, result', (({'AccePT': 'application/json'}, True), ({}, False), (None, False)), ) def test_instance_equality(self, other, result): assert (self.case_insensitive_dict == other) is result class TestHTTPHeaderDictCompatibility(TestCaseInsensitiveDict): """HTTPHeaderDict should be completely compatible with CaseInsensitiveDict when used for headers, so ensure that all the tests for the base class also pass for this one.""" @pytest.fixture(autouse=True) def setup(self): self.case_insensitive_dict = HTTPHeaderDict() self.case_insensitive_dict['Accept'] = 'application/json' class TestHTTPHeaderDict: @pytest.fixture(autouse=True) def setup(self): self.kvs = [ ('animal', 'chicken'), ('AnimaL', 'Cow'), ('CAKE', 'Cheese!'), ('Sauce', 'Bread'), ('Sauce', 'Cherry, or Plum Tomato'), ] # HTTPHeaderDict from urllib3. self.u3dict = ud = U3HeaderDict() [ud.add(*tpl) for tpl in self.kvs] # Regular dictionary. self.ddict = dict(self.kvs) self.ddict['Sauce'] = ['Bread!', 'Cherry, or Plum Tomato'] # Used by test_extend. All of these "extra" values are mostly # equivalent to each other. self.extra_hd = hd2 = HTTPHeaderDict(ANIMAL=['Dog', 'elephant']) hd2['cake'] = 'Babka' hd2.setlist('sound', ['quiet', 'LOUD']) hd2['CUTLERY'] = 'fork' self.extra_tuple_pairs = tuple_pairs = [ ('ANIMAL', 'Dog'), ('Animal', 'elephant'), ('cake', ['Babka']), ('sound', 'quiet'), ('sound', 'LOUD'), ('CUTLERY', 'fork'), ] self.extra_simple_dict = dict(tuple_pairs) self.extra_simple_dict['sound'] = ('quiet', 'LOUD') self.extra_u3 = U3HeaderDict() for k, v in tuple_pairs: if isinstance(v, (tuple, list)): for vi in v: self.extra_u3.add(k, vi) else: self.extra_u3.add(k, v) def test_item_access(self): hd = HTTPHeaderDict(self.kvs) # Test that values are combined. assert hd['Sauce'] == 'Bread, Cherry, or Plum Tomato' assert hd['ANIMAL'] == 'chicken, Cow' # Test we can overwrite values. hd['animal'] = 'Goat!' assert hd['anIMal'] == 'Goat!' # Test deletion works. del hd['sauce'] pytest.raises(KeyError, hd.__getitem__, 'sauce') # Only string types allowed. pytest.raises(ValueError, hd.__setitem__, 'cake', ['Cheese', 'sponge']) def test_equality(self): hd = HTTPHeaderDict(self.u3dict) assert hd == self.u3dict assert hd == HTTPHeaderDict(hd) # Test that we still work even if we are comparing to a # CaseInsensitiveDict instance. cid = CaseInsensitiveDict(hd) assert hd == cid assert cid == hd def test_lower_items(self): hd = HTTPHeaderDict(self.kvs, cutlery='fork') assert list(hd.lower_items()) == [ ('animal', 'chicken, Cow'), ('cake', 'Cheese!'), ('sauce', 'Bread, Cherry, or Plum Tomato'), ('cutlery', 'fork'), ] def test_copy(self): hd = HTTPHeaderDict(self.u3dict) hd2 = hd.copy() assert hd is not hd2 assert hd == hd2 def test_get_and_set_list(self): hd = HTTPHeaderDict(self.kvs) assert hd.getlist('SAUCE') == ['Bread', 'Cherry, or Plum Tomato'] assert hd.getlist('CAKE') == ['Cheese!'] assert hd.getlist('DRINK') == [] # Needs to be a regular sequence type containing just strings. pytest.raises(ValueError, hd.setlist, 'Drink', 'Water') pytest.raises(ValueError, hd.setlist, 'Drink', ['H', 2, 'O']) # Test multi-setting. hd.setlist('Drink', ['Water', 'Juice']) assert hd.getlist('DRINK') == ['Water', 'Juice'] # Setting to an empty sequence should remove the entry. hd.setlist('DRInk', []) pytest.raises(KeyError, hd.__getitem__, 'DrinK') assert hd.getlist('DRiNK') == [] def test_add(self): hd = HTTPHeaderDict() hd.add('sound', 'quiet') hd.add('SOUND', 'LOUD') assert hd.getlist('Sound') == ['quiet', 'LOUD'] # Enforce type-checking in the add method. pytest.raises(ValueError, hd.add, 'Sound', 5) @pytest.mark.parametrize( 'attr,as_arg,animal_arg_is_ordered', [('extra_hd', True, True), ('extra_tuple_pairs', True, True), ('extra_simple_dict', True, False), ('extra_u3', True, False), ('extra_simple_dict', False, False)], # These types will have the "animal" arguments in our preferred order. # And these types will lose the ordering, so we can't make assertions # about the final order of those values. ) def test_extend(self, attr, as_arg, animal_arg_is_ordered): item = getattr(self, attr) # Call extend with the associated values - we should see all of the # merged data in the HTTPHeaderDict instance. extras = {'cutlery': 'knife'} hd = HTTPHeaderDict(self.kvs) if as_arg: hd.extend(item, **extras) else: hd.extend(extras, **item) # Test all the stored values are what we expect. mget = hd.getlist # Depending on the item we merged in, we might be able to make # assumptions what the overall order of the structure is. animal_seq = mget('animal') if animal_arg_is_ordered: assert animal_seq == ['chicken', 'Cow', 'Dog', 'elephant'] else: # The existing order in HTTPHeadersDict of the first two values # should be preserved - no guarantees in which order the other # two values are added. assert animal_seq in [ ['chicken', 'Cow', 'Dog', 'elephant'], ['chicken', 'Cow', 'elephant', 'Dog'], ] assert mget('cake') == ['Cheese!', 'Babka'] assert mget('sound') == ['quiet', 'LOUD'] # We don't mandate the order in which these dictionaries are # processed, so it's fine whichever order it is. assert mget('cutlery') in [['fork', 'knife'], ['knife', 'fork']] def test_extend_type_checking(self): hd = HTTPHeaderDict() pytest.raises(ValueError, hd.extend, dict(type=['xml', None, 'html'])) def test_repr(self): hd = HTTPHeaderDict() assert repr(hd) == '{}' hd.add('type', 'xml') assert repr(hd) == "{'type': 'xml'}" hd.add('type', 'html') assert repr(hd) == "{'type': ('xml', 'html')}" # We can't guarantee order once we have more than one key. hd.add('Accept', 'text/html') assert repr(hd) in [ "{'type': ('xml', 'html'), 'Accept': 'text/html'}", "{'Accept': 'text/html', 'type': ('xml', 'html')}", ] assert str(hd) == repr(hd) class TestLookupDict: @pytest.fixture(autouse=True) def setup(self): """LookupDict instance with "bad_gateway" attribute.""" self.lookup_dict = LookupDict('test') self.lookup_dict.bad_gateway = 502 def test_repr(self): assert repr(self.lookup_dict) == "" get_item_parameters = pytest.mark.parametrize( 'key, value', (('bad_gateway', 502), ('not_a_key', None)) ) @get_item_parameters def test_getitem(self, key, value): assert self.lookup_dict[key] == value @get_item_parameters def test_get(self, key, value): assert self.lookup_dict.get(key) == value