#!/usr/bin/env python3 """Focused integrity contract tests.""" import sys import tempfile import json from pathlib import Path import numpy as np import soundfile as sf sys.path.insert(0, str(Path(__file__).parent.parent)) from data_integrity import (initialize_transcript_provenance, sha256_file, validate_export_integrity, validate_text_integrity) def _row(wav_path: Path, text: str = "hello"): row = {"chunk_id": "clip_1", "text": text, "audio_path": str(wav_path)} initialize_transcript_provenance(row, transcript_verbatim="raw hello") row["audio_sha256"] = sha256_file(wav_path) row["audio_hash_scheme"] = "file_sha256_v1" row["promotion_status"] = "training_ready" return row def _split_rows(root: Path): rows = [] for index, split in enumerate(("train", "val", "test")): wav = root / f"clip_{split}.wav" sf.write(wav, np.full(240, index * 0.1, dtype=np.float32), 24000) row = _row(wav, text=f"hello {split}") row["chunk_id"] = f"clip_{split}" row["video_id"] = f"video_{split}" row["split"] = split row["split_policy"] = "identity_aware_component_hash_v4" row["split_component_id"] = f"component_{split}" row["split_near_text_audit_status"] = "complete" row["split_coverage_status"] = "complete" rows.append(row) return rows def test_stale_text_or_audio_is_rejected_before_export(): with tempfile.TemporaryDirectory() as tmp: wav = Path(tmp) / "clip.wav" sf.write(wav, np.zeros(240, dtype=np.float32), 24000) row = _row(wav) row["text"] = "changed" ok, reason = validate_text_integrity(row) assert not ok and reason == "training_text_alias_mismatch" try: validate_export_integrity([row]) except ValueError as exc: assert "training_text_alias_mismatch" in str(exc) else: raise AssertionError("stale text must block export") def test_export_requires_explicit_promotion(): with tempfile.TemporaryDirectory() as tmp: wav = Path(tmp) / "clip.wav" sf.write(wav, np.zeros(240, dtype=np.float32), 24000) row = _row(wav) row.pop("promotion_status") try: validate_export_integrity([row]) except ValueError as exc: assert "not_training_ready" in str(exc) else: raise AssertionError("unpromoted row must block export") def test_f5_root_arrow_is_built_from_train_rows_only(): import dataset_exporter with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) rows = _split_rows(root) # A stale same-name destination must be replaced by the validated source. stale_dst = root / "integrity_custom" / "wavs" / "clip_train.wav" stale_dst.parent.mkdir(parents=True) sf.write(stale_dst, np.ones(240, dtype=np.float32), 24000) calls = [] original = dataset_exporter.build_arrow_dataset dataset_exporter.build_arrow_dataset = lambda records, directory, name: calls.append( (directory.name, [record["chunk_id"] for record in records]) ) try: dataset_exporter.export_f5_tts_dataset(rows, "integrity", root, {}) finally: dataset_exporter.build_arrow_dataset = original assert calls[0] == ("integrity_custom", ["clip_train"]) assert ("val", ["clip_val"]) in calls assert ("test", ["clip_test"]) in calls assert sha256_file(stale_dst) == sha256_file(root / "clip_train.wav") duration_map = json.loads((root / "integrity_custom" / "duration.json").read_text(encoding="utf-8")) assert set(duration_map) == {"clip_train.wav"} root_manifest = (root / "integrity_custom" / "manifest.jsonl").read_text(encoding="utf-8").splitlines() all_manifest = ( root / "integrity_custom" / "manifest_all_splits.jsonl" ).read_text(encoding="utf-8").splitlines() assert len(root_manifest) == 1 assert len(all_manifest) == 3 def test_unverified_export_requires_a_reason(): import dataset_exporter with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) wav = root / "clip.wav" sf.write(wav, np.zeros(240, dtype=np.float32), 24000) row = _row(wav) row["split"] = "train" try: dataset_exporter.export_f5_tts_dataset( [row], "unverified", root, {"allow_unverified_export": True}, ) except ValueError as exc: assert "export_override_reason" in str(exc) else: raise AssertionError("an unverified export must record its reason") def test_cross_split_identity_leak_is_rejected(): with tempfile.TemporaryDirectory() as tmp: wav = Path(tmp) / "clip.wav" sf.write(wav, np.zeros(240, dtype=np.float32), 24000) train_row = _row(wav) train_row["split"] = "train" val_row = dict(train_row) val_row["chunk_id"] = "clip_2" val_row["split"] = "val" try: validate_export_integrity([train_row, val_row]) except ValueError as exc: assert "cross_split_audio_sha256_leak" in str(exc) assert "cross_split_text_sha256_leak" in str(exc) else: raise AssertionError("cross-split payload identities must block export") def test_verified_export_rejects_missing_validation_and_test_splits(): with tempfile.TemporaryDirectory() as tmp: rows = _split_rows(Path(tmp))[:1] try: validate_export_integrity(rows) except ValueError as exc: assert "missing_required_splits:val,test" in str(exc) else: raise AssertionError("verified export must contain every required split") def test_verified_export_accepts_complete_split_coverage(): with tempfile.TemporaryDirectory() as tmp: validate_export_integrity(_split_rows(Path(tmp))) def test_insufficient_component_coverage_metadata_is_rejected(): with tempfile.TemporaryDirectory() as tmp: rows = _split_rows(Path(tmp)) rows[0]["split_component_id"] = "component_train" rows[0]["split_coverage_status"] = "insufficient_components" try: validate_export_integrity(rows) except ValueError as exc: assert "clip_train:insufficient_split_coverage" in str(exc) else: raise AssertionError("insufficient component coverage must block verified export") def test_unverified_export_bypasses_split_coverage_checks(): with tempfile.TemporaryDirectory() as tmp: rows = _split_rows(Path(tmp))[:1] rows[0]["split_component_id"] = "component_train" rows[0]["split_coverage_status"] = "insufficient_components" validate_export_integrity(rows, allow_unverified=True) def test_verified_export_requires_complete_identity_aware_near_text_audit(): with tempfile.TemporaryDirectory() as tmp: rows = _split_rows(Path(tmp)) rows[0]["split_near_text_audit_status"] = "candidate_limit_truncated" try: validate_export_integrity(rows) except ValueError as exc: assert "near_text_split_audit_incomplete" in str(exc) else: raise AssertionError("verified export must require a complete near-text split audit") if __name__ == "__main__": test_stale_text_or_audio_is_rejected_before_export() test_export_requires_explicit_promotion() test_f5_root_arrow_is_built_from_train_rows_only() test_unverified_export_requires_a_reason() test_cross_split_identity_leak_is_rejected() test_verified_export_rejects_missing_validation_and_test_splits() test_verified_export_accepts_complete_split_coverage() test_insufficient_component_coverage_metadata_is_rejected() test_unverified_export_bypasses_split_coverage_checks() print("All data_integrity tests passed!")