feat(processing): built-in 'look' presets (Cinematic/Vivid/Cozy/Soft/Cool)
Seed five curated, read-only post-processing templates so a non-expert gets instant good-looking output before discovering the filter pipeline. Each is an opinionated chain of existing filters (auto-crop/saturation/contrast/colour- temperature/temporal-blur) tuned for a use case (films, games, evening ambience, low-flicker, crisp cool-white). Mirrors the built-in-gradient pattern: adds is_builtin to PostprocessingTemplate, seeds missing looks on store init (idempotent, additive — no migration), and makes built-ins read-only (update/delete raise -> 400; clone to customise). Surfaced via the existing template picker + is_builtin in the response/type. 7 unit tests (seeding, idempotency, read-only protection, round-trip); full suite green (1926 passed). (A runtime intensity slider is a follow-up — it needs a filter-chain parameterisation layer.)
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""Tests for built-in curated 'look' postprocessing templates."""
|
||||
|
||||
import pytest
|
||||
|
||||
from ledgrab.core.filters.registry import FilterRegistry
|
||||
from ledgrab.storage.postprocessing_template import PostprocessingTemplate
|
||||
from ledgrab.storage.postprocessing_template_store import (
|
||||
_BUILTIN_LOOKS,
|
||||
PostprocessingTemplateStore,
|
||||
)
|
||||
|
||||
|
||||
def test_builtins_are_seeded(tmp_db):
|
||||
store = PostprocessingTemplateStore(tmp_db)
|
||||
for key in _BUILTIN_LOOKS:
|
||||
tpl = store.get_template(f"pp_builtin_{key}")
|
||||
assert tpl.is_builtin is True
|
||||
assert tpl.filters # non-empty chain
|
||||
|
||||
|
||||
def test_builtin_filters_use_registered_ids(tmp_db):
|
||||
store = PostprocessingTemplateStore(tmp_db)
|
||||
for key in _BUILTIN_LOOKS:
|
||||
tpl = store.get_template(f"pp_builtin_{key}")
|
||||
for fi in tpl.filters:
|
||||
assert FilterRegistry.is_registered(fi.filter_id), fi.filter_id
|
||||
|
||||
|
||||
def test_seeding_is_idempotent(tmp_db):
|
||||
PostprocessingTemplateStore(tmp_db)
|
||||
store2 = PostprocessingTemplateStore(tmp_db)
|
||||
ids = [t.id for t in store2.get_all_templates() if t.id.startswith("pp_builtin_")]
|
||||
assert sorted(ids) == sorted(f"pp_builtin_{k}" for k in _BUILTIN_LOOKS)
|
||||
|
||||
|
||||
def test_builtin_update_is_blocked(tmp_db):
|
||||
store = PostprocessingTemplateStore(tmp_db)
|
||||
with pytest.raises(ValueError, match="read-only"):
|
||||
store.update_template("pp_builtin_vivid", name="Hacked")
|
||||
|
||||
|
||||
def test_builtin_delete_is_blocked(tmp_db):
|
||||
store = PostprocessingTemplateStore(tmp_db)
|
||||
with pytest.raises(ValueError, match="cannot be deleted"):
|
||||
store.delete_template("pp_builtin_vivid")
|
||||
|
||||
|
||||
def test_user_template_still_editable_and_deletable(tmp_db):
|
||||
store = PostprocessingTemplateStore(tmp_db)
|
||||
tpl = store.create_template("My Look", filters=[])
|
||||
assert tpl.is_builtin is False
|
||||
store.update_template(tpl.id, description="changed")
|
||||
store.delete_template(tpl.id)
|
||||
with pytest.raises(ValueError):
|
||||
store.get_template(tpl.id)
|
||||
|
||||
|
||||
def test_is_builtin_round_trips_through_dict():
|
||||
tpl = PostprocessingTemplate.from_dict(
|
||||
{
|
||||
"id": "pp_x",
|
||||
"name": "x",
|
||||
"filters": [],
|
||||
"created_at": "2026-01-01T00:00:00+00:00",
|
||||
"updated_at": "2026-01-01T00:00:00+00:00",
|
||||
"is_builtin": True,
|
||||
}
|
||||
)
|
||||
assert tpl.is_builtin is True
|
||||
assert tpl.to_dict()["is_builtin"] is True
|
||||
# legacy dict without the field defaults to False
|
||||
legacy = PostprocessingTemplate.from_dict(
|
||||
{
|
||||
"id": "pp_y",
|
||||
"name": "y",
|
||||
"filters": [],
|
||||
"created_at": "2026-01-01T00:00:00+00:00",
|
||||
"updated_at": "2026-01-01T00:00:00+00:00",
|
||||
}
|
||||
)
|
||||
assert legacy.is_builtin is False
|
||||
Reference in New Issue
Block a user