From patchwork Sun Apr 12 16:55:46 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Fr=C3=A9d=C3=A9ric_Mangano-Tarumi?= X-Patchwork-Id: 1580 Return-Path: Delivered-To: patchwork@archlinux.org Received: from apollo.archlinux.org (localhost [127.0.0.1]) by apollo.archlinux.org (Postfix) with ESMTP id 6D2FA18291963 for ; Sun, 12 Apr 2020 16:56:06 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on apollo.archlinux.org X-Spam-Level: X-Spam-Status: No, score=-0.6 required=5.0 tests=DKIM_INVALID=1, DKIM_SIGNED=0.1,MAILING_LIST_MULTI=-1,RCVD_IN_DNSWL_LOW=-0.7, SPF_HELO_NONE=0.001 autolearn=ham autolearn_force=no version=3.4.4 X-Spam-BL-Results: [127.0.9.1] Received: from orion.archlinux.org (orion.archlinux.org [IPv6:2a01:4f8:160:6087::1]) by apollo.archlinux.org (Postfix) with ESMTPS for ; Sun, 12 Apr 2020 16:56:06 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id D2ED21AE96D57D; Sun, 12 Apr 2020 16:55:55 +0000 (UTC) Received: from luna.archlinux.org (luna.archlinux.org [5.9.250.164]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits)) (No client certificate requested) (Authenticated sender: luna) by orion.archlinux.org (Postfix) with ESMTPSA id BE4AD1AE96D578; Sun, 12 Apr 2020 16:55:54 +0000 (UTC) Authentication-Results: orion.archlinux.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=q7G3L+dL Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 854C62B29D; Sun, 12 Apr 2020 16:55:54 +0000 (UTC) Authentication-Results: luna.archlinux.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=mg0.fr header.i=@mg0.fr header.b=q7G3L+dL Received: from luna.archlinux.org (luna.archlinux.org [127.0.0.1]) by luna.archlinux.org (Postfix) with ESMTP id 071F52B29C for ; Sun, 12 Apr 2020 16:55:50 +0000 (UTC) Received: from orion.archlinux.org (orion.archlinux.org [IPv6:2a01:4f8:160:6087::1]) by luna.archlinux.org (Postfix) with ESMTPS for ; Sun, 12 Apr 2020 16:55:49 +0000 (UTC) Received: from orion.archlinux.org (localhost [127.0.0.1]) by orion.archlinux.org (Postfix) with ESMTP id D74221AE96D4A1 for ; Sun, 12 Apr 2020 16:55:47 +0000 (UTC) Received: from tsubame.mg0.fr (tsubame.mg0.fr [IPv6:2001:41d0:401:3100::402b]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-384) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by orion.archlinux.org (Postfix) with ESMTPS for ; Sun, 12 Apr 2020 16:55:47 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mg0.fr; s=tsubame; h=Content-Transfer-Encoding:Content-Type:MIME-Version:Message-ID: Subject:To:From:Date:Sender:Reply-To:Cc:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: In-Reply-To:References:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=g5gVsYMb/GPO0o8V0LASHEYM2wWMNu+hg0zH0CBT13Q=; b=q7G3L+dLUoAnYjrBSA5i6TrGv2 IpEoJPm4sT0UG/z9Pj8pej2BC0cJYFFKyV8s3R3bwXy1gHwI/+14FT3mUjNi7vFWvSo40qdyYbtbp LweoZMRYI7A+Y6HUo5pCPBZ9l+646f7pHKCASdNZ1X3gwT6Eeg86C24mNaLT/BDu9fR8=; Received: from fmang by tsubame.mg0.fr with local (Exim 4.93) (envelope-from ) id 1jNfta-00AtCG-Co for aur-dev@archlinux.org; Sun, 12 Apr 2020 18:55:46 +0200 Date: Sun, 12 Apr 2020 18:55:46 +0200 From: =?utf-8?b?RnLDqWTDqXJpYw==?= Mangano-Tarumi To: aur-dev@archlinux.org Subject: [PATCH] First HTTP functional test of the RPC interface Message-ID: <20200412165546.GA2595450@tsubame.mg0.fr> MIME-Version: 1.0 Content-Disposition: inline X-BeenThere: aur-dev@archlinux.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Arch User Repository \(AUR\) Development" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: aur-dev-bounces@archlinux.org Sender: "aur-dev" 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 + + +@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) + + +@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"