tts-voice-creator-clone-and.../tests/test_voice_provenance.py
mARTin-B78 781bf6e7f2 feat(voices): consent and provenance field per voice
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-04 16:39:32 +02:00

100 lines
4.4 KiB
Python

"""Voice consent/provenance: validation, defaults, persistence, upload route (temp dirs only)."""
import asyncio
import io
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
os.environ.setdefault("CONFIG_DIR", tempfile.mkdtemp(prefix="ttvc-prov-test-config-"))
from fastapi import HTTPException, UploadFile
from starlette.datastructures import Headers
from core.provenance import import_provenance, validate_provenance
from core.voice import _load_meta, _save_meta, _voice_package_paths
import routes.library as lib
class ValidationTests(unittest.TestCase):
def test_defaults_and_cleaning(self):
p = validate_provenance({})
self.assertEqual(p["basis"], "unknown")
p = validate_provenance({"basis": "licensed", "licence": " CC-BY\x00 4.0 ", "source": "https://x.org/a"})
self.assertEqual(p["licence"], "CC-BY 4.0")
self.assertTrue(p["set_at"])
def test_rejects(self):
for bad in ({"basis": "stolen"}, {"note": "x" * 1001}, {"source": "javascript:alert(1)"},
{"source": "ftp://x.org/a"}, {"source": "https://u:pw@x.org/"}, {"licence": 5}):
with self.assertRaises(ValueError, msg=str(bad)):
validate_provenance(bad)
def test_recording_not_settable_by_client(self):
self.assertNotIn("recorded_consent", validate_provenance({"recorded_consent": "../../etc/passwd"}))
self.assertEqual(validate_provenance({}, {"recorded_consent": "a.webm"})["recorded_consent"], "a.webm")
def test_import_prefill_stays_unknown(self):
p = import_provenance("https://voices.example/v/1")
self.assertEqual((p["basis"], p["source"]), ("unknown", "https://voices.example/v/1"))
self.assertEqual(import_provenance("ftp://bad")["source"], "")
class RouteTests(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.dir = Path(self.tmp.name)
self.wav = self.dir / "EN_F_Test.wav"
self.wav.write_bytes(b"RIFF")
patch = mock.patch.object(lib, "_load_settings", return_value={"voices_scan_dir": str(self.dir)})
patch.start()
self.addCleanup(patch.stop)
self.addCleanup(self.tmp.cleanup)
def _req(self, body):
class R:
async def json(_):
return body
return R()
def test_put_persists_and_validates(self):
out = asyncio.run(lib.put_voice_provenance("EN_F_Test", self._req({"basis": "own_voice", "note": "me"})))
self.assertEqual(_load_meta(self.wav)["provenance"]["basis"], "own_voice")
self.assertEqual(out["provenance"]["note"], "me")
with self.assertRaises(HTTPException) as cm:
asyncio.run(lib.put_voice_provenance("EN_F_Test", self._req({"basis": "nope"})))
self.assertEqual(cm.exception.status_code, 400)
with self.assertRaises(HTTPException) as cm:
asyncio.run(lib.put_voice_provenance("missing", self._req({})))
self.assertEqual(cm.exception.status_code, 404)
def test_legacy_voice_has_no_provenance(self):
self.assertNotIn("provenance", _load_meta(self.wav)) # UI treats absent as unknown
def _upload(self, name, ctype, data):
f = UploadFile(io.BytesIO(data), filename=name, headers=Headers({"content-type": ctype}))
return asyncio.run(lib.upload_consent_recording("EN_F_Test", f))
def test_consent_upload(self):
out = self._upload("c.webm", "audio/webm;codecs=opus", b"abc")
self.assertEqual(out["provenance"]["recorded_consent"], "EN_F_Test.webm")
self.assertTrue(self.wav.with_suffix(".consent").exists())
self.assertIn(self.wav.with_suffix(".consent"), _voice_package_paths(self.wav))
self.assertEqual(asyncio.run(lib.serve_consent_recording("EN_F_Test")).media_type, "audio/webm")
for name, ctype in (("c.exe", "audio/webm"), ("c.webm", "text/html"), ("../c.webm", "audio/webm")):
with self.assertRaises(HTTPException, msg=name):
self._upload(name, ctype, b"abc")
def test_consent_too_large(self):
with mock.patch("core.provenance.MAX_CONSENT_BYTES", 2), self.assertRaises(HTTPException) as cm:
self._upload("c.webm", "audio/webm", b"abcdef")
self.assertEqual(cm.exception.status_code, 413)
self.assertFalse(self.wav.with_suffix(".consent").exists())
if __name__ == "__main__":
unittest.main()