Files
wordcloud/backend/tests/test_wcd_import.py
broccoli c26de1f498
Build, Push and Deploy / build (push) Successful in 11s
Build, Push and Deploy / deploy (push) Successful in 9s
fix(wcd): create sticker assets when reused content is a reference
2026-09-11 19:50:00 +08:00

195 lines
6.4 KiB
Python

import asyncio
import json
import hashlib
import sys
import zipfile
from io import BytesIO
from pathlib import Path
from types import SimpleNamespace
from fastapi import UploadFile
from PIL import Image
import pytest
BACKEND_DIR = Path(__file__).resolve().parents[1]
if str(BACKEND_DIR) not in sys.path:
sys.path.insert(0, str(BACKEND_DIR))
from service import app # noqa: E402
SVG = (
'<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">'
'<rect width="20" height="10" fill="#3366ff"/></svg>'
)
def png_bytes(color=(51, 102, 255)):
buffer = BytesIO()
Image.new("RGB", (1, 1), color).save(buffer, format="PNG")
return buffer.getvalue()
@pytest.fixture
def isolated_asset_store(tmp_path, monkeypatch):
assets_dir = tmp_path / "service_assets"
assets_dir.mkdir()
monkeypatch.setattr(app, "ASSETS_DIR", assets_dir)
design_templates_dir = tmp_path / "service_design_templates"
design_templates_dir.mkdir()
monkeypatch.setattr(app, "DESIGN_TEMPLATES_DIR", design_templates_dir)
return assets_dir
def asset_dirs(assets_dir):
return sorted(path for path in assets_dir.glob("*/*") if path.is_dir())
def test_duplicate_content_is_reused(isolated_asset_store):
first = app._register_import_asset("first", SVG.encode(), "image/svg+xml")
second = app._register_import_asset("second", SVG.encode(), "image/svg+xml")
assert first["asset_id"] == second["asset_id"]
assert second["name"] == "first"
assert len(asset_dirs(isolated_asset_store)) == 1
def test_same_name_different_content_gets_unique_assets(isolated_asset_store):
first = app._register_import_asset("same-name", SVG.encode(), "image/svg+xml")
second = app._register_import_asset(
"same-name", SVG.replace("#3366ff", "#ff3366").encode(), "image/svg+xml"
)
assert first["asset_id"] != second["asset_id"]
assert len(asset_dirs(isolated_asset_store)) == 2
assert first["sha256"] != second["sha256"]
def test_stale_sha256_meta_does_not_reuse_mismatched_file(isolated_asset_store):
content = SVG.encode()
existing_id = "asset_stale00000000000000000000000000"
existing_dir = isolated_asset_store / existing_id[:2] / existing_id
existing_dir.mkdir(parents=True)
(existing_dir / "asset.svg").write_text('<svg xmlns="x" width="1" height="1"/>')
(existing_dir / "meta.json").write_text(
json.dumps(
{
"asset_id": existing_id,
"name": "old",
"type": "sticker",
"mime_type": "image/svg+xml",
"sha256": hashlib.sha256(content).hexdigest(),
}
)
)
imported = app._register_import_asset("new", content, "image/svg+xml")
assert imported["asset_id"] != existing_id
assert len(asset_dirs(isolated_asset_store)) == 2
def test_reference_with_same_content_creates_sticker_asset(isolated_asset_store):
content = SVG.encode()
existing_id = "asset_reference00000000000000000000000"
existing_dir = isolated_asset_store / existing_id[:2] / existing_id
existing_dir.mkdir(parents=True)
(existing_dir / "asset.svg").write_bytes(content)
(existing_dir / "meta.json").write_text(
json.dumps(
{
"asset_id": existing_id,
"name": "old",
"type": "reference",
"mime_type": "image/svg+xml",
"sha256": hashlib.sha256(content).hexdigest(),
}
)
)
imported = app._register_import_asset("new", content, "image/svg+xml")
assert imported["asset_id"] != existing_id
assert imported["type"] == "sticker"
assert len(asset_dirs(isolated_asset_store)) == 2
def test_wcd_production_uses_real_asset_ids(tmp_path, isolated_asset_store, monkeypatch):
manifest = {
"format": "wordcloud-canvas",
"version": 1,
"name": "test-design",
"assets": [{"id": "pkg-1", "name": "sticker", "type": "svg", "mimeType": "image/svg+xml"}],
}
document_json = {
"width": 40,
"height": 20,
"background": "#ffffff",
"layers": [],
"layerFolders": [],
"elements": [
{
"id": "element-1",
"type": "sticker",
"name": "sticker",
"assetId": "pkg-1",
"x": 5,
"y": 5,
"width": 10,
"height": 10,
"rotation": 0,
"opacity": 1,
}
],
}
package = BytesIO()
with zipfile.ZipFile(package, "w") as archive:
archive.writestr("manifest.json", json.dumps(manifest))
archive.writestr("document.json", json.dumps(document_json))
archive.writestr("assets/pkg-1.png", png_bytes())
class Paths:
input_dir = tmp_path / "input"
output_dir = tmp_path / "output"
def prepare_job_dirs(job_id):
Paths.input_dir.mkdir(parents=True)
Paths.output_dir.mkdir(parents=True)
return Paths
manager = SimpleNamespace(
create_job=lambda: "job-test",
set_artifacts=lambda *args, **kwargs: None,
set_status=lambda *args, **kwargs: None,
)
original_compose = app._compose_design_png
captured = {}
def compose(document, file_map, output_path):
captured["document"] = document
captured["file_map"] = dict(file_map)
original_compose(document, file_map, output_path)
monkeypatch.setattr(app, "manager", manager)
monkeypatch.setattr(app, "storage", SimpleNamespace(prepare_job_dirs=prepare_job_dirs))
monkeypatch.setattr(app, "_write_order", lambda *args, **kwargs: None)
monkeypatch.setattr(app, "_compose_design_png", compose)
monkeypatch.setattr(app.threading, "Thread", lambda target, **kwargs: SimpleNamespace(start=target))
upload = UploadFile(filename="test.wcd", file=BytesIO(package.getvalue()))
asyncio.run(app._create_wcd_job(upload, {}))
document = captured["document"]
file_map = captured["file_map"]
real_ids = [element["assetId"] for element in document["elements"]]
assert real_ids
assert all(asset_id in file_map for asset_id in real_ids)
assert all(Path(asset_id).is_absolute() is False for asset_id in real_ids)
png_path = isolated_asset_store.parent / "composed.png"
original_compose(document, file_map, png_path)
with Image.open(png_path) as image:
assert image.getpixel((10, 10)) == (51, 102, 255)