import logging
import re
from http.server import BaseHTTPRequestHandler
from test.data import TEST_DATA_DIR
from test.utils import GraphHelper
from test.utils.graph import cached_graph
from test.utils.http import (
MOCK_HTTP_REQUEST_WILDCARD,
MethodName,
MockHTTPRequest,
MockHTTPResponse,
ctx_http_handler,
)
from test.utils.httpservermock import ServedBaseHTTPServerMock
from test.utils.wildcard import URL_PARSE_RESULT_WILDCARD
from urllib.error import HTTPError
import pytest
from rdflib import Graph, Namespace
"""
Test that correct content negotiation headers are passed
by graph.parse
"""
xmltestdoc = """
"""
n3testdoc = """@prefix : .
:a :b :c .
"""
nttestdoc = " .\n"
ttltestdoc = """@prefix : .
:a :b :c .
"""
jsonldtestdoc = """
[
{
"@id": "http://example.org/a",
"http://example.org/b": [
{
"@id": "http://example.org/c"
}
]
}
]
"""
EG = Namespace("http://example.org/")
class ContentNegotiationHandler(BaseHTTPRequestHandler):
def do_GET(self): # noqa: N802
self.send_response(200, "OK")
# fun fun fun parsing accept header.
acs = self.headers["Accept"].split(",")
acq = [x.split(";") for x in acs if ";" in x]
acn = [(x, "q=1") for x in acs if ";" not in x]
acs = [(x[0].strip(), float(x[1].strip()[2:])) for x in acq + acn]
ac = sorted(acs, key=lambda x: x[1])
ct = ac[-1]
if "application/rdf+xml" in ct:
rct = "application/rdf+xml"
content = xmltestdoc
elif "text/n3" in ct:
rct = "text/n3"
content = n3testdoc
elif "application/trig" in ct:
rct = "application/trig"
content = ttltestdoc
elif "text/plain" in ct or "application/n-triples" in ct:
rct = "text/plain"
content = nttestdoc
elif "application/ld+json" in ct:
rct = "application/ld+json"
content = jsonldtestdoc
else: # "text/turtle" in ct:
rct = "text/turtle"
content = ttltestdoc
self.send_header("Content-type", rct)
self.end_headers()
self.wfile.write(content.encode("utf-8"))
def log_message(self, *args):
pass
class TestGraphHTTP:
def test_content_negotiation(self) -> None:
expected = Graph()
expected.add((EG.a, EG.b, EG.c))
expected_triples = GraphHelper.triple_set(expected)
with ctx_http_handler(ContentNegotiationHandler) as server:
(host, port) = server.server_address
if isinstance(host, (bytes, bytearray)):
host = host.decode("utf-8")
url = f"http://{host}:{port}/foo"
for format in ("xml", "n3", "nt"):
graph = Graph()
graph.parse(url, format=format)
assert expected_triples == GraphHelper.triple_set(graph)
def test_content_negotiation_no_format(self) -> None:
expected = Graph()
expected.add((EG.a, EG.b, EG.c))
expected_triples = GraphHelper.triple_set(expected)
with ctx_http_handler(ContentNegotiationHandler) as server:
(host, port) = server.server_address
if isinstance(host, (bytes, bytearray)):
host = host.decode("utf-8")
url = f"http://{host}:{port}/foo"
graph = Graph()
graph.parse(url)
assert expected_triples == GraphHelper.triple_set(graph)
def test_source(self) -> None:
expected = Graph()
expected.add((EG["a"], EG["b"], EG["c"]))
expected_triples = GraphHelper.triple_set(expected)
with ServedBaseHTTPServerMock() as httpmock:
url = httpmock.url
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
200,
"OK",
f"<{EG['a']}> <{EG['b']}> <{EG['c']}>.".encode(),
{"Content-Type": ["text/turtle"]},
)
)
graph = Graph()
graph.parse(source=url)
assert expected_triples == GraphHelper.triple_set(graph)
def test_3xx(self) -> None:
expected = Graph()
expected.add((EG["a"], EG["b"], EG["c"]))
expected_triples = GraphHelper.triple_set(expected)
with ServedBaseHTTPServerMock() as httpmock:
url = httpmock.url
for idx in range(3):
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
302,
"FOUND",
"".encode(),
{"Location": [f"{url}/loc/302/{idx}"]},
)
)
for idx in range(3):
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
303,
"See Other",
"".encode(),
{"Location": [f"{url}/loc/303/{idx}"]},
)
)
for idx in range(3):
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
308,
"Permanent Redirect",
"".encode(),
{"Location": [f"{url}/loc/308/{idx}"]},
)
)
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
200,
"OK",
f"<{EG['a']}> <{EG['b']}> <{EG['c']}>.".encode(),
{"Content-Type": ["text/turtle"]},
)
)
graph = Graph()
graph.parse(location=url, format="turtle")
assert expected_triples == GraphHelper.triple_set(graph)
httpmock.mocks[MethodName.GET].assert_called()
assert len(httpmock.requests[MethodName.GET]) == 10
for request in httpmock.requests[MethodName.GET]:
# type error: Argument 2 to "match" has incompatible type "Optional[Any]"; expected "str"
assert re.match(r"text/turtle", request.headers.get("Accept")) # type: ignore[arg-type]
request_paths = [
request.path for request in httpmock.requests[MethodName.GET]
]
assert request_paths == [
"/",
"/loc/302/0",
"/loc/302/1",
"/loc/302/2",
"/loc/303/0",
"/loc/303/1",
"/loc/303/2",
"/loc/308/0",
"/loc/308/1",
"/loc/308/2",
]
def test_5xx(self):
with ServedBaseHTTPServerMock() as httpmock:
url = httpmock.url
httpmock.responses[MethodName.GET].append(
MockHTTPResponse(500, "Internal Server Error", "".encode(), {})
)
graph = Graph()
with pytest.raises(HTTPError) as raised:
graph.parse(location=url, format="turtle")
assert raised.value.code == 500
@pytest.mark.parametrize(
["url_suffix", "expected_request"],
[
(
"/resource/Almería",
MOCK_HTTP_REQUEST_WILDCARD._replace(
path="/resource/Almer%C3%ADa",
parsed_path=URL_PARSE_RESULT_WILDCARD._replace(
path="/resource/Almer%C3%ADa"
),
),
),
(
"/resource/Almería?foo=bar",
MOCK_HTTP_REQUEST_WILDCARD._replace(
parsed_path=URL_PARSE_RESULT_WILDCARD._replace(
path="/resource/Almer%C3%ADa"
),
path_query={"foo": ["bar"]},
),
),
],
)
def test_iri_source(
url_suffix: str,
expected_request: MockHTTPRequest,
function_httpmock: ServedBaseHTTPServerMock,
) -> None:
diverse_triples_path = TEST_DATA_DIR / "variants/diverse_triples.ttl"
function_httpmock.responses[MethodName.GET].append(
MockHTTPResponse(
200,
"OK",
diverse_triples_path.read_bytes(),
{"Content-Type": ["text/turtle"]},
)
)
g = Graph()
g.parse(f"{function_httpmock.url}{url_suffix}")
assert function_httpmock.call_count == 1
GraphHelper.assert_triple_sets_equals(cached_graph((diverse_triples_path,)), g)
assert len(g) > 1
req = function_httpmock.requests[MethodName.GET].pop(0)
logging.debug("req = %s", req)
assert expected_request == req