diff --git a/src/dockerflow/sanic/checks.py b/src/dockerflow/sanic/checks.py index 332c865..50c8925 100644 --- a/src/dockerflow/sanic/checks.py +++ b/src/dockerflow/sanic/checks.py @@ -56,12 +56,17 @@ async def check_redis_connected(redis): """ import aioredis + if aioredis.__version__.startswith("1."): + RedisConnectionError = aioredis.ConnectionClosedError + else: + RedisConnectionError = aioredis.ConnectionError + errors = [] try: with await redis.conn as r: result = await r.ping() - except aioredis.ConnectionClosedError as e: + except RedisConnectionError as e: msg = "Could not connect to redis: {!s}".format(e) errors.append(Error(msg, id=health.ERROR_CANNOT_CONNECT_REDIS)) except aioredis.RedisError as e: diff --git a/tests/constraints/sanic-20.txt b/tests/constraints/sanic-20.txt index d450454..7c6c3ea 100644 --- a/tests/constraints/sanic-20.txt +++ b/tests/constraints/sanic-20.txt @@ -1 +1,3 @@ -Sanic<21 +Sanic>=20,<21 +aioredis<2 +sanic_redis<0.3.0 diff --git a/tests/constraints/sanic-21.txt b/tests/constraints/sanic-21.txt new file mode 100644 index 0000000..0d223de --- /dev/null +++ b/tests/constraints/sanic-21.txt @@ -0,0 +1 @@ +Sanic>=21,<22 diff --git a/tests/constraints/sanic-22.txt b/tests/constraints/sanic-22.txt new file mode 100644 index 0000000..97479fa --- /dev/null +++ b/tests/constraints/sanic-22.txt @@ -0,0 +1 @@ +Sanic>=22,<23 diff --git a/tests/requirements/sanic-20.txt b/tests/requirements/sanic-20.txt new file mode 100644 index 0000000..e11c6f2 --- /dev/null +++ b/tests/requirements/sanic-20.txt @@ -0,0 +1,7 @@ +# these are constrained by the files in tests/constraints/*.txt +# to support a triple stack Django/Flask/Sanic +aiohttp +aioredis +Sanic +sanic_redis +uvloop>=0.14.0rc1 diff --git a/tests/requirements/sanic.txt b/tests/requirements/sanic.txt index 41ec1d2..20ffec4 100644 --- a/tests/requirements/sanic.txt +++ b/tests/requirements/sanic.txt @@ -1,7 +1,8 @@ # these are constrained by the files in tests/constraints/*.txt # to support a triple stack Django/Flask/Sanic aiohttp -aioredis<2.0.0 +aioredis Sanic -sanic_redis<0.3.0 +sanic_redis +sanic-testing uvloop>=0.14.0rc1 diff --git a/tests/sanic/test_sanic.py b/tests/sanic/test_sanic.py index f3682a2..b3c4be5 100644 --- a/tests/sanic/test_sanic.py +++ b/tests/sanic/test_sanic.py @@ -3,11 +3,11 @@ # file, you can obtain one at http://mozilla.org/MPL/2.0/. import functools import logging -import socket +import uuid import aioredis import pytest -import sanic.testing +import sanic import sanic_redis.core from sanic import Sanic, response from sanic_redis import SanicRedis @@ -15,9 +15,14 @@ from dockerflow import health from dockerflow.sanic import Dockerflow, checks +if sanic.__version__.startswith("20."): + from sanic.testing import SanicTestClient +else: + from sanic_testing.testing import SanicTestClient + class FakeRedis: - def __init__(self, error=None, **kw): + def __init__(self, *args, error=None, **kw): self.error = error def __await__(self): @@ -30,7 +35,7 @@ def __enter__(self): def __exit__(self, *exc_info): pass - def close(self): + async def close(self): pass async def wait_closed(self): @@ -38,7 +43,11 @@ async def wait_closed(self): async def ping(self): if self.error == "connection": - raise aioredis.ConnectionClosedError("fake") + if aioredis.__version__.startswith("1."): + RedisConnectionError = aioredis.ConnectionClosedError + else: + RedisConnectionError = aioredis.ConnectionError + raise RedisConnectionError("fake") elif self.error == "redis": raise aioredis.RedisError("fake") elif self.error == "malformed": @@ -47,13 +56,20 @@ async def ping(self): return b"PONG" -async def fake_redis(**kw): - return FakeRedis(**kw) +class FakeRedis1(FakeRedis): + def close(self): + pass + + +async def fake_redis(*args, **kw): + if aioredis.__version__.startswith("1."): + return FakeRedis1(*args, **kw) + return FakeRedis(*args, **kw) @pytest.fixture(scope="function") def app(): - app = Sanic("dockerflow") + app = Sanic(f"dockerflow-{uuid.uuid4().hex}") @app.route("/") async def root(request): @@ -77,30 +93,27 @@ def dockerflow_redis(app): @pytest.fixture def test_client(app): - # Create SanicTestClient manually and provide a socket object instead of host - # and port when calling Sanic.run in order to avoid parallel test failures - # caused by Sanic.test_client bindngs to a static port - s = socket.socket() - s.bind((sanic.testing.HOST, 0)) - try: - # initialize test_client with socket's port - test_client = sanic.testing.SanicTestClient(app, s.getsockname()[1]) - # override app.run to drop host and port in favor of socket - run = app.run - app.run = lambda host, port, **kw: run(sock=s, **kw) - # yield test_client - yield test_client - finally: - s.close() + return SanicTestClient(app) -def test_instantiating(app): +@pytest.mark.skipif(not sanic.__version__.startswith("20."), reason="requires sanic 20") +def test_instantiating_sanic_20(app): dockerflow = Dockerflow() assert "dockerflow.heartbeat" not in app.router.routes_names dockerflow.init_app(app) assert "dockerflow.heartbeat" in app.router.routes_names +@pytest.mark.skipif( + sanic.__version__.startswith("20."), reason="requires sanic 21 or later" +) +def test_instantiating(app): + Dockerflow() + assert ("__heartbeat__",) not in app.router.routes_all + Dockerflow(app) + assert ("__heartbeat__",) in app.router.routes_all + + def test_version_exists(dockerflow, mocker, test_client, version_content): mocker.patch.object(dockerflow, "_version_callback", return_value=version_content) _, response = test_client.get("/__version__") @@ -178,7 +191,10 @@ async def warning_check2(): def test_redis_check(dockerflow_redis, mocker, test_client): assert "check_redis_connected" in dockerflow_redis.checks - mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis) + if aioredis.__version__.startswith("1."): + mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis) + else: + mocker.patch.object(sanic_redis.core, "from_url", fake_redis) _, response = test_client.get("/__heartbeat__") assert response.status == 200 assert response.json["status"] == "ok" @@ -198,7 +214,10 @@ def test_redis_check(dockerflow_redis, mocker, test_client): def test_redis_check_error(dockerflow_redis, mocker, test_client, error, messages): assert "check_redis_connected" in dockerflow_redis.checks fake_redis_error = functools.partial(fake_redis, error=error) - mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis_error) + if aioredis.__version__.startswith("1."): + mocker.patch.object(sanic_redis.core, "create_redis_pool", fake_redis_error) + else: + mocker.patch.object(sanic_redis.core, "from_url", fake_redis_error) _, response = test_client.get("/__heartbeat__") assert response.status == 500 assert response.json["status"] == "error" diff --git a/tox.ini b/tox.ini index 7f04c32..378e2a4 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,8 @@ envlist = py{37,38,39,310}-dj32 py{38,39,310}-dj{40} py{37,38,39,310}-fl{012,10,11,20,21} - py{37,38,39}-s{20} + py{37,38,39}-s20 + py{37,38,39,310}-s{21,22} [gh-actions] python = @@ -26,7 +27,8 @@ deps = -rtests/requirements/default.txt dj{32,40}: -rtests/requirements/django.txt fl{012,10,11,20,21}: -rtests/requirements/flask.txt - s{20}: -rtests/requirements/sanic.txt + s20: -rtests/requirements/sanic-20.txt + s{21,22}: -rtests/requirements/sanic.txt dj32: -ctests/constraints/django-3.2.txt dj40: -ctests/constraints/django-4.0.txt fl012: -ctests/constraints/flask-0.12.txt @@ -35,11 +37,13 @@ deps = fl20: -ctests/constraints/flask-2.0.txt fl21: -ctests/constraints/flask-2.0.txt s20: -ctests/constraints/sanic-20.txt + s21: -ctests/constraints/sanic-21.txt + s22: -ctests/constraints/sanic-22.txt commands = python --version dj{32,40}: pytest tests/core/ tests/django --nomigrations {posargs:} fl{012,10,11,20,21}: pytest tests/core/ tests/flask/ {posargs:} - s{20}: pytest tests/core/ tests/sanic/ {posargs:} + s{20,21,22}: pytest tests/core/ tests/sanic/ {posargs:} [testenv:py38-docs] basepython = python3.8