feat: import F95 screenshot galleries with AVIF support
CI / frontend (push) Failing after 19s
CI / backend (push) Failing after 41s

This commit is contained in:
mrleo1nid committed 2026-10-26 17:51:19 +03:00
1 parent 5b911d1e3b
commit 69f71cb836
21 files changed
+513 -33

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
import asyncio
import importlib
import io
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager
from types import SimpleNamespace
from unittest.mock import patch
from alembic.config import Config
from alembic.migration import MigrationContext
from alembic.operations import Operations
from alembic.script import ScriptDirectory
from app import gc
from app.blobs import blob_path, put_bytes
from app.config import get_settings
from app.models import Game
def test_gallery_migration_has_one_head_and_postgres_default_for_existing_games():
output = io.StringIO()
context = MigrationContext.configure(dialect_name="postgresql", opts={"as_sql": True, "output_buffer": output})
operations = Operations(context)
migration = importlib.import_module("migrations.versions.0006_game_gallery")
with patch("alembic.op.add_column", operations.add_column):
migration.upgrade()
sql = output.getvalue()
assert "gallery_hashes VARCHAR(80)[] DEFAULT '{}' NOT NULL" in sql
assert ScriptDirectory.from_config(Config("alembic.ini")).get_heads() == ["0006"]
def test_gc_retains_gallery_images_without_cover_or_version_references(tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path))
get_settings.cache_clear()
live = "a" * 64 + ".webp"
orphan = "b" * 64 + ".webp"
for value in (live, orphan):
path = blob_path(value)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"image")
async def scalars(statement):
expr = statement.column_descriptions[0]["expr"]
return SimpleNamespace(all=lambda: [[live]] if expr is Game.gallery_hashes else [])
@asynccontextmanager
async def session():
yield SimpleNamespace(scalars=scalars)
@asynccontextmanager
async def access(**_):
yield
monkeypatch.setattr(gc, "SessionLocal", session)
monkeypatch.setattr(gc, "storage_access", access)
try:
report = asyncio.run(gc.collect())
assert report["retainedBlobs"] == 1
assert report["files"] == 1
assert blob_path(live).exists()
finally:
get_settings.cache_clear()
def test_identical_parallel_gallery_images_share_a_blob(tmp_path, monkeypatch):
monkeypatch.setenv("DATA_DIR", str(tmp_path))
get_settings.cache_clear()
try:
with ThreadPoolExecutor(max_workers=8) as executor:
results = list(executor.map(lambda _: put_bytes(b"same image", ".webp"), range(32)))
assert len({value[0] for value in results}) == 1
assert blob_path(results[0][0]).read_bytes() == b"same image"
assert not list((tmp_path / "blobs/tmp").iterdir())
finally:
get_settings.cache_clear()