1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
import asyncio
import hashlib
import io
import unittest
from base64 import b64encode
from contextlib import asynccontextmanager, contextmanager
from http import HTTPStatus
from pathlib import Path
from typing import Callable
from unittest.mock import patch
import pytest
from PIL import Image
from slidge.util import SubclassableOnce
SubclassableOnce.TEST_MODE = True
@pytest.fixture
def MockRE():
class MockRE:
@staticmethod
def match(*a, **kw):
return True
return MockRE
@pytest.fixture(scope="session")
def avatar_path() -> Path:
return Path(__file__).parent.parent / "dev" / "assets" / "5x5.png"
@pytest.fixture(scope="session")
def avatar2_path() -> Path:
return Path(__file__).parent.parent / "dev" / "assets" / "slidge-color-small.png"
@pytest.fixture(scope="class")
def avatar(request, avatar_path, avatar2_path):
img = Image.open(avatar_path)
with io.BytesIO() as f:
img.save(f, format="PNG")
img_bytes = f.getvalue()
class MockResponse:
def __init__(self, status, headers=True):
if headers:
self.headers = {"etag": "etag", "last-modified": "last"}
else:
self.headers = {}
self.status = status
@staticmethod
async def read():
return img_bytes
def raise_for_status(self):
pass
@asynccontextmanager
async def mock_get(url, headers=None):
if url == "SLOW":
await asyncio.sleep(1)
else:
assert url in ("AVATAR_URL", "AVATAR_URL_NO_HEADERS")
if headers and (
headers.get("If-None-Match") == "etag"
or headers.get("If-Modified-Since") == "last"
):
yield MockResponse(HTTPStatus.NOT_MODIFIED)
else:
yield MockResponse(HTTPStatus.OK, headers=url == "AVATAR_URL")
@contextmanager
def switch_avatar(_self):
nonlocal img_bytes
backup = img_bytes
with io.BytesIO() as f:
Image.open(avatar2_path).save(f, format="PNG")
img_bytes = f.getvalue()
yield
img_bytes = backup
request.cls.avatar_path = avatar_path
request.cls.avatar_image = img
request.cls.avatar_bytes = img_bytes
request.cls.avatar_sha1 = hashlib.sha1(img_bytes).hexdigest()
request.cls.avatar_url = "AVATAR_URL"
request.cls.avatar_url_no_headers = "AVATAR_URL_NO_HEADERS"
request.cls.switch_avatar = switch_avatar
request.cls.avatar_base64 = b64encode(img_bytes).decode("utf-8")
request.cls.avatar_original_sha1 = hashlib.sha1(avatar_path.read_bytes()).hexdigest()
with patch("slidge.db.avatar.avatar_cache.http", create=True) as mock:
mock.get = mock_get
mock.head = mock_get
yield request
# just to have typings for the fixture which pycharm does not understand
class AvatarFixtureMixin:
avatar_path: Path
avatar_image: Image
avatar_bytes: bytes
avatar_sha1: str
avatar_original_sha1: str
avatar_url: str
avatar_url_no_headers: str
avatar_base64: str
switch_avatar: Callable
class MockUUID4:
def __init__(self, i: int) -> None:
self.i = i
self.hex = str(i)
def __repr__(self):
return self.i.__repr__()
class UUIDFixtureMixin(unittest.TestCase):
def _next_uuid(self):
self._uuid_counter += 1
return self._uuid_counter
def setUp(self):
super().setUp()
self._uuid_counter = 0
self._uuid_patch = unittest.mock.patch("uuid.uuid4", side_effect=self._next_uuid)
self._uuid_patch.start()
def tearDown(self) -> None:
super().tearDown()
self._uuid_patch.stop()
|