Локальный FastAPI-сервис поверх GigaAM v3 и sherpa-onnx: приём аудио, очередь задач, разделение по говорящим, постобработка терминов. Доставка на Windows - ZIP со встроенным Python, без установки чего-либо. Обновление кода при запуске тянется из релизов Gitea: меняется только папка app, десятки килобайт вместо всего пакета. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
87 lines
3.2 KiB
Python
87 lines
3.2 KiB
Python
"""Тесты хранилища задач: переживает перезапуск, не теряет статусы."""
|
|
import pytest
|
|
|
|
from app.store import JobStore, JobStatus
|
|
|
|
|
|
@pytest.fixture
|
|
def store(tmp_path):
|
|
return JobStore(tmp_path / "jobs.db")
|
|
|
|
|
|
class TestJobLifecycle:
|
|
def test_new_job_is_queued(self, store):
|
|
job_id = store.create(filename="call.wav", duration_sec=120.0)
|
|
job = store.get(job_id)
|
|
assert job["status"] == JobStatus.QUEUED
|
|
assert job["filename"] == "call.wav"
|
|
|
|
def test_job_id_is_unique(self, store):
|
|
ids = {store.create(filename=f"{i}.wav", duration_sec=1.0) for i in range(20)}
|
|
assert len(ids) == 20
|
|
|
|
def test_mark_running_then_done(self, store):
|
|
job_id = store.create(filename="a.wav", duration_sec=10.0)
|
|
store.mark_running(job_id)
|
|
assert store.get(job_id)["status"] == JobStatus.RUNNING
|
|
store.mark_done(job_id, result={"turns": []})
|
|
job = store.get(job_id)
|
|
assert job["status"] == JobStatus.DONE
|
|
assert job["result"] == {"turns": []}
|
|
|
|
def test_mark_failed_keeps_error(self, store):
|
|
job_id = store.create(filename="a.wav", duration_sec=10.0)
|
|
store.mark_failed(job_id, error="сломалось")
|
|
job = store.get(job_id)
|
|
assert job["status"] == JobStatus.FAILED
|
|
assert job["error"] == "сломалось"
|
|
|
|
def test_unknown_job_returns_none(self, store):
|
|
assert store.get("нет-такого") is None
|
|
|
|
def test_survives_reopen(self, tmp_path):
|
|
"""Задачи должны переживать перезапуск сервиса."""
|
|
path = tmp_path / "jobs.db"
|
|
first = JobStore(path)
|
|
job_id = first.create(filename="a.wav", duration_sec=5.0)
|
|
first.mark_done(job_id, result={"ok": True})
|
|
|
|
second = JobStore(path)
|
|
assert second.get(job_id)["result"] == {"ok": True}
|
|
|
|
|
|
class TestQueue:
|
|
def test_take_next_returns_oldest_queued(self, store):
|
|
first = store.create(filename="1.wav", duration_sec=1.0)
|
|
store.create(filename="2.wav", duration_sec=1.0)
|
|
assert store.take_next() == first
|
|
|
|
def test_take_next_skips_running(self, store):
|
|
job_id = store.create(filename="1.wav", duration_sec=1.0)
|
|
store.mark_running(job_id)
|
|
assert store.take_next() is None
|
|
|
|
def test_take_next_on_empty_queue(self, store):
|
|
assert store.take_next() is None
|
|
|
|
def test_queue_position_counts_only_waiting(self, store):
|
|
a = store.create(filename="1.wav", duration_sec=1.0)
|
|
b = store.create(filename="2.wav", duration_sec=1.0)
|
|
store.mark_running(a)
|
|
assert store.queue_position(b) == 1
|
|
|
|
|
|
class TestCleanup:
|
|
def test_removes_old_finished_jobs(self, store):
|
|
job_id = store.create(filename="a.wav", duration_sec=1.0)
|
|
store.mark_done(job_id, result={})
|
|
removed = store.cleanup(max_age_hours=0)
|
|
assert removed == 1
|
|
assert store.get(job_id) is None
|
|
|
|
def test_keeps_running_jobs_regardless_of_age(self, store):
|
|
job_id = store.create(filename="a.wav", duration_sec=1.0)
|
|
store.mark_running(job_id)
|
|
assert store.cleanup(max_age_hours=0) == 0
|
|
assert store.get(job_id) is not None
|