diff options
Diffstat (limited to '')
-rw-r--r-- | testing/mozbase/mozhttpd/tests/api.py | 381 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/baseurl.py | 33 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/basic.py | 50 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/filelisting.py | 68 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/manifest.ini | 10 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/paths.py | 121 | ||||
-rw-r--r-- | testing/mozbase/mozhttpd/tests/requestlog.py | 62 |
7 files changed, 725 insertions, 0 deletions
diff --git a/testing/mozbase/mozhttpd/tests/api.py b/testing/mozbase/mozhttpd/tests/api.py new file mode 100644 index 0000000000..c2fce58be9 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/api.py @@ -0,0 +1,381 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import collections +import json +import os + +import mozhttpd +import mozunit +import pytest +from six import ensure_binary, ensure_str +from six.moves.urllib.error import HTTPError +from six.moves.urllib.request import ( + HTTPHandler, + ProxyHandler, + Request, + build_opener, + install_opener, + urlopen, +) + + +def httpd_url(httpd, path, querystr=None): + """Return the URL to a started MozHttpd server for the given info.""" + + url = "http://127.0.0.1:{port}{path}".format( + port=httpd.httpd.server_port, + path=path, + ) + + if querystr is not None: + url = "{url}?{querystr}".format( + url=url, + querystr=querystr, + ) + + return url + + +@pytest.fixture(name="num_requests") +def fixture_num_requests(): + """Return a defaultdict to count requests to HTTP handlers.""" + return collections.defaultdict(int) + + +@pytest.fixture(name="try_get") +def fixture_try_get(num_requests): + """Return a function to try GET requests to the server.""" + + def try_get(httpd, querystr): + """Try GET requests to the server.""" + + num_requests["get_handler"] = 0 + + f = urlopen(httpd_url(httpd, "/api/resource/1", querystr)) + + assert f.getcode() == 200 + assert json.loads(f.read()) == {"called": 1, "id": "1", "query": querystr} + assert num_requests["get_handler"] == 1 + + return try_get + + +@pytest.fixture(name="try_post") +def fixture_try_post(num_requests): + """Return a function to try POST calls to the server.""" + + def try_post(httpd, querystr): + """Try POST calls to the server.""" + + num_requests["post_handler"] = 0 + + postdata = {"hamburgers": "1234"} + + f = urlopen( + httpd_url(httpd, "/api/resource/", querystr), + data=ensure_binary(json.dumps(postdata)), + ) + + assert f.getcode() == 201 + assert json.loads(f.read()) == { + "called": 1, + "data": postdata, + "query": querystr, + } + assert num_requests["post_handler"] == 1 + + return try_post + + +@pytest.fixture(name="try_del") +def fixture_try_del(num_requests): + """Return a function to try DEL calls to the server.""" + + def try_del(httpd, querystr): + """Try DEL calls to the server.""" + + num_requests["del_handler"] = 0 + + opener = build_opener(HTTPHandler) + request = Request(httpd_url(httpd, "/api/resource/1", querystr)) + request.get_method = lambda: "DEL" + f = opener.open(request) + + assert f.getcode() == 200 + assert json.loads(f.read()) == {"called": 1, "id": "1", "query": querystr} + assert num_requests["del_handler"] == 1 + + return try_del + + +@pytest.fixture(name="httpd_no_urlhandlers") +def fixture_httpd_no_urlhandlers(): + """Yields a started MozHttpd server with no URL handlers.""" + httpd = mozhttpd.MozHttpd(port=0) + httpd.start(block=False) + yield httpd + httpd.stop() + + +@pytest.fixture(name="httpd_with_docroot") +def fixture_httpd_with_docroot(num_requests): + """Yields a started MozHttpd server with docroot set.""" + + @mozhttpd.handlers.json_response + def get_handler(request, objid): + """Handler for HTTP GET requests.""" + + num_requests["get_handler"] += 1 + + return ( + 200, + { + "called": num_requests["get_handler"], + "id": objid, + "query": request.query, + }, + ) + + httpd = mozhttpd.MozHttpd( + port=0, + docroot=os.path.dirname(os.path.abspath(__file__)), + urlhandlers=[ + { + "method": "GET", + "path": "/api/resource/([^/]+)/?", + "function": get_handler, + } + ], + ) + + httpd.start(block=False) + yield httpd + httpd.stop() + + +@pytest.fixture(name="httpd") +def fixture_httpd(num_requests): + """Yield a started MozHttpd server.""" + + @mozhttpd.handlers.json_response + def get_handler(request, objid): + """Handler for HTTP GET requests.""" + + num_requests["get_handler"] += 1 + + return ( + 200, + { + "called": num_requests["get_handler"], + "id": objid, + "query": request.query, + }, + ) + + @mozhttpd.handlers.json_response + def post_handler(request): + """Handler for HTTP POST requests.""" + + num_requests["post_handler"] += 1 + + return ( + 201, + { + "called": num_requests["post_handler"], + "data": json.loads(request.body), + "query": request.query, + }, + ) + + @mozhttpd.handlers.json_response + def del_handler(request, objid): + """Handler for HTTP DEL requests.""" + + num_requests["del_handler"] += 1 + + return ( + 200, + { + "called": num_requests["del_handler"], + "id": objid, + "query": request.query, + }, + ) + + httpd = mozhttpd.MozHttpd( + port=0, + urlhandlers=[ + { + "method": "GET", + "path": "/api/resource/([^/]+)/?", + "function": get_handler, + }, + { + "method": "POST", + "path": "/api/resource/?", + "function": post_handler, + }, + { + "method": "DEL", + "path": "/api/resource/([^/]+)/?", + "function": del_handler, + }, + ], + ) + + httpd.start(block=False) + yield httpd + httpd.stop() + + +def test_api(httpd, try_get, try_post, try_del): + # GET requests + try_get(httpd, "") + try_get(httpd, "?foo=bar") + + # POST requests + try_post(httpd, "") + try_post(httpd, "?foo=bar") + + # DEL requests + try_del(httpd, "") + try_del(httpd, "?foo=bar") + + # GET: By default we don't serve any files if we just define an API + with pytest.raises(HTTPError) as exc_info: + urlopen(httpd_url(httpd, "/")) + + assert exc_info.value.code == 404 + + +def test_nonexistent_resources(httpd_no_urlhandlers): + # GET: Return 404 for non-existent endpoint + with pytest.raises(HTTPError) as excinfo: + urlopen(httpd_url(httpd_no_urlhandlers, "/api/resource/")) + assert excinfo.value.code == 404 + + # POST: POST should also return 404 + with pytest.raises(HTTPError) as excinfo: + urlopen( + httpd_url(httpd_no_urlhandlers, "/api/resource/"), + data=ensure_binary(json.dumps({})), + ) + assert excinfo.value.code == 404 + + # DEL: DEL should also return 404 + opener = build_opener(HTTPHandler) + request = Request(httpd_url(httpd_no_urlhandlers, "/api/resource/")) + request.get_method = lambda: "DEL" + + with pytest.raises(HTTPError) as excinfo: + opener.open(request) + assert excinfo.value.code == 404 + + +def test_api_with_docroot(httpd_with_docroot, try_get): + f = urlopen(httpd_url(httpd_with_docroot, "/")) + assert f.getcode() == 200 + assert "Directory listing for" in ensure_str(f.read()) + + # Make sure API methods still work + try_get(httpd_with_docroot, "") + try_get(httpd_with_docroot, "?foo=bar") + + +def index_contents(host): + """Return the expected index contents for the given host.""" + return "{host} index".format(host=host) + + +@pytest.fixture(name="hosts") +def fixture_hosts(): + """Returns a tuple of hosts.""" + return ("mozilla.com", "mozilla.org") + + +@pytest.fixture(name="docroot") +def fixture_docroot(tmpdir): + """Returns a path object to a temporary docroot directory.""" + docroot = tmpdir.mkdir("docroot") + index_file = docroot.join("index.html") + index_file.write(index_contents("*")) + + yield docroot + + docroot.remove() + + +@pytest.fixture(name="httpd_with_proxy_handler") +def fixture_httpd_with_proxy_handler(docroot): + """Yields a started MozHttpd server for the proxy test.""" + + httpd = mozhttpd.MozHttpd(port=0, docroot=str(docroot)) + httpd.start(block=False) + + port = httpd.httpd.server_port + proxy_support = ProxyHandler( + { + "http": "http://127.0.0.1:{port:d}".format(port=port), + } + ) + install_opener(build_opener(proxy_support)) + + yield httpd + + httpd.stop() + + # Reset proxy opener in case it changed + install_opener(None) + + +def test_proxy(httpd_with_proxy_handler, hosts): + for host in hosts: + f = urlopen("http://{host}/".format(host=host)) + assert f.getcode() == 200 + assert f.read() == ensure_binary(index_contents("*")) + + +@pytest.fixture(name="httpd_with_proxy_host_dirs") +def fixture_httpd_with_proxy_host_dirs(docroot, hosts): + for host in hosts: + index_file = docroot.mkdir(host).join("index.html") + index_file.write(index_contents(host)) + + httpd = mozhttpd.MozHttpd(port=0, docroot=str(docroot), proxy_host_dirs=True) + + httpd.start(block=False) + + port = httpd.httpd.server_port + proxy_support = ProxyHandler( + {"http": "http://127.0.0.1:{port:d}".format(port=port)} + ) + install_opener(build_opener(proxy_support)) + + yield httpd + + httpd.stop() + + # Reset proxy opener in case it changed + install_opener(None) + + +def test_proxy_separate_directories(httpd_with_proxy_host_dirs, hosts): + for host in hosts: + f = urlopen("http://{host}/".format(host=host)) + assert f.getcode() == 200 + assert f.read() == ensure_binary(index_contents(host)) + + unproxied_host = "notmozilla.org" + + with pytest.raises(HTTPError) as excinfo: + urlopen("http://{host}/".format(host=unproxied_host)) + + assert excinfo.value.code == 404 + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozhttpd/tests/baseurl.py b/testing/mozbase/mozhttpd/tests/baseurl.py new file mode 100644 index 0000000000..4bf923a8d7 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/baseurl.py @@ -0,0 +1,33 @@ +import mozhttpd +import mozunit +import pytest + + +@pytest.fixture(name="httpd") +def fixture_httpd(): + """Yields a started MozHttpd server.""" + httpd = mozhttpd.MozHttpd(port=0) + httpd.start(block=False) + yield httpd + httpd.stop() + + +def test_base_url(httpd): + port = httpd.httpd.server_port + + want = "http://127.0.0.1:{}/".format(port) + got = httpd.get_url() + assert got == want + + want = "http://127.0.0.1:{}/cheezburgers.html".format(port) + got = httpd.get_url(path="/cheezburgers.html") + assert got == want + + +def test_base_url_when_not_started(): + httpd = mozhttpd.MozHttpd(port=0) + assert httpd.get_url() is None + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozhttpd/tests/basic.py b/testing/mozbase/mozhttpd/tests/basic.py new file mode 100644 index 0000000000..a9dcf109e0 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/basic.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +import os + +import mozfile +import mozhttpd +import mozunit +import pytest + + +@pytest.fixture(name="files") +def fixture_files(): + """Return a list of tuples with name and binary_string.""" + return [("small", os.urandom(128)), ("large", os.urandom(16384))] + + +@pytest.fixture(name="docroot") +def fixture_docroot(tmpdir, files): + """Yield a str path to docroot.""" + docroot = tmpdir.mkdir("docroot") + + for name, binary_string in files: + filename = docroot.join(name) + filename.write_binary(binary_string) + + yield str(docroot) + + docroot.remove() + + +@pytest.fixture(name="httpd_url") +def fixture_httpd_url(docroot): + """Yield the URL to a started MozHttpd server.""" + httpd = mozhttpd.MozHttpd(docroot=docroot) + httpd.start() + yield httpd.get_url() + httpd.stop() + + +def test_basic(httpd_url, files): + """Test that mozhttpd can serve files.""" + + # Retrieve file and check contents matchup + for name, binary_string in files: + retrieved_content = mozfile.load(httpd_url + name).read() + assert retrieved_content == binary_string + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozhttpd/tests/filelisting.py b/testing/mozbase/mozhttpd/tests/filelisting.py new file mode 100644 index 0000000000..195059a261 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/filelisting.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import re + +import mozhttpd +import mozunit +import pytest +from six import ensure_str +from six.moves.urllib.request import urlopen + + +@pytest.fixture(name="docroot") +def fixture_docroot(): + """Returns a docroot path.""" + return os.path.dirname(os.path.abspath(__file__)) + + +@pytest.fixture(name="httpd") +def fixture_httpd(docroot): + """Yields a started MozHttpd server.""" + httpd = mozhttpd.MozHttpd(port=0, docroot=docroot) + httpd.start(block=False) + yield httpd + httpd.stop() + + +@pytest.mark.parametrize( + "path", + [ + pytest.param("", id="no_params"), + pytest.param("?foo=bar&fleem=&foo=fleem", id="with_params"), + ], +) +def test_filelist(httpd, docroot, path): + f = urlopen( + "http://{host}:{port}/{path}".format( + host="127.0.0.1", port=httpd.httpd.server_port, path=path + ) + ) + + filelist = os.listdir(docroot) + + pattern = "\<[a-zA-Z0-9\-\_\.\=\"'\/\\\%\!\@\#\$\^\&\*\(\) :;]*\>" + + for line in f.readlines(): + subbed_lined = re.sub(pattern, "", ensure_str(line).strip("\n")) + webline = subbed_lined.strip("/").strip().strip("@") + + if ( + webline + and not webline.startswith("Directory listing for") + and not webline.startswith("<!DOCTYPE") + ): + msg = "File {} in dir listing corresponds to a file".format(webline) + assert webline in filelist, msg + filelist.remove(webline) + + msg = "Should have no items in filelist ({}) unaccounted for".format(filelist) + assert len(filelist) == 0, msg + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozhttpd/tests/manifest.ini b/testing/mozbase/mozhttpd/tests/manifest.ini new file mode 100644 index 0000000000..dde73a6963 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/manifest.ini @@ -0,0 +1,10 @@ +[DEFAULT] +subsuite = mozbase +[api.py] +skip-if = python == 3 +[baseurl.py] +[basic.py] +[filelisting.py] +skip-if = python == 3 +[paths.py] +[requestlog.py] diff --git a/testing/mozbase/mozhttpd/tests/paths.py b/testing/mozbase/mozhttpd/tests/paths.py new file mode 100644 index 0000000000..6d4c2ce953 --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/paths.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python + +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +import mozhttpd +import mozunit +import pytest +from six.moves.urllib.error import HTTPError +from six.moves.urllib.request import urlopen + + +def try_get(url, expected_contents): + f = urlopen(url) + assert f.getcode() == 200 + assert f.read() == expected_contents + + +def try_get_expect_404(url): + with pytest.raises(HTTPError) as excinfo: + urlopen(url) + assert excinfo.value.code == 404 + + +@pytest.fixture(name="httpd_basic") +def fixture_httpd_basic(tmpdir): + d1 = tmpdir.mkdir("d1") + d1.join("test1.txt").write("test 1 contents") + + d2 = tmpdir.mkdir("d2") + d2.join("test2.txt").write("test 2 contents") + + httpd = mozhttpd.MozHttpd( + port=0, + docroot=str(d1), + path_mappings={"/files": str(d2)}, + ) + httpd.start(block=False) + + yield httpd + + httpd.stop() + d1.remove() + d2.remove() + + +def test_basic(httpd_basic): + """Test that requests to docroot and a path mapping work as expected.""" + try_get(httpd_basic.get_url("/test1.txt"), b"test 1 contents") + try_get(httpd_basic.get_url("/files/test2.txt"), b"test 2 contents") + try_get_expect_404(httpd_basic.get_url("/files/test2_nope.txt")) + + +@pytest.fixture(name="httpd_substring_mappings") +def fixture_httpd_substring_mappings(tmpdir): + d1 = tmpdir.mkdir("d1") + d1.join("test1.txt").write("test 1 contents") + + d2 = tmpdir.mkdir("d2") + d2.join("test2.txt").write("test 2 contents") + + httpd = mozhttpd.MozHttpd( + port=0, + path_mappings={"/abcxyz": str(d1), "/abc": str(d2)}, + ) + httpd.start(block=False) + yield httpd + httpd.stop() + d1.remove() + d2.remove() + + +def test_substring_mappings(httpd_substring_mappings): + httpd = httpd_substring_mappings + try_get(httpd.get_url("/abcxyz/test1.txt"), b"test 1 contents") + try_get(httpd.get_url("/abc/test2.txt"), b"test 2 contents") + + +@pytest.fixture(name="httpd_multipart_path_mapping") +def fixture_httpd_multipart_path_mapping(tmpdir): + d1 = tmpdir.mkdir("d1") + d1.join("test1.txt").write("test 1 contents") + + httpd = mozhttpd.MozHttpd( + port=0, + path_mappings={"/abc/def/ghi": str(d1)}, + ) + httpd.start(block=False) + yield httpd + httpd.stop() + d1.remove() + + +def test_multipart_path_mapping(httpd_multipart_path_mapping): + """Test that a path mapping with multiple directories works.""" + httpd = httpd_multipart_path_mapping + try_get(httpd.get_url("/abc/def/ghi/test1.txt"), b"test 1 contents") + try_get_expect_404(httpd.get_url("/abc/test1.txt")) + try_get_expect_404(httpd.get_url("/abc/def/test1.txt")) + + +@pytest.fixture(name="httpd_no_docroot") +def fixture_httpd_no_docroot(tmpdir): + d1 = tmpdir.mkdir("d1") + httpd = mozhttpd.MozHttpd( + port=0, + path_mappings={"/foo": str(d1)}, + ) + httpd.start(block=False) + yield httpd + httpd.stop() + d1.remove() + + +def test_no_docroot(httpd_no_docroot): + """Test that path mappings with no docroot work.""" + try_get_expect_404(httpd_no_docroot.get_url()) + + +if __name__ == "__main__": + mozunit.main() diff --git a/testing/mozbase/mozhttpd/tests/requestlog.py b/testing/mozbase/mozhttpd/tests/requestlog.py new file mode 100644 index 0000000000..8e7b065f3d --- /dev/null +++ b/testing/mozbase/mozhttpd/tests/requestlog.py @@ -0,0 +1,62 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import os + +import mozhttpd +import mozunit +import pytest +from six.moves.urllib.request import urlopen + + +def log_requests(enabled): + """Decorator to change the log_requests parameter for MozHttpd.""" + param_id = "enabled" if enabled else "disabled" + return pytest.mark.parametrize("log_requests", [enabled], ids=[param_id]) + + +@pytest.fixture(name="docroot") +def fixture_docroot(): + """Return a docroot path.""" + return os.path.dirname(os.path.abspath(__file__)) + + +@pytest.fixture(name="request_log") +def fixture_request_log(docroot, log_requests): + """Yields the request log of a started MozHttpd server.""" + httpd = mozhttpd.MozHttpd( + port=0, + docroot=docroot, + log_requests=log_requests, + ) + httpd.start(block=False) + + url = "http://{host}:{port}/".format( + host="127.0.0.1", + port=httpd.httpd.server_port, + ) + f = urlopen(url) + f.read() + + yield httpd.request_log + + httpd.stop() + + +@log_requests(True) +def test_logging_enabled(request_log): + assert len(request_log) == 1 + log_entry = request_log[0] + assert log_entry["method"] == "GET" + assert log_entry["path"] == "/" + assert type(log_entry["time"]) == float + + +@log_requests(False) +def test_logging_disabled(request_log): + assert len(request_log) == 0 + + +if __name__ == "__main__": + mozunit.main() |