8312db1b49
Implements process_utterance() with concurrent STT/embedding via asyncio.gather, speaker matching with configurable thresholds, utterance DB write, and WAV clip save. httpx imported lazily to keep the dev environment functional without full install. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
90 lines
3.2 KiB
Python
90 lines
3.2 KiB
Python
import pytest
|
|
import numpy as np
|
|
from datetime import datetime, timezone
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
# Pre-import so mocker.patch can resolve the module before the test's local import
|
|
import app.pipeline.processor # noqa: F401
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_utterance_known_speaker(mocker, settings, db):
|
|
# Setup: insert a room and speaker with embedding
|
|
db.execute("INSERT INTO rooms (name) VALUES (?)", ("Kitchen",))
|
|
db.execute("INSERT INTO speakers (name) VALUES (?)", ("Jeremy",))
|
|
db.commit()
|
|
room = db.execute("SELECT id FROM rooms WHERE name='Kitchen'").fetchone()
|
|
speaker = db.execute("SELECT id FROM speakers WHERE name='Jeremy'").fetchone()
|
|
|
|
from app.pipeline.recognition import pack_embedding
|
|
ref_emb = np.array([1.0] + [0.0] * 255, dtype=np.float32)
|
|
db.execute(
|
|
"INSERT INTO voice_embeddings (speaker_id, embedding) VALUES (?,?)",
|
|
(speaker["id"], pack_embedding(ref_emb)),
|
|
)
|
|
db.commit()
|
|
|
|
# Mock Whisper HTTP call
|
|
mocker.patch(
|
|
"app.pipeline.processor.transcribe_audio",
|
|
new=AsyncMock(return_value="hello world"),
|
|
)
|
|
# Mock resemblyzer — return a very similar embedding
|
|
query_emb = np.array([0.99] + [0.0] * 255, dtype=np.float32)
|
|
mocker.patch("app.pipeline.processor.embed_audio", return_value=query_emb)
|
|
# Mock clip saving
|
|
mocker.patch("app.pipeline.processor.save_clip", return_value="/data/clips/test.wav")
|
|
|
|
from app.pipeline.processor import process_utterance
|
|
start = datetime(2026, 1, 1, 10, 0, 0, tzinfo=timezone.utc)
|
|
end = datetime(2026, 1, 1, 10, 0, 3, tzinfo=timezone.utc)
|
|
|
|
utterance_id = await process_utterance(
|
|
room_id=room["id"],
|
|
audio_bytes=b"\x00" * 100,
|
|
start_time=start,
|
|
end_time=end,
|
|
settings=settings,
|
|
db=db,
|
|
)
|
|
|
|
assert utterance_id is not None
|
|
row = db.execute("SELECT * FROM utterances WHERE id=?", (utterance_id,)).fetchone()
|
|
assert row["transcript"] == "hello world"
|
|
assert row["match_status"] == "known"
|
|
assert row["speaker_id"] == speaker["id"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_process_utterance_unknown_speaker(mocker, settings, db):
|
|
db.execute("INSERT INTO rooms (name) VALUES (?)", ("Office",))
|
|
db.commit()
|
|
room = db.execute("SELECT id FROM rooms WHERE name='Office'").fetchone()
|
|
|
|
mocker.patch(
|
|
"app.pipeline.processor.transcribe_audio",
|
|
new=AsyncMock(return_value="test transcript"),
|
|
)
|
|
mocker.patch(
|
|
"app.pipeline.processor.embed_audio",
|
|
return_value=np.zeros(256, dtype=np.float32),
|
|
)
|
|
mocker.patch("app.pipeline.processor.save_clip", return_value="/data/clips/test.wav")
|
|
|
|
from app.pipeline.processor import process_utterance
|
|
start = datetime(2026, 1, 1, 10, 0, 0, tzinfo=timezone.utc)
|
|
end = datetime(2026, 1, 1, 10, 0, 2, tzinfo=timezone.utc)
|
|
|
|
utterance_id = await process_utterance(
|
|
room_id=room["id"],
|
|
audio_bytes=b"\x00" * 100,
|
|
start_time=start,
|
|
end_time=end,
|
|
settings=settings,
|
|
db=db,
|
|
)
|
|
|
|
row = db.execute("SELECT * FROM utterances WHERE id=?", (utterance_id,)).fetchone()
|
|
assert row["match_status"] == "unknown"
|
|
assert row["speaker_id"] is None
|