Files
meeting-lab/tests/test_mvp_api.py
T

176 lines
6.7 KiB
Python

import json
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from scripts import run_mvp_meeting as cli
from src.meeting_lab.models.meeting_context import load_meeting_context
from src.meeting_lab.orchestration import mvp as mvp_api
from src.meeting_lab.orchestration.mvp import MvpMeetingConfig, MvpRunResult
from src.meeting_lab.protocol.generate_direct_protocol import DirectProtocolResult
from src.meeting_lab.transcription.whisper import TranscriptionError, TranscriptionResult
def context_data():
return {
"schema_version": "1",
"meeting": {
"meeting_id": "programmatic-test",
"title": "Programmatic Test",
"language": "de",
"date": None,
"objective": "API prüfen",
"notes": "",
},
"participants": [
{
"participant_id": "person-1",
"display_name": "Test Person",
"aliases": [],
"role": "Projektleitung",
"department": None,
"attendance_status": "present",
"notes": None,
}
],
"speaker_mappings": {"SPEAKER_00": "person-1"},
"mentioned_people": [],
"organization": {"name": "Example", "departments": []},
"known_entities": {},
"context_rules": {"do_not_infer_responsibilities": True},
}
def fake_transcribe(audio, model, output, language, **kwargs):
output.mkdir(parents=True, exist_ok=True)
raw = output / "whisper_raw.json"
transcript = output / "transcript.json"
text = output / "transcript.txt"
metadata = output / "runtime_metadata.json"
raw.write_text('{"transcription": []}\n', encoding="utf-8")
transcript.write_text(
json.dumps(
{
"text": "Ein kurzer Besprechungstext.",
"segments": [
{
"id": 0,
"start": 0.0,
"end": 1.0,
"text": "Ein kurzer Besprechungstext.",
}
],
}
),
encoding="utf-8",
)
text.write_text("Ein kurzer Besprechungstext.\n", encoding="utf-8")
metadata.write_text('{"runtime_seconds": 0.1}\n', encoding="utf-8")
return TranscriptionResult(output, raw, transcript, text, metadata, 0.1)
def fake_protocol(transcript, context, **kwargs):
rendered_context = load_meeting_context(context)
assert rendered_context.meeting_id == "programmatic-test"
return DirectProtocolResult(
protocol_text="# Meeting Protocol\n",
exact_prompt="prompt",
model_metadata={"model": kwargs["model"]},
runtime_metadata={"request_count": 1},
raw_response={"response": "# Meeting Protocol\n"},
)
class MvpApiTests(unittest.TestCase):
def config(self, root: Path):
audio = root / "meeting.wav"
model = root / "model.bin"
audio.write_bytes(b"audio")
model.write_bytes(b"model")
return MvpMeetingConfig(
audio_file=audio,
whisper_model=model,
output_root=root / "runs",
model="test:model",
)
def test_programmatic_context_is_persisted_without_source_yaml(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
config = self.config(root)
events = []
with (
patch.object(mvp_api, "transcribe_audio", side_effect=fake_transcribe),
patch.object(
mvp_api, "generate_direct_protocol", side_effect=fake_protocol
),
patch.object(subprocess, "run") as subprocess_run,
):
result = mvp_api.run_mvp_meeting(
config, meeting_context=context_data(), progress_sink=events.append
)
self.assertEqual(result.exit_code, 0)
context_path = result.run_dir / "context/meeting_context.yaml"
self.assertTrue(context_path.is_file())
persisted = load_meeting_context(context_path)
self.assertEqual(persisted.meeting_id, "programmatic-test")
self.assertEqual(persisted.speaker_mappings, {"SPEAKER_00": "person-1"})
subprocess_run.assert_not_called()
self.assertEqual(
[(event.stage, event.status) for event in events],
[
("preparing", "started"),
("preparing", "completed"),
("transcription", "started"),
("transcription", "completed"),
("protocol_generation", "started"),
("protocol_generation", "completed"),
("completed", "completed"),
],
)
self.assertTrue(all(event.progress is None for event in events))
def test_failure_emits_terminal_failure_event(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
config = self.config(root)
events = []
with patch.object(
mvp_api,
"transcribe_audio",
side_effect=TranscriptionError("stopped"),
):
result = mvp_api.run_mvp_meeting(config, progress_sink=events.append)
self.assertEqual(result.exit_code, 2)
self.assertEqual(events[-1].stage, "failed")
self.assertEqual(events[-1].status, "failed")
self.assertIn("transcription", events[-1].message)
metadata = json.loads((result.run_dir / "run_metadata.json").read_text())
self.assertEqual(metadata["status"], "failed")
def test_cli_defaults_and_wrapper_delegate_without_subprocess(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
config = self.config(root)
args = cli.parse_args(
[str(config.audio_file), "--whisper-model", str(config.whisper_model)]
)
expected = MvpRunResult(0, root / "run", root / "run/protocol.md")
with patch.object(cli, "run_mvp_meeting", return_value=expected) as api:
actual = cli.run(args, context_override=context_data())
self.assertEqual(actual, (0, expected.run_dir, expected.protocol_path))
delegated = api.call_args.args[0]
self.assertEqual(delegated.diarization, "off")
self.assertEqual(delegated.language, "de")
self.assertEqual(delegated.whisper_executable, "whisper-cli")
self.assertEqual(api.call_args.kwargs["meeting_context"], context_data())
if __name__ == "__main__":
unittest.main()