Files
JMR-devandClaude Sonnet 5 b23995f218 refactor: rename the package from ccn-transcribe to audio-scribe
The distribution, console script and import package are now audio-scribe /
audio_scribe (src/audio_scribe). Everything named for the old project follows:

- CcnError -> AudioScribeError, and its code "ccn_error" -> "audio_scribe_error"
  (the code is never persisted, so existing job state still loads)
- CCN_LIVE -> AUDIO_SCRIBE_LIVE for the live-GPU tests
- OpenVINO kernel cache moves to <cache>/audio-scribe/ov_cache; the first run
  after upgrading recompiles kernels, and the old directory is left in place
- README, build-binary.sh, hatch/coverage config and uv.lock updated to match

Breaking: the command is now `audio-scribe`; reinstall any tool install of the
old name with `uv tool uninstall ccn-transcribe && uv tool install .`.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 15:52:42 -05:00

168 lines
6.2 KiB
Python

from __future__ import annotations
import pytest
from audio_scribe import errors
from audio_scribe.jobs import state as job_state
def make() -> job_state.JobState:
return job_state.JobState.new(job_id="youtube-abc", source_url="https://y.test/watch?v=abc")
class TestNew:
def test_starts_with_everything_missing(self) -> None:
job = make()
assert job.video.status is job_state.ArtifactStatus.MISSING
assert job.audio.status is job_state.ArtifactStatus.MISSING
assert job.outputs.status is job_state.ArtifactStatus.MISSING
def test_records_identity_and_timestamps(self) -> None:
job = make()
assert job.job_id == "youtube-abc"
assert job.source_url == "https://y.test/watch?v=abc"
assert job.created_at and job.updated_at
def test_carries_the_current_schema_version(self) -> None:
assert make().schema_version == job_state.SCHEMA_VERSION
class TestRoundTrip:
def test_survives_serialization(self) -> None:
job = make()
job.video = job_state.Artifact(
job_state.ArtifactStatus.PRESENT, path="media/v.mkv", duration_s=12.5
)
job.title = "A title"
assert job_state.JobState.from_dict(job.to_dict()) == job
def test_enums_serialize_as_plain_strings(self) -> None:
doc = make().to_dict()
assert doc["artifacts"]["video"]["status"] == "missing"
def test_outputs_paths_round_trip(self) -> None:
job = make()
job.outputs = job_state.Outputs(
job_state.ArtifactStatus.PRESENT, paths={"srt": "out/transcript.srt"}
)
assert job_state.JobState.from_dict(job.to_dict()).outputs.paths == {
"srt": "out/transcript.srt"
}
def test_transcription_record_round_trips(self) -> None:
job = make()
job.transcription = job_state.TranscriptionRecord(
model_id="m",
model_revision="r",
backend="openvino",
device="GPU",
language="en",
task="transcribe",
num_beams=1,
)
restored = job_state.JobState.from_dict(job.to_dict())
assert restored.transcription is not None
assert restored.transcription.device == "GPU"
def test_unknown_keys_are_ignored_not_fatal(self) -> None:
doc = make().to_dict()
doc["something_from_the_future"] = 1
assert job_state.JobState.from_dict(doc).job_id == "youtube-abc"
class TestSchemaVersioning:
def test_a_newer_schema_is_refused(self) -> None:
doc = make().to_dict()
doc["schema_version"] = job_state.SCHEMA_VERSION + 1
# Guessing at a future layout risks destroying the user's state dir.
with pytest.raises(errors.SchemaTooNewError):
job_state.JobState.from_dict(doc)
def test_the_current_schema_is_accepted(self) -> None:
assert job_state.JobState.from_dict(make().to_dict()) is not None
class TestParamsChanged:
def _record(self, **kw: object) -> job_state.TranscriptionRecord:
base: dict[str, object] = {
"model_id": "m",
"model_revision": "r",
"backend": "openvino",
"device": "GPU",
"language": "en",
"task": "transcribe",
"num_beams": 1,
}
base.update(kw)
return job_state.TranscriptionRecord(**base) # type: ignore[arg-type]
def test_no_prior_run_counts_as_changed(self) -> None:
assert make().params_changed(self._record()) is True
def test_identical_params_are_unchanged(self) -> None:
job = make()
job.transcription = self._record()
assert job.params_changed(self._record()) is False
@pytest.mark.parametrize(
("field", "value"),
[("model_id", "other"), ("language", "de"), ("task", "translate"), ("num_beams", 5)],
)
def test_a_different_request_forces_a_rerun(self, field: str, value: object) -> None:
job = make()
job.transcription = self._record()
assert job.params_changed(self._record(**{field: value})) is True
def test_device_and_backend_do_not_force_a_rerun(self) -> None:
# Falling back from GPU to CPU must not invalidate a good transcript.
job = make()
job.transcription = self._record()
assert job.params_changed(self._record(device="CPU", backend="x")) is False
class TestDefaultsFromSparseDocuments:
def test_a_document_with_no_artifacts_key_gets_defaults(self) -> None:
doc = make().to_dict()
del doc["artifacts"]
restored = job_state.JobState.from_dict(doc)
assert restored.video.status is job_state.ArtifactStatus.MISSING
assert not restored.outputs.paths
def test_an_empty_artifact_entry_gets_defaults(self) -> None:
doc = make().to_dict()
doc["artifacts"] = {"video": None, "audio": {}, "outputs": None}
restored = job_state.JobState.from_dict(doc)
assert restored.video.status is job_state.ArtifactStatus.MISSING
assert restored.audio.status is job_state.ArtifactStatus.MISSING
assert restored.outputs.status is job_state.ArtifactStatus.MISSING
class TestModelComparison:
def _record(self, model_id: str | None) -> job_state.TranscriptionRecord:
return job_state.TranscriptionRecord(
model_id=model_id,
model_revision=None,
backend="openvino",
device="GPU",
language="en",
task="transcribe",
num_beams=1,
)
def test_an_unpinned_model_matches_whatever_ran(self) -> None:
# The request carries None; the record carries the resolved id. Comparing
# them directly would make every re-run look like a parameter change.
job = make()
job.transcription = self._record("OpenVINO/whisper-large-v3-turbo-int8-ov")
assert job.params_changed(self._record(None)) is False
def test_an_explicit_pin_that_differs_forces_a_rerun(self) -> None:
job = make()
job.transcription = self._record("model-a")
assert job.params_changed(self._record("model-b")) is True
def test_an_explicit_pin_that_matches_does_not(self) -> None:
job = make()
job.transcription = self._record("model-a")
assert job.params_changed(self._record("model-a")) is False