[PATCH] First HTTP functional test of the RPC interface

Frédéric Mangano-Tarumi fmang at mg0.fr
Sun Apr 12 16:55:46 UTC 2020


Though barely useful in its current state, it shows how to integrate
pytest with SQLAlchemy and Werkzeug, providing a test framework for the
potential future Flask port, while actually testing the current PHP
implementation.
---
 aurweb/test/__init__.py |  0
 aurweb/test/conftest.py | 51 +++++++++++++++++++++++++++++++++++++++++
 aurweb/test/test_rpc.py | 21 +++++++++++++++++
 aurweb/test/wsgihttp.py | 38 ++++++++++++++++++++++++++++++
 test/README.md          |  3 +++
 test/rpc.t              |  2 ++
 6 files changed, 115 insertions(+)
 create mode 100644 aurweb/test/__init__.py
 create mode 100644 aurweb/test/conftest.py
 create mode 100644 aurweb/test/test_rpc.py
 create mode 100644 aurweb/test/wsgihttp.py
 create mode 100755 test/rpc.t

diff --git a/aurweb/test/__init__.py b/aurweb/test/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/aurweb/test/conftest.py b/aurweb/test/conftest.py
new file mode 100644
index 00000000..49cc2f6e
--- /dev/null
+++ b/aurweb/test/conftest.py
@@ -0,0 +1,51 @@
+"""
+Fixtures for pytest.
+
+This module is automatically loaded by pytest.
+"""
+
+import pytest
+import sqlalchemy
+import werkzeug.test
+import werkzeug.wrappers
+import werkzeug.wrappers.json
+
+import aurweb.config
+import aurweb.db
+from aurweb.test.wsgihttp import WsgiHttpProxy
+
+
+class Response(werkzeug.wrappers.CommonResponseDescriptorsMixin,
+               werkzeug.wrappers.json.JSONMixin,
+               werkzeug.wrappers.BaseResponse):
+    """
+    Custom response object to be returned by the test client. More mixins could
+    be added if need be.
+
+    See https://werkzeug.palletsprojects.com/en/1.0.x/wrappers/#mixin-classes
+    """
+    pass
+
+
+ at pytest.fixture
+def client():
+    """
+    Build a Werkzeug test client for making HTTP requests to AUR. It requires
+    that the AUR test website is already running at `[options] aur_location`,
+    specified in the configuration file.
+
+    When aurweb becomes a pure Flask application, this should return Flask’s
+    test_client(), which is a Werkzeug test client too.
+    https://flask.palletsprojects.com/en/1.1.x/testing/#the-testing-skeleton
+    """
+    base_uri = aurweb.config.get("options", "aur_location")
+    proxy = WsgiHttpProxy(base_uri)
+    return werkzeug.test.Client(proxy, Response)
+
+
+ at pytest.fixture(scope="session")
+def db_engine():
+    """
+    Return an SQLAlchemy engine to the configured database.
+    """
+    return sqlalchemy.create_engine(aurweb.db.get_sqlalchemy_url())
diff --git a/aurweb/test/test_rpc.py b/aurweb/test/test_rpc.py
new file mode 100644
index 00000000..7079145c
--- /dev/null
+++ b/aurweb/test/test_rpc.py
@@ -0,0 +1,21 @@
+"""
+Test suite for the RPC interface.
+
+See also `doc/rpc.txt` for the RPC interface documentation.
+"""
+
+import pytest
+from sqlalchemy.sql import select
+
+from aurweb.schema import Packages
+
+
+def test_search_by_name(client, db_engine):
+    """Take a package from the database, and find it through the RPC interface."""
+    with db_engine.connect() as conn:
+        pkg = conn.execute(select([Packages]).limit(1)).fetchone()
+        if pkg is None:
+            pytest.skip("needs at least one package in the database")
+    resp = client.get("/rpc/", query_string={"v": "5", "type": "search", "arg": pkg["Name"]})
+    result = resp.json
+    assert result["resultcount"] >= 1
diff --git a/aurweb/test/wsgihttp.py b/aurweb/test/wsgihttp.py
new file mode 100644
index 00000000..5b9d8040
--- /dev/null
+++ b/aurweb/test/wsgihttp.py
@@ -0,0 +1,38 @@
+import http.client
+import urllib.parse
+
+
+class WsgiHttpProxy:
+    """
+    WSGI-to-HTTP proxy, that is to say a WSGI application that forwards every
+    WSGI request to an HTTP server, then the HTTP response back to WSGI.
+
+    The base URL the constructor takes is something like
+    `http://localhost:8080`. It must not contain a path, a query string or a
+    fragment, as the proxy wouldn’t now what to do with it.
+
+    Only the HTTP scheme is supported, but HTTPS could probably be easily added.
+    """
+
+    def __init__(self, base_url):
+        parts = urllib.parse.urlsplit(base_url)
+        self.netloc = parts.netloc
+        # Limitations of this dumb proxy
+        assert parts.scheme == "http"
+        assert parts.path in ("", "/")
+        assert parts.query == ""
+        assert parts.fragment == ""
+
+    def __call__(self, environ, start_response):
+        conn = http.client.HTTPConnection(self.netloc)
+        conn.request(
+            method=environ["REQUEST_METHOD"],
+            url=urllib.parse.urlunsplit((
+                "http", self.netloc,
+                urllib.parse.quote(environ["PATH_INFO"]),
+                environ["QUERY_STRING"], "")),
+            body=environ["wsgi.input"].read(int(environ.get("CONTENT_LENGTH", 0))),
+        )
+        resp = conn.getresponse()
+        start_response(f"{resp.status} {resp.reason}", resp.getheaders())
+        return resp
diff --git a/test/README.md b/test/README.md
index de7eff18..cc8baf33 100644
--- a/test/README.md
+++ b/test/README.md
@@ -20,8 +20,11 @@ For all the test to run, the following Arch packages should be installed:
 - python-bleach
 - python-markdown
 - python-pygit2
+- python-pytest
+- python-pytest-tap
 - python-sqlalchemy
 - python-srcinfo
+- python-werkzeug
 
 Writing tests
 -------------
diff --git a/test/rpc.t b/test/rpc.t
new file mode 100755
index 00000000..f950f7df
--- /dev/null
+++ b/test/rpc.t
@@ -0,0 +1,2 @@
+#!/bin/sh
+pytest --tap-stream "$(dirname "$0")/../aurweb/test/test_rpc.py"
-- 
2.26.0


More information about the aur-dev mailing list