113 lines
4.6 KiB
Python
113 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from app.fonts.store import FontCatalog, FontValidationError
|
|
from app.templates.store import TemplateStore, prepare_scene
|
|
from app.animations.store import AnimationStore
|
|
|
|
ID = "builtin:walnut-bus-8x16"
|
|
|
|
|
|
def scene(font=ID):
|
|
return prepare_scene(dict(version=1, width=64, height=64,
|
|
pixelRgb=base64.b64encode(bytes(64*64*3)).decode("ascii"),
|
|
elements=[dict(id="text",type="text",text="高铁路",font=font,size=16,x=0,y=8,align="left",color="#FFFFFF")]))
|
|
|
|
|
|
class ChangingFont:
|
|
def __init__(self, catalog):
|
|
self.catalog, self.version = catalog, 1
|
|
|
|
def render_version(self, identifier):
|
|
return str(self.version)*64 if identifier == ID else None
|
|
|
|
def resolve(self, identifier):
|
|
return self.catalog.resolve(ID if self.version == 1 else "builtin:walnut-bus-16x32")
|
|
|
|
|
|
def test_template_cache_rebuilt_without_revision_or_record_change(tmp_path):
|
|
owner=ChangingFont(FontCatalog(tmp_path))
|
|
store=TemplateStore(tmp_path, font_resolver=owner.resolve)
|
|
item=store.create("bus",scene())
|
|
path=store.thumbnail_path(item["id"])
|
|
record=store._record_path(item["id"])
|
|
before_bytes, before_record = path.read_bytes(), record.read_bytes()
|
|
assert "font_v=" in item["thumbnail_url"]
|
|
owner.version=2
|
|
restarted=TemplateStore(tmp_path,font_resolver=owner.resolve)
|
|
updated=restarted.list()["templates"][0]
|
|
assert updated["revision"] == item["revision"]
|
|
assert record.read_bytes() == before_record
|
|
assert path.read_bytes() != before_bytes
|
|
assert updated["thumbnail_url"] != item["thumbnail_url"]
|
|
stable=path.stat().st_mtime_ns
|
|
TemplateStore(tmp_path,font_resolver=owner.resolve)
|
|
assert path.stat().st_mtime_ns == stable
|
|
|
|
|
|
def test_animation_cache_rebuilt_without_scene_or_revision_change(tmp_path):
|
|
owner=ChangingFont(FontCatalog(tmp_path))
|
|
store=AnimationStore(tmp_path,font_resolver=owner.resolve)
|
|
item=store.create_from_frames("bus",[dict(scene=scene(),duration_ms=100)])
|
|
frame=store.get(item["id"])["frames"][0]
|
|
stored_frame=store._read(item["id"])["frames"][0]
|
|
path=store._thumb_dir(item["id"])/stored_frame["thumbnail"]
|
|
record=store._path(item["id"])
|
|
before_bytes, before_record = path.read_bytes(), record.read_bytes()
|
|
owner.version=2
|
|
restarted=AnimationStore(tmp_path,font_resolver=owner.resolve)
|
|
updated=restarted.get(item["id"])
|
|
assert updated["revision"] == item["revision"]
|
|
assert record.read_bytes() == before_record
|
|
assert path.read_bytes() != before_bytes
|
|
assert updated["frames"][0]["thumbnail_url"] != frame["thumbnail_url"]
|
|
stable=path.stat().st_mtime_ns
|
|
AnimationStore(tmp_path,font_resolver=owner.resolve)
|
|
assert path.stat().st_mtime_ns == stable
|
|
|
|
|
|
def test_unrelated_thumbnails_untouched(tmp_path):
|
|
owner=ChangingFont(FontCatalog(tmp_path))
|
|
store=TemplateStore(tmp_path,font_resolver=owner.resolve)
|
|
item=store.create("normal",scene("default"))
|
|
path=store.thumbnail_path(item["id"])
|
|
stable=path.read_bytes(),path.stat().st_mtime_ns
|
|
assert "font_v=" not in item["thumbnail_url"]
|
|
owner.version=2
|
|
TemplateStore(tmp_path,font_resolver=owner.resolve)
|
|
assert (path.read_bytes(),path.stat().st_mtime_ns) == stable
|
|
|
|
|
|
def test_exact_obsolete_retirement_and_corruption_preservation(tmp_path, monkeypatch):
|
|
import app.fonts.store as module
|
|
bundle=Path(module.__file__).parent/'bundled'
|
|
registration=json.loads((bundle/'manifest.json').read_text(encoding='utf-8'))
|
|
old_payload=b'old-font-fixture'
|
|
old_digest=hashlib.sha256(old_payload).hexdigest()
|
|
registration['obsolete']=[old_digest]
|
|
registration['aliases']={f'user:{old_digest}:0':ID}
|
|
# Substitute only the registration reader, retaining real bundled font
|
|
# files and checksum validation; fixture files never need old private state.
|
|
real_read=Path.read_text
|
|
def read(path,*args,**kwargs):
|
|
if path == bundle/'manifest.json':return json.dumps(registration)
|
|
return real_read(path,*args,**kwargs)
|
|
monkeypatch.setattr(Path,'read_text',read)
|
|
root=tmp_path/'fonts';root.mkdir()
|
|
old=root/f'{old_digest}.font';old.write_bytes(old_payload)
|
|
unrelated=root/'unregistered.font';unrelated.write_bytes(b'keep')
|
|
catalog=FontCatalog(tmp_path)
|
|
assert not old.exists()
|
|
assert unrelated.read_bytes() == b'keep'
|
|
assert catalog.resolve(f'user:{old_digest}:0') == catalog.resolve(ID)
|
|
old.write_bytes(b'corrupt')
|
|
with pytest.raises(FontValidationError,match='preserved'):
|
|
FontCatalog(tmp_path)
|
|
assert old.read_bytes() == b'corrupt'
|