import pytest

from datetime import timedelta

from webob import cookies
from webob.compat import text_
from webob.compat import native_

py2only = pytest.mark.skipif("sys.version_info >= (3, 0)")
py3only = pytest.mark.skipif("sys.version_info < (3, 0)")

def setup_module(module):
    cookies._should_raise = True

def teardown_module(module):
    cookies._should_raise = False

def test_cookie_empty():
    c = cookies.Cookie() # empty cookie
    assert repr(c) == '<Cookie: []>'

def test_cookie_one_value():
    c = cookies.Cookie('dismiss-top=6')
    vals = list(c.values())
    assert len(vals) == 1
    assert vals[0].name == b'dismiss-top'
    assert vals[0].value == b'6'

def test_cookie_one_value_with_trailing_semi():
    c = cookies.Cookie('dismiss-top=6;')
    vals = list(c.values())
    assert len(vals) == 1
    assert vals[0].name == b'dismiss-top'
    assert vals[0].value == b'6'
    c = cookies.Cookie('dismiss-top=6;')

def test_cookie_escaped_unquoted():
    assert list(cookies.parse_cookie('x=\\040')) == [(b'x', b' ')]

def test_cookie_complex():
    c = cookies.Cookie('dismiss-top=6; CP=null*, '
                       'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,"')

    def d(v):
        return v.decode('ascii')
    c_dict = dict((d(k), d(v.value)) for k, v in c.items())
    assert c_dict == {
        'a': '42,',
        'CP': 'null*',
        'PHPSESSID': '0a539d42abc001cdc762809248d4beed',
        'dismiss-top': '6'
    }

def test_cookie_complex_serialize():
    c = cookies.Cookie('dismiss-top=6; CP=null*, '
                       'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42"')
    assert c.serialize() == 'CP=null*; PHPSESSID=0a539d42abc001cdc762809248d4beed;'\
                            ' a=42; dismiss-top=6'

def test_cookie_load_multiple():
    c = cookies.Cookie('a=1; Secure=true')
    vals = list(c.values())
    assert len(vals) == 1
    assert c[b'a'][b'secure'] == b'true'

def test_cookie_secure():
    c = cookies.Cookie()
    c[text_('foo')] = b'bar'
    c[b'foo'].secure = True
    assert c.serialize() == 'foo=bar; secure'

def test_cookie_httponly():
    c = cookies.Cookie()
    c['foo'] = b'bar'
    c[b'foo'].httponly = True
    assert c.serialize() == 'foo=bar; HttpOnly'

def test_cookie_samesite_strict():
    c = cookies.Cookie()
    c[b"foo"] = b"bar"
    c[b"foo"].samesite = b"Strict"
    assert c.serialize() == "foo=bar; SameSite=Strict"

def test_cookie_samesite_lax():
    c = cookies.Cookie()
    c[b"foo"] = b"bar"
    c[b"foo"].samesite = b"Lax"
    assert c.serialize() == "foo=bar; SameSite=Lax"

def test_cookie_samesite_none():
    c = cookies.Cookie()
    c[b"foo"] = b"bar"
    c[b"foo"].samesite = b"None"
    c[b"foo"].secure = True
    assert c.serialize() == "foo=bar; secure; SameSite=None"

def test_cookie_samesite_none_not_secure():
    c = cookies.Cookie()
    c[b"foo"] = b"bar"
    c[b"foo"].samesite = b"None"
    with pytest.raises(ValueError):
        c.serialize()

def test_cookie_samesite_future__default():
    # ensure default behavior when unsupported values are provided
    c = cookies.Cookie()
    with pytest.raises(ValueError) as excinfo:
        c[b"foo"] = b"bar"
        c[b"foo"].samesite = b"Future"
        c.serialize()
    assert excinfo.value.args[0] == "SameSite must be 'strict', 'lax', or 'none'"


def test_cookie_samesite_future__monkeypatched(monkeypatch):
    # disable validation so future args pass
    monkeypatch.setattr(cookies, "SAMESITE_VALIDATION", False)
    c = cookies.Cookie()
    c[b"foo"] = b"bar"
    c[b"foo"].samesite = b"Future"
    assert c.serialize() == "foo=bar; SameSite=Future"

    # ensure we can toggle it to True and re-achieve default behavior...
    monkeypatch.setattr(cookies, "SAMESITE_VALIDATION", True)
    with pytest.raises(ValueError) as excinfo:
        c[b"foo"] = b"bar"
        c[b"foo"].samesite = b"Future"
        c.serialize()
    assert excinfo.value.args[0] == "SameSite must be 'strict', 'lax', or 'none'"


def test_cookie_reserved_keys():
    c = cookies.Cookie('dismiss-top=6; CP=null*; $version=42; a=42')
    assert '$version' not in c
    c = cookies.Cookie('$reserved=42; a=$42')
    assert list(c.keys()) == [b'a']

def test_serialize_cookie_date():
    """
        Testing webob.cookies.serialize_cookie_date.
        Missing scenarios:
            * input value is an str, should be returned verbatim
            * input value is an int, should be converted to timedelta and we
              should continue the rest of the process
    """
    date_one = cookies.serialize_cookie_date(b'Tue, 04-Jan-2011 13:43:50 GMT')
    assert date_one == b'Tue, 04-Jan-2011 13:43:50 GMT'
    date_two = cookies.serialize_cookie_date(text_('Tue, 04-Jan-2011 13:43:50 GMT'))
    assert date_two == b'Tue, 04-Jan-2011 13:43:50 GMT'
    assert cookies.serialize_cookie_date(None) is None
    cdate_delta = cookies.serialize_cookie_date(timedelta(seconds=10))
    cdate_int = cookies.serialize_cookie_date(10)
    assert cdate_delta == cdate_int

def test_serialize_samesite():
    assert cookies.serialize_samesite(b"Lax") == b"Lax"
    assert cookies.serialize_samesite(b"Strict") == b"Strict"
    assert cookies.serialize_samesite(b"None") == b"None"

    with pytest.raises(ValueError):
        cookies.serialize_samesite(b"SomethingElse")

def test_ch_unquote():
    assert cookies._unquote(b'"hello world') == b'"hello world'
    assert cookies._unquote(b'hello world') == b'hello world'
    assert cookies._unquote(b'"hello world"') == b'hello world'

    # Spaces are not valid in cookies, we support getting them, but won't
    # support sending them
    with pytest.raises(ValueError):
        cookies._value_quote(b'hello world')

    # quotation mark escaped w/ backslash is unquoted correctly (support
    # pre webob 1.3 cookies)
    assert cookies._unquote(b'"\\""') == b'"'
    # we also are able to unquote the newer \\042 serialization of quotation
    # mark
    assert cookies._unquote(b'"\\042"') == b'"'

    # New cookies can not contain quotes.
    with pytest.raises(ValueError):
        cookies._value_quote(b'"')

    # backslash escaped w/ backslash is unquoted correctly (support
    # pre webob 1.3 cookies)
    assert cookies._unquote(b'"\\\\"') == b'\\'
    # we also are able to unquote the newer \\134 serialization of backslash
    assert cookies._unquote(b'"\\134"') == b'\\'

    # Cookies may not contain a backslash
    with pytest.raises(ValueError):
        cookies._value_quote(b'\\')

    # misc byte escaped as octal
    assert cookies._unquote(b'"\\377"') == b'\xff'

    with pytest.raises(ValueError):
        cookies._value_quote(b'\xff')

    # combination
    assert cookies._unquote(b'"a\\"\\377"') == b'a"\xff'
    with pytest.raises(ValueError):
        cookies._value_quote(b'a"\xff')

def test_cookie_invalid_name():
    c = cookies.Cookie()
    c['La Pe\xc3\xb1a'] = '1'
    assert len(c) == 0

def test_morsel_serialize_with_expires():
    morsel = cookies.Morsel(b'bleh', b'blah')
    morsel.expires = b'Tue, 04-Jan-2011 13:43:50 GMT'
    result = morsel.serialize()
    assert result, 'bleh=blah; expires=Tue, 04-Jan-2011 13:43:50 GMT'

def test_serialize_max_age_timedelta():
    import datetime
    val = datetime.timedelta(86400)
    result = cookies.serialize_max_age(val)
    assert result == b'7464960000'

def test_serialize_max_age_int():
    val = 86400
    result = cookies.serialize_max_age(val)
    assert result == b'86400'

def test_serialize_max_age_str():
    val = '86400'
    result = cookies.serialize_max_age(val)
    assert result == b'86400'

def test_parse_qmark_in_val():
    v = r'x="\"\073\054\""; expires=Sun, 12-Jun-2011 23:16:01 GMT'
    c = cookies.Cookie(v)
    assert c[b'x'].value == b'";,"'
    assert c[b'x'].expires, b'Sun == 12-Jun-2011 23:16:01 GMT'

def test_morsel_repr():
    v = cookies.Morsel(b'a', b'b')
    result = repr(v)
    assert result == "<Morsel: a='b'>"

class TestRequestCookies(object):
    def _makeOne(self, environ):
        from webob.cookies import RequestCookies
        return RequestCookies(environ)

    def test_get_no_cache_key_in_environ_no_http_cookie_header(self):
        environ = {}
        inst = self._makeOne(environ)
        assert inst.get('a') is None
        parsed = environ['webob._parsed_cookies']
        assert parsed == ({}, '')

    def test_get_no_cache_key_in_environ_has_http_cookie_header(self):
        header = 'a=1; b=2'
        environ = {'HTTP_COOKIE': header}
        inst = self._makeOne(environ)
        assert inst.get('a') == '1'
        parsed = environ['webob._parsed_cookies'][0]
        assert parsed['a'] == '1'
        assert parsed['b'] == '2'
        assert environ['HTTP_COOKIE'] == header # no change

    def test_get_cache_key_in_environ_no_http_cookie_header(self):
        environ = {'webob._parsed_cookies': ({}, '')}
        inst = self._makeOne(environ)
        assert inst.get('a') is None
        parsed = environ['webob._parsed_cookies']
        assert parsed == ({}, '')

    def test_get_cache_key_in_environ_has_http_cookie_header(self):
        header = 'a=1; b=2'
        environ = {'HTTP_COOKIE': header, 'webob._parsed_cookies': ({}, '')}
        inst = self._makeOne(environ)
        assert inst.get('a') == '1'
        parsed = environ['webob._parsed_cookies'][0]
        assert parsed['a'] == '1'
        assert parsed['b'] == '2'
        assert environ['HTTP_COOKIE'] == header # no change

    def test_get_missing_with_default(self):
        environ = {}
        inst = self._makeOne(environ)
        assert inst.get('a', '') == ''

    def test___setitem__name_not_string_type(self):
        inst = self._makeOne({})
        with pytest.raises(TypeError):
            inst.__setitem__(None, 1)

    def test___setitem__name_not_encodeable_to_ascii(self):
        name = native_(b'La Pe\xc3\xb1a', 'utf-8')
        inst = self._makeOne({})
        with pytest.raises(TypeError):
            inst.__setitem__(name, 'abc')

    def test___setitem__name_not_rfc2109_valid(self):
        name = '$a'
        inst = self._makeOne({})
        with pytest.raises(TypeError):
            inst.__setitem__(name, 'abc')

    def test___setitem__value_not_string_type(self):
        inst = self._makeOne({})
        with pytest.raises(ValueError):
            inst.__setitem__('a', None)

    def test___setitem__value_not_utf_8_decodeable(self):
        value = text_(b'La Pe\xc3\xb1a', 'utf-8')
        value = value.encode('utf-16')
        inst = self._makeOne({})
        with pytest.raises(ValueError):
            inst.__setitem__('a', value)

    def test__setitem__success_no_existing_headers(self):
        value = native_(b'test_cookie', 'utf-8')
        environ = {}
        inst = self._makeOne(environ)
        inst['a'] = value
        assert environ['HTTP_COOKIE'] == 'a=test_cookie'

    def test__setitem__success_append(self):
        value = native_(b'test_cookie', 'utf-8')
        environ = {'HTTP_COOKIE': 'a=1; b=2'}
        inst = self._makeOne(environ)
        inst['c'] = value
        assert environ['HTTP_COOKIE'] == 'a=1; b=2; c=test_cookie'

    def test__setitem__success_replace(self):
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        inst['b'] = 'abc'
        assert environ['HTTP_COOKIE'] == 'a=1; b=abc; c=3'
        inst['c'] = '4'
        assert environ['HTTP_COOKIE'] == 'a=1; b=abc; c=4'

    def test__delitem__fail_no_http_cookie(self):
        environ = {}
        inst = self._makeOne(environ)
        with pytest.raises(KeyError):
            inst.__delitem__('a')
        assert environ == {}

    def test__delitem__fail_with_http_cookie(self):
        environ = {'HTTP_COOKIE': ''}
        inst = self._makeOne(environ)
        with pytest.raises(KeyError):
            inst.__delitem__('a')
        assert environ == {'HTTP_COOKIE': ''}

    def test__delitem__success(self):
        environ = {'HTTP_COOKIE': 'a=1'}
        inst = self._makeOne(environ)
        del inst['a']
        assert environ['HTTP_COOKIE'] == ''
        assert inst._cache == {}

    def test_keys(self):
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(inst.keys())) == ['a', 'b', 'c']

    def test_values(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(inst.values())) == ['1', '3', val]

    def test_items(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(inst.items())) == [('a', '1'), ('b', val), ('c', '3')]

    @py2only
    def test_iterkeys(self):
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(inst.iterkeys())) == ['a', 'b', 'c']

    @py3only
    def test_iterkeys_py3(self):
        environ = {'HTTP_COOKIE': b'a=1; b="La Pe\\303\\261a"; c=3'.decode('utf-8')}
        inst = self._makeOne(environ)
        assert sorted(list(inst.keys())) == ['a', 'b', 'c']

    @py2only
    def test_itervalues(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        sorted(list(inst.itervalues())) == ['1', '3', val]

    @py3only
    def test_itervalues_py3(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': b'a=1; b="La Pe\\303\\261a"; c=3'.decode('utf-8')}
        inst = self._makeOne(environ)
        sorted(list(inst.values())) == ['1', '3', val]

    @py2only
    def test_iteritems(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': 'a=1; b="La Pe\\303\\261a"; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(inst.iteritems())) == [('a', '1'), ('b', val), ('c', '3')]

    @py3only
    def test_iteritems_py3(self):
        val = text_(b'La Pe\xc3\xb1a', 'utf-8')
        environ = {'HTTP_COOKIE': b'a=1; b="La Pe\\303\\261a"; c=3'.decode('utf-8')}
        inst = self._makeOne(environ)
        assert sorted(list(inst.items())) == [('a', '1'), ('b', val), ('c', '3')]

    def test___contains__(self):
        environ = {'HTTP_COOKIE': 'a=1'}
        inst = self._makeOne(environ)
        assert 'a' in inst
        assert 'b' not in inst

    def test___iter__(self):
        environ = {'HTTP_COOKIE': 'a=1; b=2; c=3'}
        inst = self._makeOne(environ)
        assert sorted(list(iter(inst))) == ['a', 'b', 'c']

    def test___len__(self):
        environ = {'HTTP_COOKIE': 'a=1; b=2; c=3'}
        inst = self._makeOne(environ)
        assert len(inst) == 3
        del inst['a']
        assert len(inst) == 2

    def test_clear(self):
        environ = {'HTTP_COOKIE': 'a=1; b=2; c=3'}
        inst = self._makeOne(environ)
        inst.clear()
        assert environ['HTTP_COOKIE'] == ''
        assert inst.get('a') is None

    def test___repr__(self):
        environ = {'HTTP_COOKIE': 'a=1; b=2; c=3'}
        inst = self._makeOne(environ)
        r = repr(inst)
        assert r.startswith('<RequestCookies (dict-like) with values ')
        assert r.endswith('>')


class TestCookieMakeCookie(object):
    def makeOne(self, name, value, **kw):
        from webob.cookies import make_cookie
        return make_cookie(name, value, **kw)

    def test_make_cookie_max_age(self):
        cookie = self.makeOne('test_cookie', 'value', max_age=500)

        assert 'test_cookie=value' in cookie
        assert 'Max-Age=500;' in cookie
        assert 'expires' in cookie

    def test_make_cookie_max_age_timedelta(self):
        from datetime import timedelta
        cookie = self.makeOne('test_cookie', 'value',
                              max_age=timedelta(seconds=500))

        assert 'test_cookie=value' in cookie
        assert 'Max-Age=500;' in cookie
        assert 'expires' in cookie
        assert 'expires=500' not in cookie

    def test_make_cookie_max_age_str_valid_int(self):
        cookie = self.makeOne('test_cookie', 'value',
                              max_age='500')

        assert 'test_cookie=value' in cookie
        assert 'Max-Age=500;' in cookie
        assert 'expires' in cookie
        assert 'expires=500' not in cookie

    def test_make_cookie_max_age_str_invalid_int(self):
        with pytest.raises(ValueError):
            self.makeOne('test_cookie', 'value', max_age='test')

    def test_make_cookie_comment(self):
        cookie = self.makeOne('test_cookie', 'value', comment='lolwhy')

        assert 'test_cookie=value' in cookie
        assert 'Comment=lolwhy' in cookie

    def test_make_cookie_path(self):
        cookie = self.makeOne('test_cookie', 'value', path='/foo/bar/baz')

        assert 'test_cookie=value' in cookie
        assert 'Path=/foo/bar/baz' in cookie

    @pytest.mark.parametrize("samesite", ["Strict", "Lax", "None"])
    def test_make_cookie_samesite(self, samesite):
        cookie = self.makeOne("test_cookie", "value", samesite=samesite, secure=True)

        assert "test_cookie=value" in cookie
        assert "SameSite=" + samesite in cookie

        assert 'test_cookie=value' in cookie
        assert 'SameSite=' + samesite in cookie

class CommonCookieProfile(object):
    def makeDummyRequest(self, **kw):
        class Dummy(object):
            def __init__(self, **kwargs):
                self.__dict__.update(**kwargs)
        d = Dummy(**kw)
        d.response = Dummy()
        d.response.headerlist = list()
        return d

    def makeOneRequest(self):
        request = self.makeDummyRequest(environ=dict())
        request.environ['HTTP_HOST'] = 'www.example.net'
        request.cookies = dict()

        return request


class TestCookieProfile(CommonCookieProfile):
    def makeOne(self, name='uns', **kw):
        if 'request' in kw:
            request = kw['request']
            del kw['request']
        else:
            request = self.makeOneRequest()

        from webob.cookies import CookieProfile
        return CookieProfile(name, **kw)(request)

    def test_cookie_creation(self):
        cookie = self.makeOne()

        from webob.cookies import CookieProfile
        assert isinstance(cookie, CookieProfile)

    def test_cookie_name(self):
        cookie = self.makeOne()

        cookie_list = cookie.get_headers("test")

        for cookie in cookie_list:
            assert cookie[1].startswith('uns')
            assert 'uns="";' not in cookie[1]

    def test_cookie_no_request(self):
        from webob.cookies import CookieProfile
        cookie = CookieProfile('uns')

        with pytest.raises(ValueError):
            cookie.get_value()

    def test_get_value_serializer_raises_value_error(self):
        class RaisingSerializer(object):
            def loads(self, val):
                raise ValueError('foo')
        cookie = self.makeOne(serializer=RaisingSerializer())
        assert cookie.get_value() is None

    def test_with_cookies(self):
        request = self.makeOneRequest()
        request.cookies['uns'] = 'InRlc3Qi'
        cookie = self.makeOne(request=request)
        ret = cookie.get_value()

        assert ret == "test"

    def test_with_invalid_cookies(self):
        request = self.makeOneRequest()
        request.cookies['uns'] = 'InRlc3Q'
        cookie = self.makeOne(request=request)
        ret = cookie.get_value()

        assert ret is None

class TestSignedCookieProfile(CommonCookieProfile):
    def makeOne(self, secret='seekrit', salt='salty', name='uns', **kw):
        if 'request' in kw:
            request = kw['request']
            del kw['request']
        else:
            request = self.makeOneRequest()

        from webob.cookies import SignedCookieProfile as CookieProfile
        return CookieProfile(secret, salt, name, **kw)(request)

    def test_cookie_name(self):
        cookie = self.makeOne()

        cookie_list = cookie.get_headers("test")

        for cookie in cookie_list:
            assert cookie[1].startswith('uns')
            assert 'uns="";' not in cookie[1]

    def test_cookie_expire(self):
        cookie = self.makeOne()

        cookie_list = cookie.get_headers(None, max_age=0)

        for cookie in cookie_list:
            assert 'Max-Age=0;' in cookie[1]

    def test_cookie_max_age(self):
        cookie = self.makeOne()

        cookie_list = cookie.get_headers("test", max_age=60)

        for cookie in cookie_list:
            assert 'Max-Age=60;' in cookie[1]
            assert 'expires=' in cookie[1]

    def test_cookie_raw(self):
        cookie = self.makeOne()

        cookie_list = cookie.get_headers("test")

        assert isinstance(cookie_list, list)

    def test_set_cookie(self):
        request = self.makeOneRequest()
        cookie = self.makeOne(request=request)

        ret = cookie.set_cookies(request.response, "test")

        assert ret == request.response

    def test_no_cookie(self):
        cookie = self.makeOne()

        ret = cookie.get_value()

        assert ret is None

    def test_with_cookies(self):
        request = self.makeOneRequest()
        request.cookies['uns'] = (
            'FLIoEwZcKG6ITQSqbYcUNnPljwOcGNs25JRVCSoZcx_uX-OA1AhssA-CNeVKpWksQ'
            'a0ktMhuQDdjzmDwgzbptiJ0ZXN0Ig'
            )
        cookie = self.makeOne(request=request)
        ret = cookie.get_value()

        assert ret == "test"

    def test_with_bad_cookie_invalid_base64(self):
        request = self.makeOneRequest()
        request.cookies['uns'] = (
            "gAJVBHRlc3RxAS4KjKfwGmCkliC4ba99rWUdpy_{}riHzK7MQFPsbSgYTgALHa"
            "SHrRkd3lyE8c4w5ruxAKOyj2h5oF69Ix7ERZv_")
        cookie = self.makeOne(request=request)

        val = cookie.get_value()

        assert val is None

    def test_with_bad_cookie_invalid_signature(self):
        request = self.makeOneRequest()
        request.cookies['uns'] = (
            "InRlc3QiFLIoEwZcKG6ITQSqbYcUNnPljwOcGNs25JRVCSoZcx/uX+OA1AhssA"
            "+CNeVKpWksQa0ktMhuQDdjzmDwgzbptg==")
        cookie = self.makeOne(secret='sekrit!', request=request)

        val = cookie.get_value()

        assert val is None

    def test_with_domain(self):
        cookie = self.makeOne(domains=("testing.example.net",))
        ret = cookie.get_headers("test")

        passed = False

        for cookie in ret:
            if 'Domain=testing.example.net' in cookie[1]:
                passed = True

        assert passed
        assert len(ret) == 1

    def test_with_domains(self):
        cookie = self.makeOne(
            domains=("testing.example.net", "testing2.example.net")
            )
        ret = cookie.get_headers("test")

        passed = 0

        for cookie in ret:
            if 'Domain=testing.example.net' in cookie[1]:
                passed += 1
            if 'Domain=testing2.example.net' in cookie[1]:
                passed += 1

        assert passed == 2
        assert len(ret) == 2

    def test_flag_secure(self):
        cookie = self.makeOne(secure=True)
        ret = cookie.get_headers("test")

        for cookie in ret:
            assert '; secure' in cookie[1]

    def test_flag_http_only(self):
        cookie = self.makeOne(httponly=True)
        ret = cookie.get_headers("test")

        for cookie in ret:
            assert '; HttpOnly' in cookie[1]

    @pytest.mark.parametrize("samesite", [b"Strict", b"Lax", b"None"])
    def test_with_samesite_bytes(self, samesite):
        cookie = self.makeOne(samesite=samesite, secure=True)
        ret = cookie.get_headers("test")

        for cookie in ret:
            assert "; SameSite=" + samesite.decode('ascii') in cookie[1]

    @pytest.mark.parametrize("samesite", ["Strict", "Lax", "None"])
    def test_with_samesite(self, samesite):
        cookie = self.makeOne(samesite=samesite, secure=True)
        ret = cookie.get_headers("test")

        for cookie in ret:
            assert "; SameSite=" + samesite in cookie[1]

    def test_cookie_length(self):
        cookie = self.makeOne()

        longstring = 'a' * 4096
        with pytest.raises(ValueError):
            cookie.get_headers(longstring)

    def test_very_long_key(self):
        longstring = 'a' * 1024
        cookie = self.makeOne(secret=longstring)

        cookie.get_headers("test")

def serialize(secret, salt, data):
    import hmac
    import base64
    import json
    from hashlib import sha1
    from webob.compat import bytes_
    salted_secret = bytes_(salt or '', 'utf-8') + bytes_(secret, 'utf-8')
    cstruct = bytes_(json.dumps(data))
    sig = hmac.new(salted_secret, cstruct, sha1).digest()
    return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=')

class TestSignedSerializer(object):
    def makeOne(self, secret, salt, hashalg='sha1', **kw):
        from webob.cookies import SignedSerializer
        return SignedSerializer(secret, salt, hashalg=hashalg, **kw)

    def test_serialize(self):
        ser = self.makeOne('seekrit', 'salty')

        assert ser.dumps('test') == serialize('seekrit', 'salty', 'test')

    def test_deserialize(self):
        ser = self.makeOne('seekrit', 'salty')

        assert ser.loads(serialize('seekrit', 'salty', 'test')) == 'test'

    def test_with_highorder_secret(self):
        secret = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8')
        ser = self.makeOne(secret, 'salty')

        assert ser.loads(serialize(secret, 'salty', 'test')) == 'test'

    def test_with_highorder_salt(self):
        salt = b'\xce\xb1\xce\xb2\xce\xb3\xce\xb4'.decode('utf-8')
        ser = self.makeOne('secret', salt)

        assert ser.loads(serialize('secret', salt, 'test')) == 'test'

    # bw-compat with webob <= 1.3.1 where secrets were encoded with latin-1
    def test_with_latin1_secret(self):
        secret = b'La Pe\xc3\xb1a'
        ser = self.makeOne(secret.decode('latin-1'), 'salty')

        assert ser.loads(serialize(secret, 'salty', 'test')), 'test'

    # bw-compat with webob <= 1.3.1 where salts were encoded with latin-1
    def test_with_latin1_salt(self):
        salt = b'La Pe\xc3\xb1a'
        ser = self.makeOne('secret', salt.decode('latin-1'))

        assert ser.loads(serialize('secret', salt, 'test')) == 'test'
