82 lines
4.1 KiB
Python
82 lines
4.1 KiB
Python
from concurrent.futures import ThreadPoolExecutor
|
|
import json
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.fonts.store import FontCatalog, FontValidationError
|
|
from app.main import create_app
|
|
|
|
|
|
def test_api_validation_and_persistence(tmp_path):
|
|
app = create_app(project_root=tmp_path, driver_kind="mock")
|
|
client = TestClient(app)
|
|
items = client.get("/api/fonts").json()["items"]
|
|
assert client.get("/api/fonts").json()["favorite_ids"] == []
|
|
ids = [item["id"] for item in items[:3]]
|
|
for identifier in ids:
|
|
assert client.put("/api/fonts/favorite", json={"id": identifier, "favorite": True}).status_code == 200
|
|
path = app.state.font_catalog.root / "favorites.json"
|
|
before = path.read_bytes()
|
|
client.put("/api/fonts/favorite", json={"id": ids[0], "favorite": True})
|
|
assert path.read_bytes() == before
|
|
assert FontCatalog(path.parent.parent).document()["favorite_ids"] == ids
|
|
for payload in ({"id": "default", "favorite": "true"}, {"id": "default", "favorite": 1}, {"id": "", "favorite": True}, {"id": "default", "favorite": True, "extra": 1}):
|
|
assert client.put("/api/fonts/favorite", json=payload).status_code == 422
|
|
assert client.put("/api/fonts/favorite", json={"id": "missing", "favorite": True}).status_code == 404
|
|
assert client.put("/api/fonts/favorite", json={"id": "missing", "favorite": False}).status_code == 200
|
|
client.put("/api/fonts/favorite", json={"id": ids[0], "favorite": False})
|
|
assert client.get("/api/fonts").json()["favorite_ids"] == ids[1:]
|
|
|
|
|
|
def test_concurrent_merge_and_write_failure(tmp_path, monkeypatch):
|
|
catalog = FontCatalog(tmp_path)
|
|
ids = [item["id"] for item in catalog.document()["items"][:3]]
|
|
with ThreadPoolExecutor(max_workers=3) as pool:
|
|
list(pool.map(lambda identifier: catalog.set_favorite(identifier, True), ids))
|
|
assert set(catalog.document()["favorite_ids"]) == set(ids)
|
|
before = (catalog.root / "favorites.json").read_bytes()
|
|
def fail(*args):
|
|
raise OSError("injected write failure")
|
|
monkeypatch.setattr("app.fonts.store.atomic_write_bytes", fail)
|
|
with pytest.raises(OSError):
|
|
catalog.set_favorite(ids[0], False)
|
|
assert set(catalog.document()["favorite_ids"]) == set(ids)
|
|
assert (catalog.root / "favorites.json").read_bytes() == before
|
|
|
|
|
|
@pytest.mark.parametrize("content", [b"broken", b'{"schema_version":2,"favorite_ids":[]}', b'{"schema_version":true,"favorite_ids":[]}', b'{"schema_version":1,"favorite_ids":["x","x"]}', b'{"schema_version":1,"favorite_ids":[],"extra":1}'])
|
|
def test_invalid_file_preserved(tmp_path, content):
|
|
(tmp_path / "fonts").mkdir()
|
|
path = tmp_path / "fonts/favorites.json"
|
|
path.write_bytes(content)
|
|
with pytest.raises(FontValidationError):
|
|
FontCatalog(tmp_path)
|
|
assert path.read_bytes() == content
|
|
|
|
|
|
def test_stale_ids_preserved_and_removable(tmp_path):
|
|
catalog = FontCatalog(tmp_path)
|
|
path = catalog.root / "favorites.json"
|
|
path.write_text(json.dumps({"schema_version": 1, "favorite_ids": ["stale"]}), encoding="utf-8")
|
|
catalog = FontCatalog(tmp_path)
|
|
assert catalog.document()["favorite_ids"] == ["stale"]
|
|
assert not any(item["id"] == "stale" for item in catalog.document()["items"])
|
|
assert catalog.set_favorite("stale", False) == []
|
|
|
|
|
|
def test_same_family_styles_are_independent(tmp_path):
|
|
from test_fonts import build_test_font
|
|
app = create_app(project_root=tmp_path, driver_kind="mock")
|
|
client = TestClient(app)
|
|
ids = []
|
|
for style in ["Bold", "Italic"]:
|
|
response = client.post("/api/fonts/import?filename=style.ttf", content=build_test_font("Same Family", style), headers={"Content-Type": "application/octet-stream"})
|
|
assert response.status_code == 201
|
|
ids.append(response.json()["fonts"][0]["id"])
|
|
client.put("/api/fonts/favorite", json={"id": ids[0], "favorite": True})
|
|
assert client.get("/api/fonts").json()["favorite_ids"] == [ids[0]]
|
|
client.put("/api/fonts/favorite", json={"id": ids[1], "favorite": True})
|
|
client.put("/api/fonts/favorite", json={"id": ids[0], "favorite": False})
|
|
assert client.get("/api/fonts").json()["favorite_ids"] == [ids[1]]
|