import os
import shutil
from datetime import date, timedelta
from datetime import datetime as dt
from datetime import timezone as tz
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
import hircine
import hircine.db as database
import hircine.db.models as models
import hircine.plugins
from hircine.app import schema
from hircine.enums import Category, Censorship, Direction, Language, Layout, Rating
@pytest.fixture(scope="session")
def anyio_backend():
return "asyncio"
def pytest_addoption(parser):
parser.addoption(
"--sql-echo",
action="store_true",
help="Enable logging of SQL statements",
)
@pytest.fixture
def empty_plugins(monkeypatch):
monkeypatch.setattr(hircine.plugins, "scraper_registry", {})
monkeypatch.setattr(hircine.plugins, "transformers", [])
@pytest.fixture
def data(tmpdir, request):
file = request.module.__file__
data = os.path.join(os.path.dirname(file), "data")
if os.path.isdir(data):
shutil.copytree(data, tmpdir, dirs_exist_ok=True)
return lambda dir: os.path.join(tmpdir, dir)
@pytest.fixture(scope="session")
def engine(pytestconfig):
yield database.create_engine(":memory:", echo=pytestconfig.option.sql_echo)
@pytest.fixture
async def session(anyio_backend, engine):
async with engine.begin() as conn:
await conn.begin_nested()
yield AsyncSession(conn, expire_on_commit=False, autoflush=False)
await conn.rollback()
@pytest.fixture(autouse=True)
async def patch_session(anyio_backend, session, engine, monkeypatch):
monkeypatch.setattr(hircine.db, "session", lambda: session)
@pytest.fixture(scope="session", autouse=True)
async def metadata(engine, anyio_backend):
await database.initialize(engine)
@pytest.fixture
def schema_execute():
async def _execute(endpoint, variables=None):
return await schema.execute(endpoint, variable_values=variables)
return _execute
@pytest.fixture
def execute(schema_execute):
def wrapper(q):
async def _execute():
return await schema_execute(q)
return _execute
return wrapper
@pytest.fixture
def execute_add(schema_execute):
def wrapper(q):
async def _execute(input):
return await schema_execute(q, {"input": input})
return _execute
return wrapper
@pytest.fixture
def execute_update(schema_execute):
def wrapper(q):
async def _execute(ids, input):
return await schema_execute(q, {"ids": ids, "input": input})
return _execute
return wrapper
@pytest.fixture
def execute_update_single(schema_execute):
def wrapper(q):
async def _execute(id, input):
return await schema_execute(q, {"id": id, "input": input})
return _execute
return wrapper
@pytest.fixture
def execute_delete(schema_execute):
def wrapper(q):
async def _execute(ids):
return await schema_execute(q, {"ids": ids})
return _execute
return wrapper
@pytest.fixture
def execute_id(schema_execute):
def wrapper(q):
async def _execute(id):
return await schema_execute(q, {"id": id})
return _execute
return wrapper
@pytest.fixture
def execute_filter(schema_execute):
def wrapper(q):
async def _execute(filter=None):
return await schema_execute(q, {"filter": filter} if filter else None)
return _execute
return wrapper
@pytest.fixture
def execute_sort(schema_execute):
def wrapper(q):
async def _execute(sort=None):
return await schema_execute(q, {"sort": sort} if sort else None)
return _execute
return wrapper
class DB:
@staticmethod
async def add(model):
async with database.session() as s:
s.add(model)
await s.commit()
return model
@staticmethod
async def add_all(*models):
async with database.session() as s:
s.add_all(models)
await s.commit()
return models
@staticmethod
async def get(modelcls, id, full=False):
async with database.session() as s:
options = modelcls.load_full() if full else []
model = await s.get(modelcls, id, options=options)
return model
@staticmethod
async def delete(modelcls, id):
async with database.session() as s:
model = await s.get(modelcls, id)
await s.delete(model)
await s.commit()
return
class Response:
def __init__(self, response, key=None):
assert response.errors is None
if key is None:
assert response.data is not None
assert len(response.data) == 1
key = next(iter(response.data.keys()))
assert key in response.data
self.data = response.data.get(key)
self.errors = response.errors
def __getattr__(self, name):
assert name in self.data
return self.data.get(name)
def assert_is(self, typename):
assert self.data["__typename"] == typename
@pytest.fixture
def gen_artist():
def _gen():
yield models.Artist(id=1, name="alan smithee")
yield models.Artist(id=2, name="david agnew")
yield models.Artist(id=3, name="robin bland")
yield models.Artist(id=4, name="robin smith")
return _gen()
@pytest.fixture
def gen_character():
def _gen():
yield models.Character(id=1, name="greta giraffe")
yield models.Character(id=2, name="bob bear")
yield models.Character(id=3, name="rico rhinoceros")
yield models.Character(id=4, name="ziggy zebra")
return _gen()
@pytest.fixture
def gen_circle():
def _gen():
yield models.Circle(id=1, name="archimedes")
yield models.Circle(id=2, name="bankoff")
yield models.Circle(id=3, name="carlyle")
yield models.Circle(id=4, name="ford")
return _gen()
@pytest.fixture
def gen_namespace():
def _gen():
yield models.Namespace(id=1, name="animal", sort_name="animal")
yield models.Namespace(id=2, name="human", sort_name="human")
return _gen()
@pytest.fixture
def gen_tag():
def _gen():
yield models.Tag(
id=1, name="small", description="barely visible", namespaces=[]
)
yield models.Tag(
id=2,
name="medium",
description="mostly average",
namespaces=[],
)
yield models.Tag(id=3, name="big", description="impressive", namespaces=[])
yield models.Tag(
id=4, name="massive", description="what is THAT", namespaces=[]
)
return _gen()
@pytest.fixture
def gen_world():
def _gen():
yield models.World(id=1, name="animal friends")
yield models.World(id=2, name="criminanimals")
yield models.World(id=3, name="in the nude")
yield models.World(id=4, name="wall street")
return _gen()
@pytest.fixture
def gen_image():
def _gen():
yield models.Image(
id=1, hash="1bb05614b44bf177589632a51ce216a2", width=3024, height=2106
)
yield models.Image(
id=2, hash="77dfd96aee1bc8c36ab7095fcf18f7ff", width=3024, height=2094
)
yield models.Image(
id=3, hash="109aac22f29bd361fbfb19f975a1b7f0", width=3019, height=2089
)
yield models.Image(
id=4, hash="e18fc95f00a087ff001ecd8675eddd14", width=3024, height=2097
)
yield models.Image(
id=5, hash="0e2cd2f176e792a3777710978768bc90", width=1607, height=2259
)
yield models.Image(
id=6, hash="64e50730eb842750ebe5417a524b83e6", width=1556, height=2264
)
yield models.Image(
id=7, hash="d906ef54788cae72e1a511c9775e6d68", width=1525, height=2259
)
yield models.Image(
id=8, hash="0f8ead4a60df09a1dd071617b5d5583b", width=1545, height=2264
)
yield models.Image(
id=9, hash="912ccb4350fb17ea1248e26ecfb5d983", width=1607, height=2259
)
yield models.Image(
id=10, hash="108edee1b417f022a6d1f999bd32d16d", width=1546, height=2224
)
yield models.Image(
id=11, hash="97c0903cb0962741174f264aaa7015d4", width=1528, height=2257
)
yield models.Image(
id=12, hash="b5490ad31d2a8910087ba932073b4e52", width=1543, height=2271
)
yield models.Image(
id=13, hash="c9ab7febcb81974a992ed1de60c728ba", width=1611, height=2257
)
yield models.Image(
id=14, hash="bcfdf22ec17a09cd4f6a0af86e966e8f", width=1553, height=2265
)
yield models.Image(
id=15, hash="1f58f4b08bf6f4ca92bd29cbce26241e", width=1526, height=2258
)
yield models.Image(
id=16, hash="f87d7e55203b5e7cf9c801db48624ef0", width=1645, height=2262
)
return _gen()
@pytest.fixture
def gen_page(gen_image):
def _gen():
yield models.Page(id=1, index=1, path="001.png", image=next(gen_image))
yield models.Page(id=2, index=2, path="002.png", image=next(gen_image))
yield models.Page(id=3, index=3, path="003.png", image=next(gen_image))
yield models.Page(id=4, index=4, path="004.png", image=next(gen_image))
yield models.Page(id=5, index=1, path="00.jpg", image=next(gen_image))
yield models.Page(id=6, index=2, path="01.jpg", image=next(gen_image))
yield models.Page(id=7, index=3, path="02.jpg", image=next(gen_image))
yield models.Page(id=8, index=4, path="03.jpg", image=next(gen_image))
yield models.Page(id=9, index=1, path="1.jpg", image=next(gen_image))
yield models.Page(id=10, index=2, path="2.jpg", image=next(gen_image))
yield models.Page(id=11, index=3, path="10.jpg", image=next(gen_image))
yield models.Page(id=12, index=4, path="11.jpg", image=next(gen_image))
yield models.Page(id=13, index=1, path="010.png", image=next(gen_image))
yield models.Page(id=14, index=2, path="011.png", image=next(gen_image))
yield models.Page(id=15, index=3, path="012.png", image=next(gen_image))
yield models.Page(id=16, index=4, path="013.png", image=next(gen_image))
return _gen()
@pytest.fixture
def gen_jumbled_pages(gen_image):
def _gen():
yield models.Page(id=101, index=3, path="3.png", image=next(gen_image))
yield models.Page(id=52, index=9, path="9.png", image=next(gen_image))
yield models.Page(id=13, index=2, path="2.png", image=next(gen_image))
yield models.Page(id=258, index=10, path="10.png", image=next(gen_image))
yield models.Page(id=7, index=7, path="7.jpg", image=next(gen_image))
yield models.Page(id=25, index=5, path="5.jpg", image=next(gen_image))
yield models.Page(id=150, index=1, path="1.jpg", image=next(gen_image))
yield models.Page(id=69, index=4, path="4.jpg", image=next(gen_image))
yield models.Page(id=219, index=6, path="6.jpg", image=next(gen_image))
yield models.Page(id=34, index=8, path="8.jpg", image=next(gen_image))
return _gen()
@pytest.fixture
def gen_jumbled_archive(gen_jumbled_pages):
def _gen():
pages = [next(gen_jumbled_pages) for _ in range(10)]
yield models.Archive(
id=100,
hash="4e1243bd22c66e76c2ba9eddc1f91394",
path="comics/jumbled.zip",
size=32559235,
mtime=dt(2002, 1, 23).astimezone(),
cover=pages[0].image,
pages=pages,
page_count=len(pages),
)
return _gen()
@pytest.fixture
def gen_archive(gen_page):
def _gen():
pages = [next(gen_page) for _ in range(4)]
yield models.Archive(
id=1,
hash="1d394f66c49ccb1d3c30870904d31bd4",
path="comics/archive-01.zip",
size=7340032,
mtime=dt(2016, 5, 10).astimezone(),
cover=pages[0].image,
pages=pages,
page_count=len(pages),
)
pages = [next(gen_page) for _ in range(4)]
yield models.Archive(
id=2,
hash="d7d8929b2e606200e863d390f71b53bb",
path="comics/archive-02.zip",
size=11335106,
mtime=dt(2008, 10, 2, tzinfo=tz(timedelta(hours=+6))),
cover=pages[0].image,
pages=pages,
page_count=len(pages),
)
pages = [next(gen_page) for _ in range(4)]
yield models.Archive(
id=3,
hash="02669dbe08c4a5f4820c10b3ff2178fa",
path="comics/sub/archive-new.zip",
size=51841969,
mtime=dt(2005, 11, 17, tzinfo=tz(timedelta(hours=+2))),
cover=pages[0].image,
pages=pages,
page_count=len(pages),
)
pages = [next(gen_page) for _ in range(4)]
yield models.Archive(
id=4,
hash="6b2ecf5ceb8befd6d0c1cd353a3df709",
path="comics/archive-03.zip",
size=13568769,
mtime=dt(1999, 5, 8, tzinfo=tz(timedelta(hours=-2))),
cover=pages[0].image,
pages=pages,
page_count=len(pages),
)
return _gen()
@pytest.fixture
def gen_comic(
gen_archive,
gen_artist,
gen_character,
gen_circle,
gen_world,
gen_tag,
gen_namespace,
):
def _gen():
artists = {a.id: a for a in gen_artist}
characters = {c.id: c for c in gen_character}
namespaces = {ns.id: ns for ns in gen_namespace}
tags = {t.id: t for t in gen_tag}
def tag(nid, tid):
return models.ComicTag(namespace=namespaces[nid], tag=tags[tid])
archive = next(gen_archive)
yield models.Comic(
id=1,
title="Arid Savannah Adventures",
url="file:///home/savannah/adventures",
category=Category.MANGA,
censorship=Censorship.NONE,
date=date(2010, 7, 5),
direction=Direction.LEFT_TO_RIGHT,
favourite=True,
language=Language.EN,
layout=Layout.SINGLE,
rating=Rating.SAFE,
archive=archive,
artists=[artists[1], artists[2]],
characters=list(characters.values()),
circles=[next(gen_circle)],
worlds=[next(gen_world)],
cover=archive.cover,
pages=archive.pages,
tags=[
tag(1, 1),
tag(1, 2),
tag(1, 3),
tag(1, 4),
],
)
archive = next(gen_archive)
yield models.Comic(
id=2,
title="This Giraffe Stole My Wallet",
original_title="Diese Giraffe hat mein Geldbeutel geklaut",
url="ftp://crimes.local/giraffes.zip",
category=Category.MANGA,
censorship=Censorship.BAR,
date=date(2002, 2, 17),
direction=Direction.LEFT_TO_RIGHT,
favourite=False,
language=Language.EN,
layout=Layout.SINGLE,
rating=Rating.QUESTIONABLE,
archive=archive,
artists=[artists[3]],
characters=[characters[1]],
circles=[next(gen_circle)],
worlds=[next(gen_world)],
cover=archive.cover,
pages=archive.pages,
tags=[
tag(1, 3),
tag(2, 1),
],
)
archive = next(gen_archive)
yield models.Comic(
id=3,
title="サイのスパ",
category=Category.ARTBOOK,
censorship=Censorship.MOSAIC,
date=date(2017, 5, 3),
direction=Direction.RIGHT_TO_LEFT,
favourite=False,
language=Language.JA,
layout=Layout.DOUBLE_OFFSET,
rating=Rating.EXPLICIT,
archive=archive,
artists=[artists[1], artists[4]],
characters=[characters[3]],
circles=[next(gen_circle)],
worlds=[next(gen_world)],
cover=archive.cover,
pages=archive.pages,
tags=[
tag(1, 4),
],
)
archive = next(gen_archive)
yield models.Comic(
id=4,
title="In the Company of Vultures",
category=Category.DOUJINSHI,
date=date(2023, 3, 10),
direction=Direction.LEFT_TO_RIGHT,
favourite=False,
language=Language.EN,
layout=Layout.SINGLE,
rating=Rating.SAFE,
archive=archive,
artists=[artists[4]],
characters=[characters[4]],
circles=[next(gen_circle)],
worlds=[next(gen_world)],
cover=archive.cover,
pages=archive.pages,
tags=[
tag(2, 1),
tag(2, 2),
tag(2, 3),
],
)
return _gen()
@pytest.fixture
def empty_comic(gen_archive):
archive = next(gen_archive)
yield models.Comic(
id=100,
title="Hic Sunt Dracones",
archive=archive,
cover=archive.cover,
pages=archive.pages,
)