Files
ECC/skills/taste-application/tests/test_schema.py
T
928c1dea72 feat(tasteforge): package reusable workflows and preserve native edits (#3033)
* feat: bundle standalone taste distillation and application workflows

* docs: fix imported taste skill markdown lint

* docs: align Turkish agent catalog with taste skills

* refactor: make ECC the canonical reusable video engine

* fix: preserve video duration when applying image overlays

* fix: preserve background colors in image compositing

* fix: report best-effort duration targets and shortfalls

* feat: ship verified Fusion presets with compatibility provenance

* feat(tasteforge): preserve native edits in application bundles

* feat(tasteforge): compile local preservation without hosted input

* fix: update js-yaml to patched 4.3.2

* test: report bounded Stop wrapper failure diagnostics

* fix(tasteforge): fail closed on unsafe output names, missing overlays and cadence

- cli: default report and spec paths are derived from pack name and profile
  genre; require the manifest's name pattern before using either as a
  filename part so a traversal string cannot write outside cwd/out.
- apply_local: a pack without cadence.json, or with no measured shots and
  no explicit mean_shot, raises instead of silently planning 1.0s shots and
  reporting a measured cadence.
- legacy apply: a missing overlay aborts before any paid upload; forge()
  would have rejected it after every take was generated.
- requirements-live: pin fal-client>=0.13.0, the first release whose
  subscribe() accepts client_timeout.

Addresses the five P1 findings from the independent review of #3033.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015fxHRsydPqEcYngGbqkgt1

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 15:31:36 +01:00

106 lines
3.9 KiB
Python

"""Failing-first tests for the tasteforge schema subset validator.
Contract (from the recovered TasteForge gen4 source, canonicalized):
- hand-rolled JSON-Schema subset: type, required, properties, items, enum,
minimum/minimum, minItems, pattern; no third-party dependency.
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1] / "scripts"
sys.path.insert(0, str(REPO_ROOT))
from tasteforge import schema # noqa: E402
class ValidatorTests(unittest.TestCase):
def setUp(self):
self.simple = {
"type": "object",
"required": ["name", "count"],
"properties": {
"name": {"type": "string", "pattern": "^[a-z][a-z0-9_-]*$"},
"count": {"type": "integer", "minimum": 0},
"tags": {"type": "array", "items": {"type": "string"}, "minItems": 1},
"mode": {"type": "string", "enum": ["local", "dry-run"]},
},
}
def test_accepts_valid_instance(self):
problems = schema.validate(
{"name": "flashethereal", "count": 3, "tags": ["a"], "mode": "local"},
self.simple,
)
self.assertEqual(problems, [])
def test_rejects_missing_required(self):
problems = schema.validate({"count": 3}, self.simple)
self.assertTrue(any("required" in p and "name" in p for p in problems))
def test_rejects_wrong_type(self):
problems = schema.validate({"name": "x", "count": "three"}, self.simple)
self.assertTrue(any("count" in p and "type" in p for p in problems))
def test_rejects_bad_pattern(self):
problems = schema.validate({"name": "Bad Name!", "count": 0}, self.simple)
self.assertTrue(any("name" in p and "pattern" in p for p in problems))
def test_rejects_bad_enum(self):
problems = schema.validate({"name": "x", "count": 0, "mode": "live"}, self.simple)
self.assertTrue(any("mode" in p and "enum" in p for p in problems))
def test_rejects_below_minimum(self):
problems = schema.validate({"name": "x", "count": -1}, self.simple)
self.assertTrue(any("count" in p and "minimum" in p for p in problems))
def test_rejects_bad_items_and_min_items(self):
problems = schema.validate({"name": "x", "count": 0, "tags": [1, 2]}, self.simple)
self.assertTrue(any("tags[0]" in p for p in problems))
problems = schema.validate({"name": "x", "count": 0, "tags": []}, self.simple)
self.assertTrue(any("tags" in p and "minItems" in p for p in problems))
def test_non_object_root_rejected(self):
problems = schema.validate(["not", "an", "object"], self.simple)
self.assertTrue(problems)
class ExportedSchemasTests(unittest.TestCase):
EXPORTED = [
"TASTE_PROFILE_SCHEMA",
"PACK_MANIFEST_SCHEMA",
"GRADE_SCHEMA",
"CADENCE_SCHEMA",
"SPEC_SCHEMA",
"TIMELINE_EVENT_SCHEMA",
"APPLICATION_REPORT_SCHEMA",
"PROVENANCE_SCHEMA",
]
def test_all_exported_schemas_exist_and_are_objects(self):
for name in self.EXPORTED:
with self.subTest(schema=name):
s = getattr(schema, name)
self.assertIsInstance(s, dict)
self.assertEqual(s.get("type"), "object")
self.assertIn("required", s)
self.assertIn("properties", s)
def test_application_report_forbids_provider_generation(self):
s = schema.APPLICATION_REPORT_SCHEMA
self.assertEqual(
s["properties"]["provider"].get("enum"), ["none"],
"application reports must only ever claim provider=none in this lane",
)
self.assertEqual(
s["properties"]["dry_run"].get("enum"), [True],
"application reports must never claim a live provider run",
)
if __name__ == "__main__":
unittest.main()