Add bundled bus fonts and shared font favorites
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
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'
|
||||
Reference in New Issue
Block a user