blob: 3da8528dbc5a22cbbd9608699d642f8abad21de7 [file] [edit]
from copy import deepcopy
import re
import warnings
import pytest
from webob.acceptparse import (
Accept,
AcceptCharset,
AcceptEncoding,
AcceptLanguage,
AcceptOffer,
HeaderState,
_item_n_weight_re,
_list_1_or_more__compiled_re,
accept_charset_property,
accept_encoding_property,
accept_language_property,
accept_property,
create_accept_charset_header,
create_accept_encoding_header,
create_accept_header,
create_accept_language_header,
)
from webob.request import Request
class StringMe:
def __init__(self, value):
self.value = value
def __str__(self):
return self.value
class Test_ItemNWeightRe:
@pytest.mark.parametrize(
"header_value",
[
"q=",
"q=1",
";q",
";q=",
";q=1",
"foo;",
"foo;q",
"foo;q1",
"foo;q=",
"foo;q=-1",
"foo;q=2",
"foo;q=1.001",
"foo;q=0.0001",
"foo;q=00",
"foo;q=01",
"foo;q=00.1",
"foo,q=0.1",
"foo;q =1",
"foo;q= 1",
],
)
def test_invalid(self, header_value):
regex = _item_n_weight_re(item_re="foo")
assert re.match("^" + regex + "$", header_value, re.VERBOSE) is None
@pytest.mark.parametrize(
"header_value, groups",
[
("foo", ("foo", None)),
("foo;q=0", ("foo", "0")),
("foo;q=0.0", ("foo", "0.0")),
("foo;q=0.00", ("foo", "0.00")),
("foo;q=0.000", ("foo", "0.000")),
("foo;q=1", ("foo", "1")),
("foo;q=1.0", ("foo", "1.0")),
("foo;q=1.00", ("foo", "1.00")),
("foo;q=1.000", ("foo", "1.000")),
("foo;q=0.1", ("foo", "0.1")),
("foo;q=0.87", ("foo", "0.87")),
("foo;q=0.382", ("foo", "0.382")),
("foo;Q=0.382", ("foo", "0.382")),
("foo ;Q=0.382", ("foo", "0.382")),
("foo; Q=0.382", ("foo", "0.382")),
("foo ; Q=0.382", ("foo", "0.382")),
],
)
def test_valid(self, header_value, groups):
regex = _item_n_weight_re(item_re="foo")
assert re.match("^" + regex + "$", header_value, re.VERBOSE).groups() == groups
class Test_List1OrMoreCompiledRe:
@pytest.mark.parametrize(
"header_value",
[
# RFC 7230 Section 7
",",
", ,",
# RFC 7230 Errata ID: 4169
"foo , ,bar,charlie ",
# Our tests
" foo , ,bar,charlie",
" ,foo , ,bar,charlie",
",foo , ,bar,charlie, ",
"\tfoo , ,bar,charlie",
"\t,foo , ,bar,charlie",
",foo , ,bar,charlie\t",
",foo , ,bar,charlie,\t",
],
)
def test_invalid(self, header_value):
regex = _list_1_or_more__compiled_re(element_re="([a-z]+)")
assert regex.match(header_value) is None
@pytest.mark.parametrize(
"header_value",
[
# RFC 7230 Section 7
"foo,bar",
"foo, bar,",
# RFC 7230 Errata ID: 4169
"foo , ,bar,charlie",
# Our tests
"foo , ,bar,charlie",
",foo , ,bar,charlie",
",foo , ,bar,charlie,",
",\t ,,, \t \t, ,\t\t\t,foo \t\t,, bar, ,\tcharlie \t,, ,",
],
)
def test_valid(self, header_value):
regex = _list_1_or_more__compiled_re(element_re="([a-z]+)")
assert regex.match(header_value)
class TestAccept__parsing:
@pytest.mark.parametrize(
"value",
[
", ",
", , ",
"noslash",
"/",
"text/",
"/html",
"text/html;",
"text/html;param",
"text/html;param=",
"text/html ;param=val;",
"text/html; param=val;",
"text/html;param=val;",
"text/html;param=\x19",
"text/html;param=\x22",
"text/html;param=\x5c",
"text/html;param=\x7f",
r'text/html;param="\"',
r'text/html;param="\\\"',
r'text/html;param="\\""',
'text/html;param="\\\x19"',
'text/html;param="\\\x7f"',
"text/html;q",
"text/html;q=",
"text/html;q=-1",
"text/html;q=2",
"text/html;q=1.001",
"text/html;q=0.0001",
"text/html;q=00",
"text/html;q=01",
"text/html;q=00.1",
"text/html,q=0.1",
"text/html;q =1",
"text/html;q= 1",
"text/html;q=1;",
"text/html;param;q=1",
"text/html;q=1;extparam;",
"text/html;q=1;extparam=val;",
'text/html;q=1;extparam="val";',
'text/html;q=1;extparam="',
'text/html;q=1;extparam="val',
'text/html;q=1;extparam=val"',
"text/html;q=1;extparam=\x19",
"text/html;q=1;extparam=\x22",
"text/html;q=1;extparam=\x5c",
"text/html;q=1;extparam=\x7f",
r'text/html;q=1;extparam="\"',
r'text/html;q=1;extparam="\\\"',
r'text/html;q=1;extparam="\\""',
'text/html;q=1;extparam="\\\x19"',
'text/html;q=1;extparam="\\\x7f"',
"text/html;param=\x19;q=1;extparam",
"text/html;param=val;q=1;extparam=\x19",
],
)
def test_parse__invalid_header(self, value):
with pytest.raises(ValueError):
Accept.parse(value=value)
@pytest.mark.parametrize(
"value, expected_list",
[
# Examples from RFC 7231, Section 5.3.2 "Accept":
(
"audio/*; q=0.2, audio/basic",
[("audio/*", 0.2, (), ()), ("audio/basic", 1.0, (), ())],
),
(
"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c",
[
("text/plain", 0.5, (), ()),
("text/html", 1.0, (), ()),
("text/x-dvi", 0.8, (), ()),
("text/x-c", 1.0, (), ()),
],
),
(
"text/*, text/plain, text/plain;format=flowed, */*",
[
("text/*", 1.0, (), ()),
("text/plain", 1.0, (), ()),
("text/plain;format=flowed", 1.0, (("format", "flowed"),), ()),
("*/*", 1.0, (), ()),
],
),
(
"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
"text/html;level=2;q=0.4, */*;q=0.5",
[
("text/*", 0.3, (), ()),
("text/html", 0.7, (), ()),
("text/html;level=1", 1.0, (("level", "1"),), ()),
("text/html;level=2", 0.4, (("level", "2"),), ()),
("*/*", 0.5, (), ()),
],
),
# Our tests
("", []),
(",", []),
(", ,", []),
(
"*/*, text/*, text/html",
[
("*/*", 1.0, (), ()),
("text/*", 1.0, (), ()),
("text/html", 1.0, (), ()),
],
),
# It does not seem from RFC 7231, section 5.3.2 "Accept" that the '*'
# in a range like '*/html' was intended to have any special meaning
# (the section lists '*/*', 'type/*' and 'type/subtype', but not
# '*/subtype'). However, because type and subtype are tokens (section
# 3.1.1.1), and a token may contain '*'s, '*/subtype' is valid.
("*/html", [("*/html", 1.0, (), ())]),
(
'text/html \t;\t param1=val1\t; param2="val2" ' + r'; param3="\"\\\\"',
[
(
r'text/html;param1=val1;param2=val2;param3="\"\\\\"',
1.0,
(("param1", "val1"), ("param2", "val2"), ("param3", r'"\\')),
(),
)
],
),
(
"text/html;param=!#$%&'*+-.^_`|~09AZaz",
[
(
"text/html;param=!#$%&'*+-.^_`|~09AZaz",
1.0,
(("param", "!#$%&'*+-.^_`|~09AZaz"),),
(),
)
],
),
('text/html;param=""', [('text/html;param=""', 1.0, (("param", ""),), ())]),
(
'text/html;param="\t \x21\x23\x24\x5a\x5b\x5d\x5e\x7d\x7e"',
[
(
'text/html;param="\t \x21\x23\x24\x5a\x5b\x5d\x5e\x7d\x7e"',
1.0,
(("param", "\t \x21\x23\x24\x5a\x5b\x5d\x5e\x7d\x7e"),),
(),
)
],
),
(
'text/html;param="\x80\x81\xfe\xff\\\x22\\\x5c"',
[
(
'text/html;param="\x80\x81\xfe\xff\\\x22\\\x5c"',
1.0,
(("param", "\x80\x81\xfe\xff\x22\x5c"),),
(),
)
],
),
(
'text/html;param="\\\t\\ \\\x21\\\x7e\\\x80\\\xff"',
[
(
'text/html;param="\t \x21\x7e\x80\xff"',
1.0,
(("param", "\t \x21\x7e\x80\xff"),),
(),
)
],
),
(
"text/html;param='val'",
# This makes it look like the media type parameter value could be
# surrounded with single quotes instead of double quotes, but the
# single quotes are actually part of the media type parameter value
# token
[("text/html;param='val'", 1.0, (("param", "'val'"),), ())],
),
("text/html;q=0.9", [("text/html", 0.9, (), ())]),
("text/html;q=0", [("text/html", 0.0, (), ())]),
("text/html;q=0.0", [("text/html", 0.0, (), ())]),
("text/html;q=0.00", [("text/html", 0.0, (), ())]),
("text/html;q=0.000", [("text/html", 0.0, (), ())]),
("text/html;q=1", [("text/html", 1.0, (), ())]),
("text/html;q=1.0", [("text/html", 1.0, (), ())]),
("text/html;q=1.00", [("text/html", 1.0, (), ())]),
("text/html;q=1.000", [("text/html", 1.0, (), ())]),
("text/html;q=0.1", [("text/html", 0.1, (), ())]),
("text/html;q=0.87", [("text/html", 0.87, (), ())]),
("text/html;q=0.382", [("text/html", 0.382, (), ())]),
("text/html;Q=0.382", [("text/html", 0.382, (), ())]),
("text/html ;Q=0.382", [("text/html", 0.382, (), ())]),
("text/html; Q=0.382", [("text/html", 0.382, (), ())]),
("text/html ; Q=0.382", [("text/html", 0.382, (), ())]),
("text/html;q=0.9;q=0.8", [("text/html", 0.9, (), (("q", "0.8"),))]),
(
"text/html;q=1;q=1;q=1",
[("text/html", 1.0, (), (("q", "1"), ("q", "1")))],
),
(
'text/html;q=0.9;extparam1;extparam2=val2;extparam3="val3"',
[
(
"text/html",
0.9,
(),
("extparam1", ("extparam2", "val2"), ("extparam3", "val3")),
)
],
),
(
"text/html;q=1;extparam=!#$%&'*+-.^_`|~09AZaz",
[("text/html", 1.0, (), (("extparam", "!#$%&'*+-.^_`|~09AZaz"),))],
),
(
'text/html;q=1;extparam=""',
[("text/html", 1.0, (), (("extparam", ""),))],
),
(
'text/html;q=1;extparam="\t \x21\x23\x24\x5a\x5b\x5d\x5e\x7d\x7e"',
[
(
"text/html",
1.0,
(),
(("extparam", "\t \x21\x23\x24\x5a\x5b\x5d\x5e\x7d\x7e"),),
)
],
),
(
'text/html;q=1;extparam="\x80\x81\xfe\xff\\\x22\\\x5c"',
[("text/html", 1.0, (), (("extparam", "\x80\x81\xfe\xff\x22\x5c"),))],
),
(
'text/html;q=1;extparam="\\\t\\ \\\x21\\\x7e\\\x80\\\xff"',
[("text/html", 1.0, (), (("extparam", "\t \x21\x7e\x80\xff"),))],
),
(
"text/html;q=1;extparam='val'",
# This makes it look like the extension parameter value could be
# surrounded with single quotes instead of double quotes, but the
# single quotes are actually part of the extension parameter value
# token
[("text/html", 1.0, (), (("extparam", "'val'"),))],
),
(
'text/html;param1="val1";param2=val2;q=0.9;extparam1="val1"'
";extparam2;extparam3=val3",
[
(
"text/html;param1=val1;param2=val2",
0.9,
(("param1", "val1"), ("param2", "val2")),
(("extparam1", "val1"), "extparam2", ("extparam3", "val3")),
)
],
),
(
", ,, a/b \t;\t p1=1 ;\t\tp2=2 ; q=0.6\t \t;\t\t e1\t; e2, ,",
[("a/b;p1=1;p2=2", 0.6, (("p1", "1"), ("p2", "2")), ("e1", "e2"))],
),
(
(
",\t , a/b;q=1;e1;e2=v2 \t,\t\t c/d, e/f;p1=v1;q=0;e1, "
+ "g/h;p1=v1\t ;\t\tp2=v2;q=0.5 \t,"
),
[
("a/b", 1.0, (), ("e1", ("e2", "v2"))),
("c/d", 1.0, (), ()),
("e/f;p1=v1", 0.0, (("p1", "v1"),), ("e1",)),
("g/h;p1=v1;p2=v2", 0.5, (("p1", "v1"), ("p2", "v2")), ()),
],
),
],
)
def test_parse__valid_header(self, value, expected_list):
returned = Accept.parse(value=value)
list_of_returned = list(returned)
assert list_of_returned == expected_list
@pytest.mark.parametrize(
"offer, expected_return, expected_str",
[
["text/html", ("text", "html", ()), "text/html"],
[
"text/html;charset=utf8",
("text", "html", (("charset", "utf8"),)),
"text/html;charset=utf8",
],
[
"text/html;charset=utf8;x-version=1",
("text", "html", (("charset", "utf8"), ("x-version", "1"))),
"text/html;charset=utf8;x-version=1",
],
[
"text/HtMl;cHaRseT=UtF-8;X-Version=1",
("text", "html", (("charset", "UtF-8"), ("x-version", "1"))),
"text/html;charset=UtF-8;x-version=1",
],
],
)
def test_parse_offer__valid(self, offer, expected_return, expected_str):
result = Accept.parse_offer(offer)
assert result == expected_return
assert str(result) == expected_str
assert result is Accept.parse_offer(result)
@pytest.mark.parametrize(
"offer",
[
"",
"foo",
"foo/bar/baz",
"*/plain",
"*/plain;charset=utf8",
"*/plain;charset=utf8;x-version=1",
"*/*;charset=utf8",
"text/*;charset=utf8",
"text/*",
"*/*",
],
)
def test_parse_offer__invalid(self, offer):
with pytest.raises(ValueError):
Accept.parse_offer(offer)
class TestAccept__valid:
def test___init___(self):
header_value = (
",\t , a/b;q=1;e1;e2=v2 \t,\t\t c/d, e/f;p1=v1;q=0;e1, "
+ "g/h;p1=v1\t ;\t\tp2=v2;q=0.5 \t,"
)
instance = Accept(header_value)
assert instance.header_state == HeaderState.Valid
assert instance.header_value == header_value
assert instance.parsed == (
("a/b", 1.0, (), ("e1", ("e2", "v2"))),
("c/d", 1.0, (), ()),
("e/f;p1=v1", 0.0, (("p1", "v1"),), ("e1",)),
("g/h;p1=v1;p2=v2", 0.5, (("p1", "v1"), ("p2", "v2")), ()),
)
def test___bool__(self):
instance = Accept("type/subtype")
returned = bool(instance)
assert returned is True
@pytest.mark.parametrize(
"header_value, expected_returned",
[
("", "<Accept('')>"),
(
r',,text/html ; p1="\"\1\"" ; q=0.50; e1=1 ;e2 , text/plain ,',
r"""<Accept('text/html;p1="\\"1\\"";q=0.5;e1=1;e2"""
+ ", text/plain')>",
),
(
',\t, a/b ; p1=1 ; p2=2 ;\t q=0.20 ;\te1="\\"\\1\\""\t; e2 ; '
+ "e3=3, c/d ,,",
r"""<Accept('a/b;p1=1;p2=2;q=0.2;e1="\\"1\\"";e2""" + ";e3=3, c/d')>",
),
],
)
def test___repr__(self, header_value, expected_returned):
instance = Accept(header_value)
assert repr(instance) == expected_returned
@pytest.mark.parametrize(
"header_value, expected_returned",
[
("", ""),
(
r',,text/html ; p1="\"\1\"" ; q=0.50; e1=1 ;e2 , text/plain ,',
r'text/html;p1="\"1\"";q=0.5;e1=1;e2, text/plain',
),
(
',\t, a/b ; p1=1 ; p2=2 ;\t q=0.20 ;\te1="\\"\\1\\""\t; e2 ; '
+ "e3=3, c/d ,,",
'a/b;p1=1;p2=2;q=0.2;e1="\\"1\\"";e2;e3=3, c/d',
),
],
)
def test___str__(self, header_value, expected_returned):
instance = Accept(header_value)
assert str(instance) == expected_returned
def test_copy(self):
instance = Accept("*/plain;charset=utf8;x-version=1")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
@pytest.mark.parametrize(
"header_value, returned",
[
("tExt/HtMl", True),
("APPlication/XHTML+xml", True),
("appliCATION/xMl", True),
("TeXt/XmL", True),
("image/jpg", False),
("TeXt/Plain", False),
("image/jpg, text/html", True),
],
)
def test_accept_html(self, header_value, returned):
instance = Accept(header_value)
assert instance.accept_html() is returned
@pytest.mark.parametrize(
"header_value, returned",
[
("tExt/HtMl", True),
("APPlication/XHTML+xml", True),
("appliCATION/xMl", True),
("TeXt/XmL", True),
("image/jpg", False),
("TeXt/Plain", False),
("image/jpg, text/html", True),
],
)
def test_accepts_html(self, header_value, returned):
instance = Accept(header_value)
assert instance.accepts_html is returned
@pytest.mark.parametrize(
"offers, expected_returned",
[
(["text/html;p=1;q=0.5"], []),
(["text/html;q=0.5"], []),
(["text/html;q=0.5;e=1"], []),
(["text/html", "text/plain;p=1;q=0.5;e=1", "foo"], [("text/html", 1.0)]),
],
)
def test_acceptable_offers__invalid_offers(self, offers, expected_returned):
assert Accept("text/html").acceptable_offers(offers=offers) == expected_returned
@pytest.mark.parametrize(
"header_value, offers, expected_returned",
[
# RFC 7231, section 5.3.2
(
"audio/*; q=0.2, audio/basic",
["audio/mpeg", "audio/basic"],
[("audio/basic", 1.0), ("audio/mpeg", 0.2)],
),
(
"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c",
["text/x-dvi", "text/x-c", "text/html", "text/plain"],
[
("text/x-c", 1.0),
("text/html", 1.0),
("text/x-dvi", 0.8),
("text/plain", 0.5),
],
),
(
"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5",
[
"text/html;level=1",
"text/html",
"text/plain",
"image/jpeg",
"text/html;level=2",
"text/html;level=3",
],
[
("text/html;level=1", 1.0),
("text/html", 0.7),
("text/html;level=3", 0.7),
("image/jpeg", 0.5),
("text/html;level=2", 0.4),
("text/plain", 0.3),
],
),
# Our tests
(
"teXT/*;Q=0.5, TeXt/hTmL;LeVeL=1",
["tExT/HtMl;lEvEl=1", "TExt/PlAiN"],
[("tExT/HtMl;lEvEl=1", 1.0), ("TExt/PlAiN", 0.5)],
),
(
"text/html, application/json",
["text/html", "application/json"],
[("text/html", 1.0), ("application/json", 1.0)],
),
(
"text/html ;\t level=1",
["text/html\t\t ; \tlevel=1"],
[("text/html\t\t ; \tlevel=1", 1.0)],
),
("", ["text/html"], []),
("text/html, image/jpeg", ["audio/basic", "text/plain"], []),
(
r'text/html;p1=1;p2=2;p3="\""',
[r'text/html;p1=1;p2="2";p3="\""'],
[(r'text/html;p1=1;p2="2";p3="\""', 1.0)],
),
("text/html;p1=1", ["text/html;p1=2"], []),
("text/html", ["text/html;p1=1"], [("text/html;p1=1", 1.0)]),
("text/html;p1=1", ["text/html"], []),
("text/html", ["text/html"], [("text/html", 1.0)]),
("text/*", ["text/html;p=1"], [("text/html;p=1", 1.0)]),
("*/*", ["text/html;p=1"], [("text/html;p=1", 1.0)]),
("text/*", ["text/html"], [("text/html", 1.0)]),
("*/*", ["text/html"], [("text/html", 1.0)]),
("text/html;p1=1;q=0", ["text/html;p1=1"], []),
("text/html;q=0", ["text/html;p1=1", "text/html"], []),
("text/*;q=0", ["text/html;p1=1", "text/html", "text/plain"], []),
(
"*/*;q=0",
["text/html;p1=1", "text/html", "text/plain", "image/jpeg"],
[],
),
(
"*/*;q=0, audio/mpeg",
[
"text/html;p1=1",
"audio/mpeg",
"text/html",
"text/plain",
"image/jpeg",
],
[("audio/mpeg", 1.0)],
),
(
"text/html;p1=1, text/html;q=0",
["text/html;p1=1"],
[("text/html;p1=1", 1.0)],
),
("text/html, text/*;q=0", ["text/html"], [("text/html", 1.0)]),
("text/*, */*;q=0", ["text/html"], [("text/html", 1.0)]),
("text/html;q=0, text/html", ["text/html"], []),
(
"text/html",
["text/html;level=1", "text/html", "text/html;level=2"],
[
("text/html;level=1", 1.0),
("text/html", 1.0),
("text/html;level=2", 1.0),
],
),
(
"text/*;q=0.3, text/html;q=0, image/png, text/html;level=1, "
+ "text/html;level=2;q=0.4, image/jpeg;q=0.5",
[
"text/html;level=1",
"text/html",
"text/plain",
"image/jpeg",
"text/html;level=2",
"text/html;level=3",
"audio/basic",
],
[
("text/html;level=1", 1.0),
("image/jpeg", 0.5),
("text/html;level=2", 0.4),
("text/plain", 0.3),
],
),
(
"text/*;q=0.3, text/html;q=0.5, text/html;level=1;q=0.7",
["text/*", "*/*", "text/html", "image/*"],
[("text/html", 0.5)],
),
(
"text/html;level=1;q=0.7",
["text/*", "*/*", "text/html", "text/html;level=1", "image/*"],
[("text/html;level=1", 0.7)],
),
("*/*", ["text/*"], []),
("", ["text/*", "*/*", "text/html", "text/html;level=1", "image/*"], []),
],
)
def test_acceptable_offers__valid_offers(
self, header_value, offers, expected_returned
):
instance = Accept(header_value)
returned = instance.acceptable_offers(offers=offers)
assert returned == expected_returned
def test_acceptable_offers_uses_AcceptOffer_objects(self):
offer = AcceptOffer("text", "html", (("level", "1"),))
instance = Accept("text/*;q=0.5")
result = instance.acceptable_offers([offer])
assert result == [(offer, 0.5)]
def test_best_match(self):
accept = Accept("text/html, foo/bar")
assert accept.best_match(["text/html", "foo/bar"]) == "text/html"
assert accept.best_match(["foo/bar", "text/html"]) == "foo/bar"
def test_best_match_with_one_lower_q(self):
accept = Accept("text/html, foo/bar;q=0.5")
assert accept.best_match(["text/html", "foo/bar"]) == "text/html"
accept = Accept("text/html;q=0.5, foo/bar")
assert accept.best_match(["text/html", "foo/bar"]) == "foo/bar"
def test_best_match_with_complex_q(self):
accept = Accept("text/html, foo/bar;q=0.55, baz/gort;q=0.59")
assert accept.best_match(["text/html", "foo/bar"]) == "text/html"
accept = Accept("text/html;q=0.5, foo/bar;q=0.586, baz/gort;q=0.596")
assert accept.best_match(["text/html", "baz/gort"]) == "baz/gort"
def test_best_match_json(self):
accept = Accept("text/html, */*; q=0.2")
assert accept.best_match(["application/json"]) == "application/json"
def test_best_match_mixedcase(self):
accept = Accept("image/jpg; q=0.2, Image/pNg; Q=0.4, image/*; q=0.05")
assert accept.best_match(["Image/JpG"]) == "Image/JpG"
assert accept.best_match(["image/Tiff"]) == "image/Tiff"
assert (
accept.best_match(["image/Tiff", "image/PnG", "image/jpg"]) == "image/PnG"
)
def test_quality(self):
accept = Accept("text/html")
assert accept.quality("text/html") == 1
accept = Accept("text/html;q=0.5")
assert accept.quality("text/html") == 0.5
accept = Accept("text/*;q=0.5, text/html")
assert accept.quality("text/html") == 1
assert accept.quality("text/bar") == 0.5
def test_quality_not_found(self):
accept = Accept("text/html")
assert accept.quality("foo/bar") is None
def test___contains__(self):
accept = Accept("A/a, B/b, C/c, B/x;q=0")
assert "A/a" in accept
assert "A/b" not in accept
assert "B/a" not in accept
assert "B/x" not in accept
for mask in ["*/*", "text/html", "TEXT/HTML"]:
assert "text/html" in Accept(mask)
class TestAccept__missing:
def test___init__(self):
instance = Accept(None)
assert instance.header_value is None
assert instance.parsed is None
assert instance.header_state == HeaderState.Missing
def test___bool__(self):
instance = Accept(None)
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = Accept(None)
assert repr(instance) == "<Accept: Missing>"
def test___str__(self):
instance = Accept(None)
assert str(instance) == "<no header in request>"
def test_copy(self):
instance = Accept(None)
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_accept_html(self):
instance = Accept(None)
assert instance.accept_html() is True
def test_accepts_html(self):
instance = Accept(None)
assert instance.accepts_html is True
@pytest.mark.parametrize(
"offers, expected_returned",
[
(["text/html;p=1;q=0.5"], []),
(["text/html;q=0.5"], []),
(["text/html;q=0.5;e=1"], []),
(["text/html", "text/plain;p=1;q=0.5;e=1", "foo"], [("text/html", 1.0)]),
],
)
def test_acceptable_offers__invalid_offers(self, offers, expected_returned):
result = Accept(None).acceptable_offers(offers=offers)
assert result == expected_returned
def test_acceptable_offers__valid_offers(self):
instance = Accept(None)
returned = instance.acceptable_offers(offers=["a/b", "c/d", "e/f"])
assert returned == [("a/b", 1.0), ("c/d", 1.0), ("e/f", 1.0)]
def test_best_match(self):
accept = Accept(None)
assert accept.best_match(["text/html", "audio/basic"]) == "text/html"
assert accept.best_match(["audio/basic", "text/html"]) == "audio/basic"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
accept = Accept(None)
assert accept.quality("text/subtype") == 1.0
def test___contains__(self):
accept = Accept(None)
assert "text/subtype" in accept
class TestAccept__invalid:
def test___init__(self):
header_value = ", "
instance = Accept(header_value)
assert instance.header_value == header_value
assert instance.parsed is None
assert instance.header_state == HeaderState.Invalid
def test___bool__(self):
instance = Accept(", ")
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = Accept("\x00")
assert repr(instance) == "<Accept: Invalid>"
def test___str__(self):
instance = Accept(", ")
assert str(instance) == "<invalid header value>"
def test_copy(self):
instance = Accept(", ")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_accept_html(self):
instance = Accept(", ")
assert instance.accept_html() is True
def test_accepts_html(self):
instance = Accept(", ")
assert instance.accepts_html is True
@pytest.mark.parametrize(
"offers, expected_returned",
[
(["text/html;p=1;q=0.5"], []),
(["text/html;q=0.5"], []),
(["text/html;q=0.5;e=1"], []),
(["text/html", "text/plain;p=1;q=0.5;e=1", "foo"], [("text/html", 1.0)]),
],
)
def test_acceptable_offers__invalid_offers(self, offers, expected_returned):
assert Accept(", ").acceptable_offers(offers=offers) == expected_returned
def test_acceptable_offers__valid_offers(self):
instance = Accept(", ")
returned = instance.acceptable_offers(offers=["a/b", "c/d", "e/f"])
assert returned == [("a/b", 1.0), ("c/d", 1.0), ("e/f", 1.0)]
def test_best_match(self):
accept = Accept(", ")
assert accept.best_match(["text/html", "audio/basic"]) == "text/html"
assert accept.best_match(["audio/basic", "text/html"]) == "audio/basic"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
accept = Accept(None)
assert accept.quality("text/subtype") == 1.0
def test___contains__(self):
accept = Accept(", ")
assert "text/subtype" in accept
class TestAccept__add:
invalid_values = [
", ",
[", "],
(", ",),
{", ": 1.0},
{", ;level=1": (1.0, ";e1=1")},
"a/b, c/d;q=1;e1;",
["a/b", "c/d;q=1;e1;"],
("a/b", "c/d;q=1;e1;"),
{"a/b": 1.0, "cd": 1.0},
{"a/b": (1.0, ";e1=1"), "c/d": (1.0, ";e2=2;")},
StringMe(", "),
StringMe("a/b, c/d;q=1;e1;"),
]
valid_nonempty_values_with_headers = [
# str
(
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# object with __str__
(
StringMe("a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1"),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of strs
(
["a/b;q=0.5", "c/d;p1=1;q=0", "e/f", "g/h;p1=1;q=1;e1=1"],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of 3-item tuples, with extension parameters
(
[
("a/b", 0.5, ""),
("c/d;p1=1", 0.0, ""),
("e/f", 1.0, ""),
("g/h;p1=1", 1.0, ";e1=1"),
],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of 2-item tuples, without extension parameters
(
[("a/b", 0.5), ("c/d;p1=1", 0.0), ("e/f", 1.0), ("g/h;p1=1", 1.0)],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1",
),
# list of a mixture of strs, 3-item tuples and 2-item tuples
(
[
("a/b", 0.5),
("c/d;p1=1", 0.0, ""),
"e/f",
("g/h;p1=1", 1.0, ";e1=1"),
],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of strs
(
("a/b;q=0.5", "c/d;p1=1;q=0", "e/f", "g/h;p1=1;q=1;e1=1"),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of 3-item tuples, with extension parameters
(
(
("a/b", 0.5, ""),
("c/d;p1=1", 0.0, ""),
("e/f", 1.0, ""),
("g/h;p1=1", 1.0, ";e1=1"),
),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of 2-item tuples, without extension parameters
(
(("a/b", 0.5), ("c/d;p1=1", 0.0), ("e/f", 1.0), ("g/h;p1=1", 1.0)),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1",
),
# tuple of a mixture of strs, 3-item tuples and 2-item tuples
(
(
("a/b", 0.5),
("c/d;p1=1", 0.0, ""),
"e/f",
("g/h;p1=1", 1.0, ";e1=1"),
),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# dict
(
{"a/b": (0.5, ";e1=1"), "c/d": 0.0, "e/f;p1=1": (1.0, ";e1=1;e2=2")},
"e/f;p1=1;q=1;e1=1;e2=2, a/b;q=0.5;e1=1, c/d;q=0",
),
]
valid_empty_values = ["", [], (), {}, StringMe("")]
valid_values_with_headers = valid_nonempty_values_with_headers + [
[x, ""] for x in valid_empty_values
]
# snapshots help confirm the instance is immutable
def snapshot_instance(self, inst):
return deepcopy(
{
"header_value": inst.header_value,
"parsed": inst.parsed,
"header_state": inst.header_state,
}
)
# we want to test math with primitive python values and Accept instances
@pytest.fixture(params=["primitive", "instance"])
def maker(self, request):
if request.param == "primitive":
return lambda x: x
return Accept
# almost always add and radd are symmetrical so we can test both and
# expect the same result
@pytest.fixture(params=["add", "radd"])
def fn(self, request):
if request.param == "add":
return lambda x, y: x + y
return lambda x, y: y + x
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_missing(self, input_value, input_header, maker, fn):
inst = Accept(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
def test_invalid_add_missing(self, maker, fn):
inst = Accept("invalid")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == "invalid"
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
def test_missing_add_missing(self, maker, fn):
inst = Accept(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("valid_value, valid_header", valid_values_with_headers)
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_valid_add_invalid(
self, valid_value, valid_header, invalid_value, maker, fn
):
inst = Accept(valid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == valid_header
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == valid_header
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_invalid_add_invalid(self, invalid_value, maker, fn):
inst = Accept("invalid")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == "invalid"
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_missing_add_invalid(self, invalid_value, maker, fn):
inst = Accept(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize(
"input_value, input_header",
valid_nonempty_values_with_headers,
)
def test_nonempty_valid_add_valid(self, input_value, input_header, maker):
seed_value = ",\t ,i/j, k/l;q=0.333,"
inst = Accept(seed_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == seed_value
result = inst + maker(input_value)
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == seed_value + ", " + input_header
result = maker(input_value) + inst
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header + ", " + seed_value
@pytest.mark.parametrize(
"input_value, input_header",
valid_nonempty_values_with_headers,
)
@pytest.mark.parametrize("empty_value", valid_empty_values)
def test_nonempty_valid_add_empty(
self, input_value, input_header, empty_value, maker, fn
):
inst = Accept(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(empty_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
@pytest.mark.parametrize("empty_value", valid_empty_values)
def test_empty_valid_add_empty(self, empty_value, maker, fn):
expected_value = ""
inst = Accept(empty_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == expected_value
result = fn(inst, maker(empty_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == expected_value
class TestCreateAccept:
def test_header_value_is_None(self):
returned = create_accept_header(None)
assert returned.header_state == HeaderState.Missing
assert returned.header_value is None
returned2 = create_accept_header(returned)
assert returned2 is returned
assert returned2.header_value is None
def test_header_value_is_valid(self):
header_value = "text/html, text/plain;q=0.9"
returned = create_accept_header(header_value)
assert returned.header_state == HeaderState.Valid
assert returned.header_value == header_value
returned2 = create_accept_header(returned)
assert returned2 is returned
assert returned2.header_value == header_value
@pytest.mark.parametrize("header_value", [", ", "noslash"])
def test_header_value_is_invalid(self, header_value):
returned = create_accept_header(header_value)
assert returned.header_state == HeaderState.Invalid
assert returned.header_value == header_value
returned2 = create_accept_header(returned)
assert returned2 is returned
assert returned2.header_value == header_value
class TestAcceptProperty:
def test_fget_header_is_valid(self):
header_value = 'text/html;p1="1";p2=v2;q=0.9;e1="1";e2, audio/basic'
request = Request.blank("/", environ={"HTTP_ACCEPT": header_value})
property_ = accept_property()
returned = property_.fget(request=request)
assert returned.header_state is HeaderState.Valid
assert returned.header_value == header_value
def test_fget_header_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": None})
property_ = accept_property()
returned = property_.fget(request=request)
assert returned.header_state is HeaderState.Missing
def test_fget_header_is_invalid(self):
header_value = "invalid"
request = Request.blank("/", environ={"HTTP_ACCEPT": header_value})
property_ = accept_property()
returned = property_.fget(request=request)
assert returned.header_state is HeaderState.Invalid
assert returned.header_value == header_value
def test_fset_value_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
header_value = 'text/html;p1="1";p2=v2;q=0.9;e1="1";e2, audio/basic'
property_ = accept_property()
property_.fset(request=request, value=header_value)
assert request.environ["HTTP_ACCEPT"] == header_value
def test_fset_value_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
property_ = accept_property()
property_.fset(request=request, value=None)
assert "HTTP_ACCEPT" not in request.environ
def test_fset_value_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
header_value = "invalid"
property_ = accept_property()
property_.fset(request=request, value=header_value)
assert request.environ["HTTP_ACCEPT"] == header_value
@pytest.mark.parametrize(
"value, value_as_header",
[
("", ""),
([], ""),
((), ""),
({}, ""),
# str
(
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of strs
(
["a/b;q=0.5", "c/d;p1=1;q=0", "e/f", "g/h;p1=1;q=1;e1=1"],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of 3-item tuples, with extension parameters
(
[
("a/b", 0.5, ""),
("c/d;p1=1", 0.0, ""),
("e/f", 1.0, ""),
("g/h;p1=1", 1.0, ";e1=1"),
],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# list of 2-item tuples, without extension parameters
(
[("a/b", 0.5), ("c/d;p1=1", 0.0), ("e/f", 1.0), ("g/h;p1=1", 1.0)],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1",
),
# list of a mixture of strs, 3-item tuples and 2-item tuples
(
[
("a/b", 0.5),
("c/d;p1=1", 0.0, ""),
"e/f",
("g/h;p1=1", 1.0, ";e1=1"),
],
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of strs
(
("a/b;q=0.5", "c/d;p1=1;q=0", "e/f", "g/h;p1=1;q=1;e1=1"),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of 3-item tuples, with extension parameters
(
(
("a/b", 0.5, ""),
("c/d;p1=1", 0.0, ""),
("e/f", 1.0, ""),
("g/h;p1=1", 1.0, ";e1=1"),
),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# tuple of 2-item tuples, without extension parameters
(
(("a/b", 0.5), ("c/d;p1=1", 0.0), ("e/f", 1.0), ("g/h;p1=1", 1.0)),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1",
),
# tuple of a mixture of strs, 3-item tuples and 2-item tuples
(
(
("a/b", 0.5),
("c/d;p1=1", 0.0, ""),
"e/f",
("g/h;p1=1", 1.0, ";e1=1"),
),
"a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1",
),
# dict
(
{"a/b": (0.5, ";e1=1"), "c/d": 0.0, "e/f;p1=1": (1.0, ";e1=1;e2=2")},
"e/f;p1=1;q=1;e1=1;e2=2, a/b;q=0.5;e1=1, c/d;q=0",
),
],
)
def test_fset_value_types(self, value, value_as_header):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
property_ = accept_property()
property_.fset(request=request, value=value)
assert request.environ["HTTP_ACCEPT"] == value_as_header
@pytest.mark.parametrize(
"header_value", ["", "a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1"]
)
def test_fset_other_type_with___str__(self, header_value):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
property_ = accept_property()
value = StringMe(header_value)
property_.fset(request=request, value=value)
assert request.environ["HTTP_ACCEPT"] == str(value)
def test_fset_valid_Accept(self):
request = Request.blank("/", environ={})
header_value = "a/b;q=0.5, c/d;p1=1;q=0, e/f, g/h;p1=1;q=1;e1=1"
header = Accept(header_value)
property_ = accept_property()
property_.fset(request=request, value=header)
assert request.environ["HTTP_ACCEPT"] == header.header_value
def test_fset_missing_Accept(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
property_ = accept_property()
header = Accept(None)
property_.fset(request=request, value=header)
assert "HTTP_ACCEPT" not in request.environ
def test_fset_invalid_Accept(self):
request = Request.blank("/", environ={})
header_value = "invalid"
header = Accept(header_value)
property_ = accept_property()
property_.fset(request=request, value=header)
assert request.environ["HTTP_ACCEPT"] == header.header_value
def test_fdel_header_key_in_environ(self):
request = Request.blank("/", environ={"HTTP_ACCEPT": "text/html"})
property_ = accept_property()
property_.fdel(request=request)
assert "HTTP_ACCEPT" not in request.environ
def test_fdel_header_key_not_in_environ(self):
request = Request.blank("/")
property_ = accept_property()
property_.fdel(request=request)
assert "HTTP_ACCEPT" not in request.environ
class TestAcceptCharset__parsing:
@pytest.mark.parametrize(
"value",
[
"",
'"',
"(",
")",
"/",
":",
";",
"<",
"=",
">",
"?",
"@",
"[",
"\\",
"]",
"{",
"}",
"foo, bar, baz;q= 0.001",
"foo , ,bar,charlie ",
],
)
def test_parse__invalid_header(self, value):
with pytest.raises(ValueError):
AcceptCharset.parse(value=value)
@pytest.mark.parametrize(
"value, expected_list",
[
("*", [("*", 1.0)]),
("!#$%&'*+-.^_`|~;q=0.5", [("!#$%&'*+-.^_`|~", 0.5)]),
("0123456789", [("0123456789", 1.0)]),
(
",\t foo \t;\t q=0.345,, bar ; Q=0.456 \t, ,\tcharlie \t,, ,",
[("foo", 0.345), ("bar", 0.456), ("charlie", 1.0)],
),
(
"iso-8859-5;q=0.372,unicode-1-1;q=0.977,UTF-8, *;q=0.000",
[
("iso-8859-5", 0.372),
("unicode-1-1", 0.977),
("UTF-8", 1.0),
("*", 0.0),
],
),
# RFC 7230 Section 7
("foo,bar", [("foo", 1.0), ("bar", 1.0)]),
("foo, bar,", [("foo", 1.0), ("bar", 1.0)]),
# RFC 7230 Errata ID: 4169
("foo , ,bar,charlie", [("foo", 1.0), ("bar", 1.0), ("charlie", 1.0)]),
],
)
def test_parse__valid_header(self, value, expected_list):
returned = AcceptCharset.parse(value=value)
list_of_returned = list(returned)
assert list_of_returned == expected_list
class TestAcceptCharset__valid:
def test_parse__inherited(self):
returned = AcceptCharset.parse(
value=",iso-8859-5 ; q=0.333 , ,utf-8,unicode-1-1 ;q=0.90,"
)
list_of_returned = list(returned)
assert list_of_returned == [
("iso-8859-5", 0.333),
("utf-8", 1.0),
("unicode-1-1", 0.9),
]
def test___init___valid_header(self):
header_value = "iso-8859-5;q=0.372,unicode-1-1;q=0.977,UTF-8, *;q=0.000"
instance = AcceptCharset(header_value)
assert instance.header_state is HeaderState.Valid
assert instance.header_value == header_value
assert instance.parsed == (
("iso-8859-5", 0.372),
("unicode-1-1", 0.977),
("UTF-8", 1.0),
("*", 0.0),
)
def test___bool__(self):
instance = AcceptCharset("valid-header")
returned = bool(instance)
assert returned is True
def test___repr__(self):
instance = AcceptCharset(",utf-7;q=0.200,UTF-8;q=0.300")
assert repr(instance) == "<AcceptCharset('utf-7;q=0.2, UTF-8;q=0.3')>"
def test___str__(self):
header_value = (
", \t,iso-8859-5;q=0.000 \t, utf-8;q=1.000, UTF-7, "
"unicode-1-1;q=0.210 ,"
)
instance = AcceptCharset(header_value)
assert str(instance) == "iso-8859-5;q=0, utf-8, UTF-7, unicode-1-1;q=0.21"
def test_copy(self):
instance = AcceptCharset(",iso-8859-5 ; q=0.333 , ,utf-8,unicode-1-1 ;q=0.90,")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
@pytest.mark.parametrize("header", ["*", "utf-8", "UTF-8"])
def test___contains__match(self, header):
assert "utf-8" in AcceptCharset(header)
def test___contains__not(self):
assert "utf-7" not in AcceptCharset("utf-8")
assert "utf-7" not in AcceptCharset("utf-7;q=0")
@pytest.mark.parametrize(
"header_value, offers, returned",
[
("UTF-7, unicode-1-1", ["UTF-8", "iso-8859-5"], []),
(
"utf-8, unicode-1-1, iSo-8859-5",
["UTF-8", "iso-8859-5"],
[("UTF-8", 1.0), ("iso-8859-5", 1.0)],
),
(
"utF-8;q=0.2, uniCode-1-1;q=0.9, iSo-8859-5;q=0.8",
["iso-8859-5", "unicode-1-1", "utf-8"],
[("unicode-1-1", 0.9), ("iso-8859-5", 0.8), ("utf-8", 0.2)],
),
(
"utf-8, unicode-1-1;q=0.9, iSo-8859-5;q=0.9",
["iso-8859-5", "utf-8", "unicode-1-1"],
[("utf-8", 1.0), ("iso-8859-5", 0.9), ("unicode-1-1", 0.9)],
),
("*", ["UTF-8", "iso-8859-5"], [("UTF-8", 1.0), ("iso-8859-5", 1.0)]),
("*;q=0.8", ["UTF-8", "iso-8859-5"], [("UTF-8", 0.8), ("iso-8859-5", 0.8)]),
("UTF-7, *", ["UTF-8", "UTF-7"], [("UTF-8", 1.0), ("UTF-7", 1.0)]),
("UTF-7;q=0.5, *", ["UTF-7", "UTF-8"], [("UTF-8", 1.0), ("UTF-7", 0.5)]),
("UTF-8, *;q=0", ["UTF-7"], []),
("UTF-8, *;q=0", ["UTF-8"], [("UTF-8", 1.0)]),
("UTF-8;q=0, *", ["UTF-8"], []),
("UTF-8;q=0, *;q=0", ["UTF-8", "UTF-7"], []),
("UTF-8, UTF-8;q=0", ["UTF-8"], [("UTF-8", 1.0)]),
(
"UTF-8, UTF-8;q=0, UTF-7",
["UTF-8", "UTF-7"],
[("UTF-8", 1.0), ("UTF-7", 1.0)],
),
(
"UTF-8;q=0.5, UTF-8;q=0.7, UTF-8;q=0.6, UTF-7",
["UTF-8", "UTF-7"],
[("UTF-7", 1.0), ("UTF-8", 0.5)],
),
(
"UTF-8;q=0.8, *;q=0.9, *;q=0",
["UTF-8", "UTF-7"],
[("UTF-7", 0.9), ("UTF-8", 0.8)],
),
("UTF-8;q=0.8, *;q=0, *;q=0.9", ["UTF-8", "UTF-7"], [("UTF-8", 0.8)]),
],
)
def test_acceptable_offers(self, header_value, offers, returned):
instance = AcceptCharset(header_value)
assert instance.acceptable_offers(offers=offers) == returned
def test_best_match(self):
accept = AcceptCharset("utf-8, iso-8859-5")
assert accept.best_match(["utf-8", "iso-8859-5"]) == "utf-8"
assert accept.best_match(["iso-8859-5", "utf-8"]) == "iso-8859-5"
def test_best_match_with_one_lower_q(self):
accept = AcceptCharset("utf-8, iso-8859-5;q=0.5")
assert accept.best_match(["utf-8", "iso-8859-5"]) == "utf-8"
accept = AcceptCharset("utf-8;q=0.5, iso-8859-5")
assert accept.best_match(["utf-8", "iso-8859-5"]) == "iso-8859-5"
def test_best_match_with_complex_q(self):
accept = AcceptCharset("utf-8, iso-8859-5;q=0.55, utf-7;q=0.59")
assert accept.best_match(["utf-8", "iso-8859-5"]) == "utf-8"
accept = AcceptCharset("utf-8;q=0.5, iso-8859-5;q=0.586, utf-7;q=0.596")
assert accept.best_match(["utf-8", "utf-7"]) == "utf-7"
def test_best_match_mixedcase(self):
accept = AcceptCharset("uTf-8; q=0.2, UtF-7; Q=0.4, *; q=0.05")
assert accept.best_match(["UtF-8"]) == "UtF-8"
assert accept.best_match(["IsO-8859-5"]) == "IsO-8859-5"
assert accept.best_match(["iSo-8859-5", "uTF-7", "UtF-8"]) == "uTF-7"
def test_best_match_zero_quality(self):
assert AcceptCharset("utf-7, *;q=0").best_match(["utf-8"]) is None
def test_quality(self):
accept = AcceptCharset("utf-8")
assert accept.quality("utf-8") == 1.0
accept = AcceptCharset("utf-8;q=0.5")
assert accept.quality("utf-8") == 0.5
def test_quality_not_found(self):
accept = AcceptCharset("utf-8")
assert accept.quality("iso-8859-5") is None
class TestAcceptCharset__missing:
def test___init__(self):
instance = AcceptCharset(None)
assert instance.header_state is HeaderState.Missing
assert instance.header_value is None
assert instance.parsed is None
def test___bool__(self):
instance = AcceptCharset(None)
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptCharset(None)
assert repr(instance) == "<AcceptCharset: Missing>"
def test___str__(self):
instance = AcceptCharset(None)
assert str(instance) == "<no header in request>"
def test_copy(self):
instance = AcceptCharset(None)
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test___contains__(self):
assert "char-set" in AcceptCharset(None)
def test_acceptable_offers(self):
instance = AcceptCharset(None)
returned = instance.acceptable_offers(offers=["utf-8", "utf-7", "unicode-1-1"])
assert returned == [("utf-8", 1.0), ("utf-7", 1.0), ("unicode-1-1", 1.0)]
def test_best_match(self):
accept = AcceptCharset(None)
assert accept.best_match(["utf-8", "iso-8859-5"]) == "utf-8"
assert accept.best_match(["iso-8859-5", "utf-8"]) == "iso-8859-5"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
instance = AcceptCharset(None)
returned = instance.quality(offer="char-set")
assert returned == 1.0
class TestAcceptCharset__invalid:
def test___init__(self):
header_value = "invalid header"
instance = AcceptCharset(header_value)
assert instance.header_state is HeaderState.Invalid
assert instance.header_value == header_value
assert instance.parsed is None
def test___bool__(self):
instance = AcceptCharset("")
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptCharset("\x00")
assert repr(instance) == "<AcceptCharset: Invalid>"
def test___str__(self):
instance = AcceptCharset("")
assert str(instance) == "<invalid header value>"
def test_copy(self):
instance = AcceptCharset("")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test___contains__(self):
assert "char-set" in AcceptCharset(None)
def test_acceptable_offers(self):
instance = AcceptCharset("")
returned = instance.acceptable_offers(offers=["utf-8", "utf-7", "unicode-1-1"])
assert returned == [("utf-8", 1.0), ("utf-7", 1.0), ("unicode-1-1", 1.0)]
def test_best_match(self):
accept = AcceptCharset("")
assert accept.best_match(["utf-8", "iso-8859-5"]) == "utf-8"
assert accept.best_match(["iso-8859-5", "utf-8"]) == "iso-8859-5"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
instance = AcceptCharset("")
returned = instance.quality(offer="char-set")
assert returned == 1.0
class TestAcceptCharset__add:
invalid_values = [
"",
[],
(),
{},
"UTF/8",
["UTF/8"],
("UTF/8",),
{"UTF/8": 1.0},
StringMe(""),
StringMe("UTF/8"),
]
valid_values_with_headers = [
(
"UTF-7;q=0.5, unicode-1-1;q=0, UTF-8",
"UTF-7;q=0.5, unicode-1-1;q=0, UTF-8",
),
(
[("UTF-7", 0.5), ("unicode-1-1", 0.0), "UTF-8"],
"UTF-7;q=0.5, unicode-1-1;q=0, UTF-8",
),
(
(("UTF-7", 0.5), ("unicode-1-1", 0.0), "UTF-8"),
"UTF-7;q=0.5, unicode-1-1;q=0, UTF-8",
),
(
{"UTF-7": 0.5, "unicode-1-1": 0.0, "UTF-8": 1.0},
"UTF-8, UTF-7;q=0.5, unicode-1-1;q=0",
),
(
StringMe("UTF-7;q=0.5, unicode-1-1;q=0, UTF-8"),
"UTF-7;q=0.5, unicode-1-1;q=0, UTF-8",
),
]
# snapshots help confirm the instance is immutable
def snapshot_instance(self, inst):
return deepcopy(
{
"header_value": inst.header_value,
"parsed": inst.parsed,
"header_state": inst.header_state,
}
)
# we want to test math with primitive python values and Accept instances
@pytest.fixture(params=["primitive", "instance"])
def maker(self, request):
if request.param == "primitive":
return lambda x: x
return AcceptCharset
# almost always add and radd are symmetrical so we can test both and
# expect the same result
@pytest.fixture(params=["add", "radd"])
def fn(self, request):
if request.param == "add":
return lambda x, y: x + y
return lambda x, y: y + x
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_missing(self, input_value, input_header, maker, fn):
inst = AcceptCharset(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
def test_invalid_add_missing(self, maker, fn):
invalid_value = "UTF/8"
inst = AcceptCharset(invalid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == invalid_value
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
def test_missing_add_missing(self, maker, fn):
inst = AcceptCharset(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("valid_value, valid_header", valid_values_with_headers)
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_valid_add_invalid(
self, valid_value, valid_header, invalid_value, maker, fn
):
inst = AcceptCharset(valid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == valid_header
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == valid_header
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_invalid_add_invalid(self, invalid_value, maker, fn):
inst = AcceptCharset("")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == ""
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_missing_add_invalid(self, invalid_value, maker, fn):
inst = AcceptCharset(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_valid(self, input_value, input_header, maker):
seed_value = "iso-8859-5"
inst = AcceptCharset(seed_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == seed_value
result = inst + maker(input_value)
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == seed_value + ", " + input_header
result = maker(input_value) + inst
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header + ", " + seed_value
class TestCreateAcceptCharsetHeader:
def test_header_value_is_valid(self):
header_value = "iso-8859-5, unicode-1-1;q=0.8"
returned = create_accept_charset_header(header_value)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Valid
assert returned.header_value == header_value
returned2 = create_accept_charset_header(returned)
assert returned2._header_value == returned._header_value
def test_header_value_is_None(self):
returned = create_accept_charset_header(None)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Missing
assert returned.header_value is None
returned2 = create_accept_charset_header(returned)
assert returned2._header_value is None
@pytest.mark.parametrize("header_value", ["", "iso-8859-5, unicode/1"])
def test_header_value_is_invalid(self, header_value):
returned = create_accept_charset_header(header_value)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Invalid
assert returned.header_value == header_value
returned2 = create_accept_charset_header(returned)
assert returned2._header_value == returned._header_value
class TestAcceptCharsetProperty:
def test_fget_header_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": None})
property_ = accept_charset_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Missing
def test_fget_header_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "UTF-8"})
property_ = accept_charset_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Valid
def test_fget_header_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": ""})
property_ = accept_charset_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptCharset)
assert returned.header_state is HeaderState.Invalid
def test_fset_value_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "UTF-8"})
property_ = accept_charset_property()
property_.fset(request=request, value=None)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_CHARSET" not in request.environ
def test_fset_value_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "UTF-8"})
property_ = accept_charset_property()
property_.fset(request=request, value="")
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_CHARSET"] == ""
def test_fset_value_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "UTF-8"})
property_ = accept_charset_property()
property_.fset(request=request, value="UTF-7")
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_CHARSET"] == "UTF-7"
@pytest.mark.parametrize(
"value, value_as_header",
[
(
"utf-8;q=0.5, iso-8859-5;q=0, utf-7",
"utf-8;q=0.5, iso-8859-5;q=0, utf-7",
),
(
[("utf-8", 0.5), ("iso-8859-5", 0.0), "utf-7"],
"utf-8;q=0.5, iso-8859-5;q=0, utf-7",
),
(
(("utf-8", 0.5), ("iso-8859-5", 0.0), "utf-7"),
"utf-8;q=0.5, iso-8859-5;q=0, utf-7",
),
(
{"utf-8": 0.5, "iso-8859-5": 0.0, "utf-7": 1.0},
"utf-7, utf-8;q=0.5, iso-8859-5;q=0",
),
],
)
def test_fset_value_types(self, value, value_as_header):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": ""})
property_ = accept_charset_property()
property_.fset(request=request, value=value)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_CHARSET"] == value_as_header
def test_fset_other_type_with_valid___str__(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": ""})
property_ = accept_charset_property()
value = StringMe("utf-8;q=0.5, iso-8859-5;q=0, utf-7")
property_.fset(request=request, value=value)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_CHARSET"] == str(value)
def test_fset_missing_AcceptCharset(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "utf-8"})
property_ = accept_charset_property()
header = AcceptCharset(None)
property_.fset(request=request, value=header)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_CHARSET" not in request.environ
def test_fset_valid_AcceptCharset(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "utf-8"})
property_ = accept_charset_property()
header = AcceptCharset("utf-7")
property_.fset(request=request, value=header)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_CHARSET"] == header.header_value
def test_fset_invalid_AcceptCharset(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "utf-8"})
property_ = accept_charset_property()
header = AcceptCharset("")
property_.fset(request=request, value=header)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_CHARSET"] == header.header_value
def test_fdel_header_key_in_environ(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_CHARSET": "utf-8"})
property_ = accept_charset_property()
property_.fdel(request=request)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_CHARSET" not in request.environ
def test_fdel_header_key_not_in_environ(self):
request = Request.blank("/")
property_ = accept_charset_property()
property_.fdel(request=request)
assert isinstance(request.accept_charset, AcceptCharset)
assert request.accept_charset.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_CHARSET" not in request.environ
class TestAcceptEncoding__parsing:
@pytest.mark.parametrize(
"value",
[
'"',
"(",
")",
"/",
":",
";",
"<",
"=",
">",
"?",
"@",
"[",
"\\",
"]",
"{",
"}",
", ",
", , ",
"gzip;q=1.0, identity; q =0.5, *;q=0",
],
)
def test_parse__invalid_header(self, value):
with pytest.raises(ValueError):
AcceptEncoding.parse(value=value)
@pytest.mark.parametrize(
"value, expected_list",
[
(",", []),
(", ,", []),
("*", [("*", 1.0)]),
("!#$%&'*+-.^_`|~;q=0.5", [("!#$%&'*+-.^_`|~", 0.5)]),
("0123456789", [("0123456789", 1.0)]),
(
",,\t foo \t;\t q=0.345,, bar ; Q=0.456 \t, ,\tCHARLIE \t,, ,",
[("foo", 0.345), ("bar", 0.456), ("CHARLIE", 1.0)],
),
# RFC 7231, section 5.3.4
("compress, gzip", [("compress", 1.0), ("gzip", 1.0)]),
("", []),
("*", [("*", 1.0)]),
("compress;q=0.5, gzip;q=1.0", [("compress", 0.5), ("gzip", 1.0)]),
(
"gzip;q=1.0, identity; q=0.5, *;q=0",
[("gzip", 1.0), ("identity", 0.5), ("*", 0.0)],
),
],
)
def test_parse__valid_header(self, value, expected_list):
returned = AcceptEncoding.parse(value=value)
list_of_returned = list(returned)
assert list_of_returned == expected_list
class TestAcceptEncoding__valid:
def test___init___(self):
header_value = ",,\t gzip;q=1.0, identity; q=0, *;q=0.5 \t ,"
instance = AcceptEncoding(header_value)
assert instance.header_state is HeaderState.Valid
assert instance.header_value == header_value
assert instance.parsed == (("gzip", 1.0), ("identity", 0.0), ("*", 0.5))
def test___bool__(self):
instance = AcceptEncoding("gzip")
returned = bool(instance)
assert returned is True
@pytest.mark.parametrize(
"header_value, expected_returned",
[
("", "<AcceptEncoding('')>"),
(
",\t, a ;\t q=0.20 , b ,',",
# single quote is valid character in token
"""<AcceptEncoding("a;q=0.2, b, \'")>""",
),
],
)
def test___repr__(self, header_value, expected_returned):
instance = AcceptEncoding(header_value)
assert repr(instance) == expected_returned
@pytest.mark.parametrize(
"header_value, expected_returned",
[("", ""), (",\t, a ;\t q=0.20 , b ,',", "a;q=0.2, b, '")],
)
def test___str__(self, header_value, expected_returned):
instance = AcceptEncoding(header_value)
assert str(instance) == expected_returned
def test_copy(self):
instance = AcceptEncoding("gzip")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
@pytest.mark.parametrize(
"header_value, offers, expected_returned",
[
("", [], []),
("gzip, compress", [], []),
("", ["gzip", "deflate"], []),
("", ["gzip", "identity"], [("identity", 1.0)]),
("compress, deflate, gzip", ["identity"], [("identity", 1.0)]),
("compress, identity;q=0, gzip", ["identity"], []),
# *;q=0 does not make sense, but is valid
("*;q=0", ["identity"], []),
("*;q=0, deflate, gzip", ["identity"], []),
("*;q=0, deflate, identity;q=0, gzip", ["identity"], []),
("*;q=0, deflate, identity;q=0.1, gzip", ["identity"], [("identity", 0.1)]),
(
"compress, deflate, gzip",
["identity", "gzip"],
[("identity", 1.0), ("gzip", 1.0)],
),
(
"compress, deflate, gzip",
["gzip", "identity"],
[("gzip", 1.0), ("identity", 1.0)],
),
(
"IDentity;q=0.5, deflATE;q=0, gZIP;q=0, COMPress",
["GZip", "DEFlate", "IDENTity", "comPRESS"],
[("comPRESS", 1.0), ("IDENTity", 0.5)],
),
(
"compress;q=0, identity, *;q=0.5, identity;q=0, *;q=0, compress",
# does not make sense, but is valid
["compress", "identity", "deflate", "gzip"],
[("identity", 1.0), ("deflate", 0.5), ("gzip", 0.5)],
),
],
)
def test_acceptable_offers(self, header_value, offers, expected_returned):
instance = AcceptEncoding(header_value)
returned = instance.acceptable_offers(offers=offers)
assert returned == expected_returned
def test_best_match(self):
accept = AcceptEncoding("gzip, iso-8859-5")
assert accept.best_match(["gzip", "iso-8859-5"]) == "gzip"
assert accept.best_match(["iso-8859-5", "gzip"]) == "iso-8859-5"
def test_best_match_with_one_lower_q(self):
accept = AcceptEncoding("gzip, compress;q=0.5")
assert accept.best_match(["gzip", "compress"]) == "gzip"
accept = AcceptEncoding("gzip;q=0.5, compress")
assert accept.best_match(["gzip", "compress"]) == "compress"
def test_best_match_with_complex_q(self):
accept = AcceptEncoding("gzip, compress;q=0.55, deflate;q=0.59")
assert accept.best_match(["gzip", "compress"]) == "gzip"
accept = AcceptEncoding("gzip;q=0.5, compress;q=0.586, deflate;q=0.596")
assert accept.best_match(["gzip", "deflate"]) == "deflate"
def test_best_match_mixedcase(self):
accept = AcceptEncoding("gZiP; q=0.2, COMPress; Q=0.4, *; q=0.05")
assert accept.best_match(["gzIP"]) == "gzIP"
assert accept.best_match(["DeFlAte"]) == "DeFlAte"
assert accept.best_match(["deflaTe", "compRess", "UtF-8"]) == "compRess"
def test_best_match_zero_quality(self):
assert AcceptEncoding("deflate, *;q=0").best_match(["gzip"]) is None
assert "content-coding" not in AcceptEncoding("*;q=0")
def test_quality(self):
accept = AcceptEncoding("gzip")
assert accept.quality("gzip") == 1
accept = AcceptEncoding("gzip;q=0.5")
assert accept.quality("gzip") == 0.5
def test_quality_with_identity(self):
accept = AcceptEncoding("gzip;q=0.5")
assert accept.quality("identity") == 1.0
accept = AcceptEncoding("gzip;q=0.5, identity;q=0")
assert accept.quality("identity") is None
accept = AcceptEncoding("gzip;q=0.5, identity;q=0.2, *;q=0")
assert accept.quality("identity") == 0.2
assert accept.quality("foo") is None
def test_quality_not_found(self):
accept = AcceptEncoding("gzip")
assert accept.quality("compress") is None
def test___contains__(self):
accept = AcceptEncoding("gzip, compress")
assert "gzip" in accept
assert "deflate" not in accept
for mask in ["*", "gzip", "gZIP"]:
accept = AcceptEncoding(mask)
assert "gzip" in accept
assert "identity" in accept
class TestAcceptEncoding__missing:
def test___init__(self):
instance = AcceptEncoding(None)
assert instance.header_state is HeaderState.Missing
assert instance.header_value is None
assert instance.parsed is None
def test___bool__(self):
instance = AcceptEncoding(None)
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptEncoding(None)
assert repr(instance) == "<AcceptEncoding: Missing>"
def test___str__(self):
instance = AcceptEncoding(None)
assert str(instance) == "<no header in request>"
def test_copy(self):
instance = AcceptEncoding(None)
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_acceptable_offers(self):
instance = AcceptEncoding(None)
returned = instance.acceptable_offers(offers=["a", "b", "c"])
assert returned == [("a", 1.0), ("b", 1.0), ("c", 1.0)]
def test_best_match(self):
accept = AcceptEncoding(None)
assert accept.best_match(["gzip", "compress"]) == "gzip"
assert accept.best_match(["compress", "gzip"]) == "compress"
assert accept.best_match(["compress", "gzip", "identity"]) == "compress"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
instance = AcceptEncoding(None)
assert instance.quality("content-coding") == 1.0
assert instance.quality("identity") == 1.0
def test___contains__(self):
instance = AcceptEncoding(None)
returned = "content-coding" in instance
assert returned is True
class TestAcceptEncoding__invalid:
def test___init__(self):
header_value = "invalid header"
instance = AcceptEncoding(header_value)
assert instance.header_state is HeaderState.Invalid
assert instance.header_value == header_value
assert instance.parsed is None
def test___bool__(self):
instance = AcceptEncoding(", ")
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptEncoding("\x00")
assert repr(instance) == "<AcceptEncoding: Invalid>"
def test___str__(self):
instance = AcceptEncoding(", ")
assert str(instance) == "<invalid header value>"
def test_copy(self):
instance = AcceptEncoding(", ")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_acceptable_offers(self):
instance = AcceptEncoding(", ")
returned = instance.acceptable_offers(offers=["a", "b", "c"])
assert returned == [("a", 1.0), ("b", 1.0), ("c", 1.0)]
def test_best_match(self):
accept = AcceptEncoding(", ")
assert accept.best_match(["gzip", "compress"]) == "gzip"
assert accept.best_match(["compress", "gzip"]) == "compress"
assert accept.best_match(["compress", "gzip", "identity"]) == "compress"
assert accept.best_match([], default_match="fallback") == "fallback"
def test_quality(self):
instance = AcceptEncoding(", ")
assert instance.quality("content-coding") == 1.0
assert instance.quality("identity") == 1.0
def test___contains__(self):
instance = AcceptEncoding(", ")
returned = "content-coding" in instance
assert returned is True
class TestAcceptEncoding__add:
invalid_values = [
", ",
[", "],
(", ",),
{", ": 1.0},
StringMe(", "),
]
valid_nonempty_values_with_headers = [
(
"compress;q=0.5, deflate;q=0, *",
"compress;q=0.5, deflate;q=0, *",
),
(
["compress;q=0.5", "deflate;q=0", "*"],
"compress;q=0.5, deflate;q=0, *",
),
(
[("compress", 0.5), ("deflate", 0.0), ("*", 1.0)],
"compress;q=0.5, deflate;q=0, *",
),
(
("compress;q=0.5", "deflate;q=0", "*"),
"compress;q=0.5, deflate;q=0, *",
),
(
(("compress", 0.5), ("deflate", 0.0), ("*", 1.0)),
"compress;q=0.5, deflate;q=0, *",
),
(
{"compress": 0.5, "deflate": 0.0, "*": 1.0},
"*, compress;q=0.5, deflate;q=0",
),
]
valid_empty_values = ["", [], (), {}, StringMe("")]
valid_values_with_headers = valid_nonempty_values_with_headers + [
[x, ""] for x in valid_empty_values
]
# snapshots help confirm the instance is immutable
def snapshot_instance(self, inst):
return deepcopy(
{
"header_value": inst.header_value,
"parsed": inst.parsed,
"header_state": inst.header_state,
}
)
# we want to test math with primitive python values and Accept instances
@pytest.fixture(params=["primitive", "instance"])
def maker(self, request):
if request.param == "primitive":
return lambda x: x
return AcceptEncoding
# almost always add and radd are symmetrical so we can test both and
# expect the same result
@pytest.fixture(params=["add", "radd"])
def fn(self, request):
if request.param == "add":
return lambda x, y: x + y
return lambda x, y: y + x
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_missing(self, input_value, input_header, maker, fn):
inst = AcceptEncoding(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
def test_invalid_add_missing(self, maker, fn):
inst = AcceptEncoding(", ")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == ", "
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
def test_missing_add_missing(self, maker, fn):
inst = AcceptEncoding(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("valid_value, valid_header", valid_values_with_headers)
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_valid_add_invalid(
self, valid_value, valid_header, invalid_value, maker, fn
):
inst = AcceptEncoding(valid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == valid_header
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == valid_header
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_invalid_add_invalid(self, invalid_value, maker, fn):
inst = AcceptEncoding(", ")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == ", "
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_missing_add_invalid(self, invalid_value, maker, fn):
inst = AcceptEncoding(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize(
"input_value, input_header",
valid_nonempty_values_with_headers,
)
def test_nonempty_valid_add_valid(self, input_value, input_header, maker):
seed_value = ",\t ,gzip, identity;q=0.333,"
inst = AcceptEncoding(seed_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == seed_value
result = inst + maker(input_value)
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == seed_value + ", " + input_header
result = maker(input_value) + inst
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header + ", " + seed_value
@pytest.mark.parametrize(
"input_value, input_header",
valid_nonempty_values_with_headers,
)
@pytest.mark.parametrize("empty_value", valid_empty_values)
def test_nonempty_valid_add_empty(
self, input_value, input_header, empty_value, maker, fn
):
inst = AcceptEncoding(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(empty_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
@pytest.mark.parametrize("empty_value", valid_empty_values)
def test_empty_valid_add_empty(self, empty_value, maker, fn):
expected_value = ""
inst = AcceptEncoding(empty_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == expected_value
result = fn(inst, maker(empty_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == expected_value
class TestCreateAcceptEncodingHeader:
def test_header_value_is_None(self):
header_value = None
returned = create_accept_encoding_header(header_value)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Missing
assert returned.header_value == header_value
returned2 = create_accept_encoding_header(returned)
assert isinstance(returned2, AcceptEncoding)
assert returned2.header_state is HeaderState.Missing
assert returned2._header_value == returned._header_value
def test_header_value_is_valid(self):
header_value = "gzip, identity;q=0.9"
returned = create_accept_encoding_header(header_value)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Valid
assert returned.header_value == header_value
returned2 = create_accept_encoding_header(returned)
assert isinstance(returned2, AcceptEncoding)
assert returned2.header_state is HeaderState.Valid
assert returned2._header_value == returned._header_value
@pytest.mark.parametrize("header_value", [", ", "gzip;q= 1"])
def test_header_value_is_invalid(self, header_value):
returned = create_accept_encoding_header(header_value)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Invalid
assert returned.header_value == header_value
returned2 = create_accept_encoding_header(returned)
assert isinstance(returned2, AcceptEncoding)
assert returned2.header_state is HeaderState.Invalid
assert returned2._header_value == returned._header_value
class TestAcceptEncodingProperty:
def test_fget_header_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": None})
property_ = accept_encoding_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Missing
def test_fget_header_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Valid
def test_fget_header_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": ", "})
property_ = accept_encoding_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptEncoding)
assert returned.header_state is HeaderState.Invalid
def test_fset_value_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
property_.fset(request=request, value=None)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_ENCODING" not in request.environ
def test_fset_value_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
property_.fset(request=request, value=", ")
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_ENCODING"] == ", "
def test_fset_value_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
property_.fset(request=request, value="compress")
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_ENCODING"] == "compress"
@pytest.mark.parametrize(
"value, value_as_header",
[
("gzip;q=0.5, compress;q=0, deflate", "gzip;q=0.5, compress;q=0, deflate"),
(
[("gzip", 0.5), ("compress", 0.0), "deflate"],
"gzip;q=0.5, compress;q=0, deflate",
),
(
(("gzip", 0.5), ("compress", 0.0), "deflate"),
"gzip;q=0.5, compress;q=0, deflate",
),
(
{"gzip": 0.5, "compress": 0.0, "deflate": 1.0},
"deflate, gzip;q=0.5, compress;q=0",
),
],
)
def test_fset_value_types(self, value, value_as_header):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": ""})
property_ = accept_encoding_property()
property_.fset(request=request, value=value)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_ENCODING"] == value_as_header
def test_fset_other_type_with_valid___str__(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": ""})
property_ = accept_encoding_property()
value = StringMe("gzip;q=0.5, compress;q=0, deflate")
property_.fset(request=request, value=value)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_ENCODING"] == str(value)
def test_fset_missing_AcceptEncoding(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": ""})
property_ = accept_encoding_property()
header = AcceptEncoding(None)
property_.fset(request=request, value=header)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_ENCODING" not in request.environ
def test_fset_valid_AcceptEncoding(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": ""})
property_ = accept_encoding_property()
header = AcceptEncoding("gzip")
property_.fset(request=request, value=header)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_ENCODING"] == header.header_value
def test_fset_invalid_AcceptEncoding(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
header = AcceptEncoding(", ")
property_.fset(request=request, value=header)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_ENCODING"] == header.header_value
def test_fdel_header_key_in_environ(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_ENCODING": "gzip"})
property_ = accept_encoding_property()
property_.fdel(request=request)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_ENCODING" not in request.environ
def test_fdel_header_key_not_in_environ(self):
request = Request.blank("/")
property_ = accept_encoding_property()
property_.fdel(request=request)
assert isinstance(request.accept_encoding, AcceptEncoding)
assert request.accept_encoding.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_ENCODING" not in request.environ
class TestAcceptLanguage:
@pytest.mark.parametrize(
"value",
[
"",
"*s",
"*-a",
"a-*",
"a" * 9,
"a-" + "a" * 9,
"a-a-" + "a" * 9,
"-",
"a-",
"-a",
"---",
"--a",
"1-a",
"1-a-a",
"en_gb",
"en/gb",
"foo, bar, baz;q= 0.001",
"foo , ,bar,charlie ",
],
)
def test_parse__invalid_header(self, value):
with pytest.raises(ValueError):
AcceptLanguage.parse(value=value)
@pytest.mark.parametrize(
"value, expected_list",
[
("*", [("*", 1.0)]),
("fR;q=0.5", [("fR", 0.5)]),
("zh-Hant;q=0.500", [("zh-Hant", 0.5)]),
("zh-Hans-CN;q=1", [("zh-Hans-CN", 1.0)]),
("de-CH-x-phonebk;q=1.0", [("de-CH-x-phonebk", 1.0)]),
("az-Arab-x-AZE-derbend;q=1.00", [("az-Arab-x-AZE-derbend", 1.0)]),
("zh-CN-a-myExt-x-private;q=1.000", [("zh-CN-a-myExt-x-private", 1.0)]),
("aaaaaaaa", [("aaaaaaaa", 1.0)]),
("aaaaaaaa-a", [("aaaaaaaa-a", 1.0)]),
("aaaaaaaa-aaaaaaaa", [("aaaaaaaa-aaaaaaaa", 1.0)]),
("a-aaaaaaaa-aaaaaaaa", [("a-aaaaaaaa-aaaaaaaa", 1.0)]),
("aaaaaaaa-a-aaaaaaaa", [("aaaaaaaa-a-aaaaaaaa", 1.0)]),
(
"zh-Hant;q=0.372,zh-CN-a-myExt-x-private;q=0.977,de,*;q=0.000",
[
("zh-Hant", 0.372),
("zh-CN-a-myExt-x-private", 0.977),
("de", 1.0),
("*", 0.0),
],
),
(
",\t foo \t;\t q=0.345,, bar ; Q=0.456 \t, ,\tcharlie \t,, ,",
[("foo", 0.345), ("bar", 0.456), ("charlie", 1.0)],
),
# RFC 7230 Section 7
("foo,bar", [("foo", 1.0), ("bar", 1.0)]),
("foo, bar,", [("foo", 1.0), ("bar", 1.0)]),
# RFC 7230 Errata ID: 4169
("foo , ,bar,charlie", [("foo", 1.0), ("bar", 1.0), ("charlie", 1.0)]),
],
)
def test_parse__valid_header(self, value, expected_list):
returned = AcceptLanguage.parse(value=value)
list_of_returned = list(returned)
assert list_of_returned == expected_list
class TestAcceptLanguage__valid:
def test___init__(self):
header_value = "zh-Hant;q=0.372,zh-CN-a-myExt-x-private;q=0.977,de,*;q=0.000"
instance = AcceptLanguage(header_value)
assert instance.header_state is HeaderState.Valid
assert instance.header_value == header_value
assert instance.parsed == (
("zh-Hant", 0.372),
("zh-CN-a-myExt-x-private", 0.977),
("de", 1.0),
("*", 0.0),
)
def test___bool__(self):
instance = AcceptLanguage("valid-header")
returned = bool(instance)
assert returned is True
def test___repr__(self):
instance = AcceptLanguage(",da;q=0.200,en-gb;q=0.300")
assert repr(instance) == "<AcceptLanguage('da;q=0.2, en-gb;q=0.3')>"
def test___str__(self):
header_value = ", \t,de;q=0.000 \t, es;q=1.000, zh, jp;q=0.210 ,"
instance = AcceptLanguage(header_value)
assert str(instance) == "de;q=0, es, zh, jp;q=0.21"
def test_copy(self):
instance = AcceptLanguage("valid-header")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
@pytest.mark.parametrize(
"header_value, language_tags, expected_returned",
[
# Example from RFC 4647, Section 3.4
(
"de-de",
["de", "de-DE-1996", "de-Deva", "de-Latn-DE"],
[("de-DE-1996", 1.0)],
),
# Empty `language_tags`
("a", [], []),
# No matches
("a, b", ["c", "d"], []),
# Several ranges and tags, no matches
("a-b;q=0.9, c-d;q=0.5, e-f", ("a", "b", "c", "d", "e", "f"), []),
# Case-insensitive match
("foO, BaR", ["foo", "bar"], [("foo", 1.0), ("bar", 1.0)]),
# If a tag matches a non-'*' range with q=0, tag is filtered out
("b-c, a, b;q=0, d;q=0", ["b-c", "a", "b-c-d", "d-e-f"], [("a", 1.0)]),
# Match if a range exactly equals a tag
("d-e-f", ["a-b-c", "d-e-f"], [("d-e-f", 1.0)]),
# Match if a range exactly equals a prefix of the tag such that the
# first character following the prefix is '-'
(
"a-b-c-d, a-b-c-d-e, a-b-c-d-f-g-h",
["a-b-c-d-f-g"],
[("a-b-c-d-f-g", 1.0)],
),
# '*', when it is the only range in the header, matches everything
("*", ["a", "b"], [("a", 1.0), ("b", 1.0)]),
# '*' range matches only tags not matched by any other range
(
"*;q=0.2, a;q=0.5, b",
["a-a", "b-a", "c-a", "d-a"],
[("b-a", 1.0), ("a-a", 0.5), ("c-a", 0.2), ("d-a", 0.2)],
),
# '*' range without a qvalue gives a matched qvalue of 1.0
(
"a;q=0.5, b, *",
["a-a", "b-a", "c-a", "d-a"],
[("b-a", 1.0), ("c-a", 1.0), ("d-a", 1.0), ("a-a", 0.5)],
),
# The qvalue for the '*' range works the same way as qvalues for
# non-'*' ranges.
(
"a;q=0.5, *;q=0.9",
# (meaning: prefer anything other than 'a', with 'a' as a
# fallback)
["a", "b"],
[("b", 0.9), ("a", 0.5)],
),
# More than one range matching the same tag: range with the highest
# qvalue is matched
("a-b-c;q=0.7, a;q=0.9, a-b;q=0.8", ["a-b-c"], [("a-b-c", 0.9)]),
# More than one range with the same qvalue matching the same tag:
# the range in an earlier position in the header is matched
(
"a-b-c;q=0.7, a;q=0.9, b;q=0.9, a-b;q=0.9",
["a-b-c", "b"],
[("a-b-c", 0.9), ("b", 0.9)],
),
# The returned list of tuples is sorted in descending order of qvalue
(
"a;q=0.7, b;q=0.3, c, d;q=0.5",
["d", "c", "b", "a"],
[("c", 1.0), ("a", 0.7), ("d", 0.5), ("b", 0.3)],
),
# When qvalues are the same, the tag whose matched range appears
# earlier in the header comes first
("a, c, b", ["b", "a", "c"], [("a", 1.0), ("c", 1.0), ("b", 1.0)]),
# When many tags match the same range (so same qvalue and same
# matched range position in header), they are returned in order of
# their position in the `language_tags` argument
("a", ["a-b", "a", "a-b-c"], [("a-b", 1.0), ("a", 1.0), ("a-b-c", 1.0)]),
# When a non-'*' range appears in the header more than once, we use
# the first one for matching and ignore the others
(
"a;q=0.5, c;q=0.6, b;q=0.7, c;q=0.9",
["a", "b", "c"],
[("b", 0.7), ("c", 0.6), ("a", 0.5)],
),
(
"a, b, c;q=0.5, c;q=0",
["a-a", "b-a", "c-a"],
[("a-a", 1.0), ("b-a", 1.0), ("c-a", 0.5)],
),
(
"a;q=0.5, c;q=0.9, b;q=0.9, c;q=0.9",
["a", "b", "c"],
[("c", 0.9), ("b", 0.9), ("a", 0.5)],
),
# When the '*' range appears in the header more than once, we use
# the first one for matching and ignore the others
(
"a;q=0.5, *;q=0.6, b;q=0.7, *;q=0.9",
["a", "b", "c"],
[("b", 0.7), ("c", 0.6), ("a", 0.5)],
),
(
"a, b, *;q=0.5, *;q=0",
["a-a", "b-a", "c-a"],
[("a-a", 1.0), ("b-a", 1.0), ("c-a", 0.5)],
),
(
"a;q=0.5, *;q=0.9, b;q=0.9, *;q=0.9",
["a", "b", "c"],
[("c", 0.9), ("b", 0.9), ("a", 0.5)],
),
# Both '*' and non-'*' ranges appearing more than once
(
"a-b;q=0.5, c-d, *, a-b, c-d;q=0.3, *;q=0",
["a-b-c", "c-d-e", "e-f-g"],
[("c-d-e", 1.0), ("e-f-g", 1.0), ("a-b-c", 0.5)],
),
],
)
def test_basic_filtering(self, header_value, language_tags, expected_returned):
instance = AcceptLanguage(header_value)
returned = instance.basic_filtering(language_tags)
assert returned == expected_returned
def test_lookup_default_tag_and_default_cannot_both_be_None(self):
instance = AcceptLanguage("valid-header")
with pytest.raises(TypeError):
instance.lookup(
language_tags=["tag"],
default_range="language-range",
default_tag=None,
default=None,
)
def test_lookup_default_range_cannot_be_asterisk(self):
instance = AcceptLanguage("valid-header")
with pytest.raises(ValueError):
instance.lookup(
language_tags=["tag"],
default_range="*",
default_tag="default-tag",
default=None,
)
@pytest.mark.parametrize(
(
"header_value, language_tags, default_range, default_tag, default"
", expected"
),
[
# Each language range in the header is considered in turn, in
# descending order of qvalue
(
"aA;q=0.3, Bb, cC;q=0.7",
["Aa", "bB", "Cc"],
None,
"default-tag",
None,
"bB",
),
# For ranges with the same qvalue, position in header is the
# tiebreaker.
(
"bB-Cc;q=0.8, aA;q=0.9, Bb;q=0.9",
["bb", "aa"],
None,
"default-tag",
None,
"aa",
),
# Each language range represents the most specific tag that is an
# acceptable match. Examples from RFC 4647, section 3.4, first
# paragraph:
(
"de-ch",
["de-CH-1996", "de-CH", "de"],
None,
"default-tag",
None,
"de-CH",
),
("de-ch", ["de-CH-1996", "de"], None, "default-tag", None, "de"),
# The language range is progressively truncated from the end until
# a matching language tag is located. From the example of a Lookup
# Fallback Pattern in RFC 4647, section 3.4:
(
"zh-Hant-CN-x-private1-private2",
[
"zh-Hant-CN-x-private1-private2",
"zh-Hant-CN-x-private1",
"zh-Hant-CN-x",
"zh-Hant-CN",
"zh-Hant",
"zh",
],
None,
"default-tag",
None,
"zh-Hant-CN-x-private1-private2",
),
(
"zh-Hant-CN-x-private1-private2",
[
"zh-Hant-CN-x-private1",
"zh-Hant-CN-x",
"zh-Hant-CN",
"zh-Hant",
"zh",
],
None,
"default-tag",
None,
"zh-Hant-CN-x-private1",
),
(
"zh-Hant-CN-x-private1-private2",
["zh-Hant-CN-x", "zh-Hant-CN", "zh-Hant", "zh"],
None,
"default-tag",
None,
"zh-Hant-CN",
),
(
"zh-Hant-CN-x-private1-private2",
["zh-Hant-CN", "zh-Hant", "zh"],
None,
"default-tag",
None,
"zh-Hant-CN",
),
(
"zh-Hant-CN-x-private1-private2",
["zh-Hant", "zh"],
None,
"default-tag",
None,
"zh-Hant",
),
("zh-Hant-CN-x-private1-private2", ["zh"], None, "default-tag", None, "zh"),
(
"zh-Hant-CN-x-private1-private2",
["some-other-tag-1", "some-other-tag-2"],
None,
"default-tag",
None,
"default-tag",
),
# Further tests to check that single-letter or -digit subtags are
# removed at the same time as their closest trailing subtag:
("AA-T-subtag", ["Aa-t", "aA"], None, "default-tag", None, "aA"),
("AA-1-subtag", ["aa-1", "aA"], None, "default-tag", None, "aA"),
(
"Aa-P-subtag-8-subtag",
["Aa-p-subtag-8", "Aa-p", "aA"],
None,
"default-tag",
None,
"aA",
),
(
"aA-3-subTag-C-subtag",
["aA-3-subtag-c", "aA-3", "aA"],
None,
"default-tag",
None,
"aA",
),
# Test that single-letter or -digit subtag in first position works
# as expected
(
"T-subtag",
["t-SubTag", "another"],
None,
"default-tag",
None,
"t-SubTag",
),
("T-subtag", ["another"], None, "default-tag", None, "default-tag"),
# If the language range "*" is followed by other language ranges,
# it is skipped.
("*, Aa-aA-AA", ["bb", "aA"], None, "default-tag", None, "aA"),
# If the language range "*" is the only one in the header, lookup
# proceeds to the default arguments.
("*", ["bb", "aa"], None, "default-tag", None, "default-tag"),
# If no other language range follows the "*" in the header, lookup
# proceeds to the default arguments.
("dd, cc, *", ["bb", "aa"], None, "default-tag", None, "default-tag"),
# If a non-'*' range has q=0, any tag that matches the range
# exactly (without subtag truncation) is not acceptable.
(
"aa, bB-Cc-DD;q=0, bB-Cc, cc",
["bb", "bb-Cc-DD", "bb-cC-dd", "Bb-cc", "bb-cC-dd"],
None,
"default-tag",
None,
"Bb-cc",
),
# ;q=0 and ;q={not 0} both in header: q=0 takes precedence and
# makes the exact match not acceptable, but the q={not 0} means
# that tags can still match after subtag truncation.
(
"aa, bB-Cc-DD;q=0.9, cc, Bb-cC-dD;q=0",
["bb", "Bb-Cc", "Bb-cC-dD"],
None,
"default-tag",
None,
"Bb-Cc",
),
# If none of the ranges in the header match any of the language
# tags, and the `default_range` argument is not None and does not
# match any q=0 range in the header, we search through it by
# progressively truncating from the end, as we do with the ranges
# in the header. Example from RFC 4647, section 3.4.1:
(
"fr-FR, zh-Hant",
["fr-FR", "fr", "zh-Hant", "zh", "ja-JP", "ja"],
"ja-JP",
"default-tag",
None,
"fr-FR",
),
(
"fr-FR, zh-Hant",
["fr", "zh-Hant", "zh", "ja-JP", "ja"],
"ja-JP",
"default-tag",
None,
"fr",
),
(
"fr-FR, zh-Hant",
["zh-Hant", "zh", "ja-JP", "ja"],
"ja-JP",
"default-tag",
None,
"zh-Hant",
),
(
"fr-FR, zh-Hant",
["zh", "ja-JP", "ja"],
"ja-JP",
"default-tag",
None,
"zh",
),
("fr-FR, zh-Hant", ["ja-JP", "ja"], "ja-JP", "default-tag", None, "ja-JP"),
("fr-FR, zh-Hant", ["ja"], "ja-JP", "default-tag", None, "ja"),
(
"fr-FR, zh-Hant",
["some-other-tag-1", "some-other-tag-2"],
"ja-JP",
"default-tag",
None,
"default-tag",
),
# If none of the ranges in the header match the language tags, the
# `default_range` argument is not None, and there is a '*;q=0'
# range in the header, then the `default_range` and its substrings
# from subtag truncation are not acceptable.
(
"aa-bb, cc-dd, *;q=0",
["ee-ff", "ee"],
"ee-ff",
None,
"default",
"default",
),
# If none of the ranges in the header match the language tags, the
# `default_range` argument is not None, and the argument exactly
# matches a non-'*' range in the header with q=0 (without fallback
# subtag truncation), then the `default_range` itself is not
# acceptable...
(
"aa-bb, cc-dd, eE-Ff;q=0",
["Ee-fF"],
"EE-FF",
"default-tag",
None,
"default-tag",
),
# ...but it should still be searched with subtag truncation,
# because its substrings other than itself are still acceptable:
(
"aa-bb, cc-dd, eE-Ff-Gg;q=0",
["Ee", "Ee-fF-gG", "Ee-fF"],
"EE-FF-GG",
"default-tag",
None,
"Ee-fF",
),
(
"aa-bb, cc-dd, eE-Ff-Gg;q=0",
["Ee-fF-gG", "Ee"],
"EE-FF-GG",
"default-tag",
None,
"Ee",
),
# If `default_range` only has one subtag, then no subtag truncation
# is possible, and we proceed to `default-tag`:
("aa-bb, cc-dd, eE;q=0", ["Ee"], "EE", "default-tag", None, "default-tag"),
# If the `default_range` argument would only match a non-'*' range
# in the header with q=0 exactly if the `default_range` had subtags
# from the end truncated, then it is acceptable, and we attempt to
# match it with the language tags using subtag truncation. However,
# the tag equivalent of the range with q=0 would be considered not
# acceptable and ruled out, if we reach it during the subtag
# truncation search.
(
"aa-bb, cc-dd, eE-Ff;q=0",
["Ee-fF", "Ee-fF-33", "ee"],
"EE-FF-33",
"default-tag",
None,
"Ee-fF-33",
),
(
"aa-bb, cc-dd, eE-Ff;q=0",
["Ee-fF", "eE"],
"EE-FF-33",
"default-tag",
None,
"eE",
),
# If none of the ranges in the header match, the `default_range`
# argument is None or does not match, and the `default_tag`
# argument is not None and does not match any range in the header
# with q=0, then the `default_tag` argument is returned.
("aa-bb, cc-dd", ["ee-ff", "ee"], None, "default-tag", None, "default-tag"),
(
"aa-bb, cc-dd",
["ee-ff", "ee"],
"gg-hh",
"default-tag",
None,
"default-tag",
),
# If none of the ranges in the header match, the `default_range`
# argument is None or does not match, the `default_tag` argument is
# not None, and there is a '*' range in the header with q=0, then
# the `default_tag` argument is not acceptable.
(
"aa-bb, cc-dd, *;q=0",
["ee-ff", "ee"],
"gg-hh",
"ii-jj",
"default",
"default",
),
# If none of the ranges in the header match, the `default_range`
# argument is None or does not match, the `default_tag` argument is
# not None and matches a non-'*' range in the header with q=0
# exactly, then the `default_tag` argument is not acceptable.
(
"aa-bb, cc-dd, iI-jJ;q=0",
["ee-ff", "ee"],
"gg-hh",
"Ii-Jj",
"default",
"default",
),
# If none of the ranges in the header match, the `default_range`
# argument is None or does not match, and the `default_tag`
# argument is None, then we proceed to the `default` argument.
("aa-bb, cc-dd", ["ee-ff", "ee"], None, None, "default", "default"),
("aa-bb, cc-dd", ["ee-ff", "ee"], "gg-hh", None, "default", "default"),
# If we fall back to the `default` argument, and it is not a
# callable, the argument itself is returned.
("aa", ["bb"], None, None, 0, 0),
(
"Aa, cC;q=0",
["bb"],
"aA-Cc",
"Cc",
["non-callable object"],
["non-callable object"],
),
# If we fall back to the `default` argument, and it is a callable,
# it is called, and the callable's return value is returned by the
# method.
("aa", ["bb"], None, None, lambda: "callable called", "callable called"),
(
"Aa, cc;q=0",
["bb"],
"aA-cC",
"cc",
lambda: "callable called",
"callable called",
),
# Even if the 'default' argument is a str that matches a q=0 range
# in the header, it is still returned.
("aa, *;q=0", ["bb"], None, None, "cc", "cc"),
("aa, cc;q=0", ["bb"], None, None, "cc", "cc"),
# If the `default_tag` argument is not acceptable because of a q=0
# range in the header, and the `default` argument is None, then
# None is returned.
("aa, Bb;q=0", ["cc"], None, "bB", None, None),
("aa, *;q=0", ["cc"], None, "bb", None, None),
# Test that method works with empty `language_tags`:
("range", [], None, "default-tag", None, "default-tag"),
# Test that method works with empty `default_range`:
("range", [], "", "default-tag", None, "default-tag"),
("range", ["tag"], "", "default-tag", None, "default-tag"),
# Test that method works with empty `default_tag`:
("range", [], "", "", None, ""),
("range", ["tag"], "default-range", "", None, ""),
],
)
def test_lookup(
self, header_value, language_tags, default_range, default_tag, default, expected
):
instance = AcceptLanguage(header_value)
returned = instance.lookup(
language_tags=language_tags,
default_range=default_range,
default_tag=default_tag,
default=default,
)
assert returned == expected
@pytest.mark.parametrize(
"header_value, offer",
[
("*", "da"),
("da", "DA"),
("en", "en-gb"),
("en-gb", "en-gb"),
],
)
def test___contains___in(self, header_value, offer):
instance = AcceptLanguage(header_value)
assert offer in instance
@pytest.mark.parametrize(
"header_value, offer",
[
("en-gb", "en-us"),
("en-gb", "fr-fr"),
("en-gb", "fr"),
("en", "fr-fr"),
("en-gb", "en"),
("en-gb", "en_GB"),
],
)
def test___contains___not_in(self, header_value, offer):
instance = AcceptLanguage(header_value)
assert offer not in instance
class TestAcceptLanguage__missing:
def test___init__(self):
instance = AcceptLanguage(None)
assert instance.header_state is HeaderState.Missing
assert instance.header_value is None
assert instance.parsed is None
def test___bool__(self):
instance = AcceptLanguage(None)
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptLanguage(None)
assert repr(instance) == "<AcceptLanguage: Missing>"
def test___str__(self):
instance = AcceptLanguage(None)
assert str(instance) == "<no header in request>"
def test_copy(self):
instance = AcceptLanguage(None)
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_basic_filtering(self):
instance = AcceptLanguage(None)
returned = instance.basic_filtering(["tag1", "tag2"])
assert returned == [("tag1", 1.0), ("tag2", 1.0)]
def test_lookup_default_tag_and_default_cannot_both_be_None(self):
instance = AcceptLanguage(None)
with pytest.raises(TypeError):
instance.lookup([], default_tag=None, default=None)
@pytest.mark.parametrize(
"default_tag, default, expected",
[
# If `default_tag` is not None, it is returned.
("default-tag", "default", "default-tag"),
# If `default_tag` is None, we proceed to the `default` argument. If
# `default` is not a callable, the argument itself is returned.
(None, 0, 0),
# If `default` is a callable, it is called, and the callable's return
# value is returned by the method.
(None, lambda: "callable called", "callable called"),
],
)
def test_lookup(self, default_tag, default, expected):
instance = AcceptLanguage(None)
returned = instance.lookup([], default_tag=default_tag, default=default)
assert returned == expected
def test___contains__(self):
instance = AcceptLanguage(None)
returned = "any-tag" in instance
assert returned is True
class TestAcceptLanguage__invalid:
def test___init__(self):
header_value = "invalid header"
instance = AcceptLanguage(header_value)
assert instance.header_state is HeaderState.Invalid
assert instance.header_value == header_value
assert instance.parsed is None
def test___bool__(self):
instance = AcceptLanguage("")
returned = bool(instance)
assert returned is False
def test___repr__(self):
instance = AcceptLanguage("\x00")
assert repr(instance) == "<AcceptLanguage: Invalid>"
def test___str__(self):
instance = AcceptLanguage("invalid header")
assert str(instance) == "<invalid header value>"
def test_copy(self):
instance = AcceptLanguage("invalid header")
result = instance.copy()
assert instance is not result
assert instance.header_value == result.header_value
assert instance.header_state == result.header_state
assert instance.parsed == result.parsed
def test_basic_filtering(self):
instance = AcceptLanguage("")
returned = instance.basic_filtering(["tag1", "tag2"])
assert returned == [("tag1", 1.0), ("tag2", 1.0)]
def test_lookup_default_tag_and_default_cannot_both_be_None(self):
instance = AcceptLanguage("")
with pytest.raises(TypeError):
instance.lookup([], default_tag=None, default=None)
@pytest.mark.parametrize(
"default_tag, default, expected",
[
# If `default_tag` is not None, it is returned.
("default-tag", "default", "default-tag"),
# If `default_tag` is None, we proceed to the `default` argument. If
# `default` is not a callable, the argument itself is returned.
(None, 0, 0),
# If `default` is a callable, it is called, and the callable's return
# value is returned by the method.
(None, lambda: "callable called", "callable called"),
],
)
def test_lookup(self, default_tag, default, expected):
instance = AcceptLanguage("")
returned = instance.lookup([], default_tag=default_tag, default=default)
assert returned == expected
def test___contains__(self):
instance = AcceptLanguage(None)
returned = "any-tag" in instance
assert returned is True
class TestAcceptLanguage__add:
invalid_values = [
"",
[],
(),
{},
"en_gb",
["en_gb"],
("en_gb",),
{"en_gb": 1.0},
",",
[","],
(",",),
{",": 1.0},
StringMe(""),
StringMe("en_gb"),
]
valid_values_with_headers = [
(
"en-gb;q=0.5, fr;q=0, es",
"en-gb;q=0.5, fr;q=0, es",
),
(
[("en-gb", 0.5), ("fr", 0.0), "es"],
"en-gb;q=0.5, fr;q=0, es",
),
(
(("en-gb", 0.5), ("fr", 0.0), "es"),
"en-gb;q=0.5, fr;q=0, es",
),
(
{"en-gb": 0.5, "fr": 0.0, "es": 1.0},
"es, en-gb;q=0.5, fr;q=0",
),
(
StringMe("en-gb;q=0.5, fr;q=0, es"),
"en-gb;q=0.5, fr;q=0, es",
),
]
# snapshots help confirm the instance is immutable
def snapshot_instance(self, inst):
return deepcopy(
{
"header_value": inst.header_value,
"parsed": inst.parsed,
"header_state": inst.header_state,
}
)
# we want to test math with primitive python values and Accept instances
@pytest.fixture(params=["primitive", "instance"])
def maker(self, request):
if request.param == "primitive":
return lambda x: x
return AcceptLanguage
# almost always add and radd are symmetrical so we can test both and
# expect the same result
@pytest.fixture(params=["add", "radd"])
def fn(self, request):
if request.param == "add":
return lambda x, y: x + y
return lambda x, y: y + x
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_missing(self, input_value, input_header, maker, fn):
inst = AcceptLanguage(input_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == input_header
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header
def test_invalid_add_missing(self, maker, fn):
invalid_value = "en_gb"
inst = AcceptLanguage(invalid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == invalid_value
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
def test_missing_add_missing(self, maker, fn):
inst = AcceptLanguage(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(None))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("valid_value, valid_header", valid_values_with_headers)
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_valid_add_invalid(
self, valid_value, valid_header, invalid_value, maker, fn
):
inst = AcceptLanguage(valid_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == valid_header
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == valid_header
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_invalid_add_invalid(self, invalid_value, maker, fn):
inst = AcceptLanguage("")
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Invalid
assert inst.header_value == ""
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize("invalid_value", invalid_values)
def test_missing_add_invalid(self, invalid_value, maker, fn):
inst = AcceptLanguage(None)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Missing
assert inst.header_value is None
result = fn(inst, maker(invalid_value))
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Missing
assert result.header_value is None
@pytest.mark.parametrize(
"input_value, input_header",
valid_values_with_headers,
)
def test_valid_add_valid(self, input_value, input_header, maker):
seed_value = "en"
inst = AcceptLanguage(seed_value)
snap = self.snapshot_instance(inst)
assert inst.header_state == HeaderState.Valid
assert inst.header_value == seed_value
result = inst + maker(input_value)
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == seed_value + ", " + input_header
result = maker(input_value) + inst
assert snap == self.snapshot_instance(inst)
assert result.header_state == HeaderState.Valid
assert result.header_value == input_header + ", " + seed_value
class TestCreateAcceptLanguageHeader:
def test_header_value_is_None(self):
header_value = None
returned = create_accept_language_header(header_value)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Missing
assert returned.header_value == header_value
returned2 = create_accept_language_header(returned)
assert returned2 is returned
assert returned2.header_state is HeaderState.Missing
assert returned2.header_value == header_value
def test_header_value_is_valid(self):
header_value = "es, ja"
returned = create_accept_language_header(header_value)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Valid
assert returned.header_value == header_value
returned2 = create_accept_language_header(returned)
assert returned2 is returned
assert returned2.header_state is HeaderState.Valid
assert returned2.header_value == header_value
@pytest.mark.parametrize("header_value", ["", "en_gb"])
def test_header_value_is_invalid(self, header_value):
returned = create_accept_language_header(header_value)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Invalid
assert returned.header_value == header_value
returned2 = create_accept_language_header(returned)
assert returned2 is returned
assert returned2.header_state is HeaderState.Invalid
assert returned2.header_value == header_value
class TestAcceptLanguageProperty:
def test_fget_header_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": None})
property_ = accept_language_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Missing
def test_fget_header_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "es"})
property_ = accept_language_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Valid
def test_fget_header_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "en_gb"})
property_ = accept_language_property()
returned = property_.fget(request=request)
assert isinstance(returned, AcceptLanguage)
assert returned.header_state is HeaderState.Invalid
def test_fset_value_is_None(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "es"})
property_ = accept_language_property()
property_.fset(request=request, value=None)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_LANGUAGE" not in request.environ
def test_fset_value_is_invalid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "es"})
property_ = accept_language_property()
property_.fset(request=request, value="en_GB")
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == "en_GB"
def test_fset_value_is_valid(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "es"})
property_ = accept_language_property()
property_.fset(request=request, value="en-GB")
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == "en-GB"
@pytest.mark.parametrize(
"value, value_as_header",
[
("en-gb;q=0.5, fr;q=0, es", "en-gb;q=0.5, fr;q=0, es"),
([("en-gb", 0.5), ("fr", 0.0), "es"], "en-gb;q=0.5, fr;q=0, es"),
((("en-gb", 0.5), ("fr", 0.0), "es"), "en-gb;q=0.5, fr;q=0, es"),
({"en-gb": 0.5, "fr": 0.0, "es": 1.0}, "es, en-gb;q=0.5, fr;q=0"),
],
)
def test_fset_value_types(self, value, value_as_header):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": ""})
property_ = accept_language_property()
property_.fset(request=request, value=value)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == value_as_header
def test_fset_other_type_with_valid___str__(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": ""})
property_ = accept_language_property()
value = StringMe("en-gb;q=0.5, fr;q=0, es")
property_.fset(request=request, value=value)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == str(value)
def test_fset_missing_AcceptLanguage(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "en"})
property_ = accept_language_property()
header = AcceptLanguage(None)
property_.fset(request=request, value=header)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_LANGUAGE" not in request.environ
def test_fset_valid_AcceptLanguage(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": ""})
property_ = accept_language_property()
header = AcceptLanguage("es")
property_.fset(request=request, value=header)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Valid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == header.header_value
def test_fset_invalid_AcceptLanguage(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": ""})
property_ = accept_language_property()
header = AcceptLanguage("en_gb")
property_.fset(request=request, value=header)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Invalid
assert request.environ["HTTP_ACCEPT_LANGUAGE"] == header.header_value
def test_fdel_header_key_in_environ(self):
request = Request.blank("/", environ={"HTTP_ACCEPT_LANGUAGE": "es"})
property_ = accept_language_property()
property_.fdel(request=request)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_LANGUAGE" not in request.environ
def test_fdel_header_key_not_in_environ(self):
request = Request.blank("/")
property_ = accept_language_property()
property_.fdel(request=request)
assert isinstance(request.accept_language, AcceptLanguage)
assert request.accept_language.header_state is HeaderState.Missing
assert "HTTP_ACCEPT_LANGUAGE" not in request.environ