import asyncio import base64 import logging import os import sys from pathlib import Path sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) import openrouter_client from openrouter_client import ( SUMMARY_TRANSCRIPT_CHAR_LIMIT, TRANSCRIPTION_MODELS, _audio_format, _build_transcription_payload, _chunk_text, summarize, transcribe, ) def test_audio_format_defaults_to_wav_when_missing_suffix(tmp_path: Path): path = tmp_path / "recording" path.write_bytes(b"abc") assert _audio_format(str(path)) == "wav" def test_build_transcription_payload_uses_base64_json_shape(tmp_path: Path): path = tmp_path / "meeting.wav" path.write_bytes(b"RIFFdemo") payload = _build_transcription_payload(str(path), TRANSCRIPTION_MODELS[0]) assert payload["model"] == "openai/gpt-4o-mini-transcribe" assert payload["input_audio"]["format"] == "wav" assert payload["input_audio"]["data"] == base64.b64encode(b"RIFFdemo").decode("ascii") def test_chunk_text_splits_long_text_under_limit(): text = ("alpha beta gamma\n" * 1500).strip() chunks = _chunk_text(text, limit=SUMMARY_TRANSCRIPT_CHAR_LIMIT) assert len(chunks) > 1 assert all(len(chunk) <= SUMMARY_TRANSCRIPT_CHAR_LIMIT for chunk in chunks) assert "\n".join(chunks) == text def test_transcribe_combines_chunk_transcripts_and_logs_chunking(tmp_path: Path, monkeypatch, caplog): source = tmp_path / "meeting.wav" normalized = tmp_path / "normalized.wav" chunk1 = tmp_path / "chunk-000.wav" chunk2 = tmp_path / "chunk-001.wav" source.write_bytes(b"source") normalized.write_bytes(b"normalized") chunk1.write_bytes(b"c1") chunk2.write_bytes(b"c2") async def fake_normalize(audio_path: str) -> str: assert audio_path == str(source) return str(normalized) async def fake_split(audio_path: str) -> list[str]: assert audio_path == str(normalized) return [str(chunk1), str(chunk2)] seen = [] async def fake_transcribe_chunk(_client, audio_path: str, _headers: dict[str, str]) -> str: seen.append(Path(audio_path).name) return { "chunk-000.wav": "hello there", "chunk-001.wav": "general kenobi", }[Path(audio_path).name] monkeypatch.setattr(openrouter_client, "_normalize_audio_for_transcription", fake_normalize) monkeypatch.setattr(openrouter_client, "_split_audio_for_transcription", fake_split) monkeypatch.setattr(openrouter_client, "_transcribe_chunk", fake_transcribe_chunk) monkeypatch.setattr(openrouter_client, "_auth_headers", lambda: {"Authorization": "Bearer test"}) class DummyClient: async def __aenter__(self): return object() async def __aexit__(self, exc_type, exc, tb): return False monkeypatch.setattr(openrouter_client.httpx, "AsyncClient", lambda timeout: DummyClient()) with caplog.at_level(logging.INFO): result = asyncio.run(transcribe(str(source))) assert result == "hello there\n\ngeneral kenobi" assert seen == ["chunk-000.wav", "chunk-001.wav"] assert "Split normalized audio into 2 chunk(s)" in caplog.text def test_summarize_uses_multi_stage_pipeline_for_long_transcript(monkeypatch, caplog): long_transcript = ("decision item follow-up\n" * 4000).strip() prompts = [] async def fake_chat_completion(prompt: str, *, timeout: int = 180) -> str: prompts.append(prompt) if "Partial meeting transcript chunk" in prompt: return f"INTERMEDIATE-{len(prompts)}" return "FINAL SUMMARY" monkeypatch.setattr(openrouter_client, "_chat_completion", fake_chat_completion) with caplog.at_level(logging.INFO): result = asyncio.run(summarize(long_transcript)) assert result == "FINAL SUMMARY" assert len(prompts) >= 2 assert any("Partial meeting transcript chunk" in prompt for prompt in prompts[:-1]) assert "Combined chunk summaries" in prompts[-1] assert "Transcript exceeds single-pass summary limit" in caplog.text