138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
"""Tests for diarization job lifecycle improvements.
|
|
|
|
Sprint GAP-004: Diarization Job Lifecycle Issues.
|
|
|
|
Tests cover:
|
|
- Database requirement enforcement for diarization jobs
|
|
- Error handling when database unavailable
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import grpc
|
|
import pytest
|
|
|
|
from noteflow.grpc._config import ServicesConfig
|
|
from noteflow.grpc.proto import noteflow_pb2
|
|
from noteflow.grpc.service import NoteFlowServicer
|
|
from noteflow.infrastructure.diarization import DiarizationEngine
|
|
|
|
|
|
class _FakeDiarizationEngine(DiarizationEngine):
|
|
"""Lightweight diarization engine stub for service config."""
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
|
|
|
|
class _AbortCalled(Exception):
|
|
"""Exception raised when gRPC abort is called."""
|
|
|
|
def __init__(self, code: grpc.StatusCode, details: str) -> None:
|
|
self.code = code
|
|
self.details = details
|
|
super().__init__(f"abort({code}, {details})")
|
|
|
|
|
|
class _MockGrpcContext:
|
|
"""Minimal async gRPC context for tests."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize context."""
|
|
self.abort_code: grpc.StatusCode | None = None
|
|
self.abort_details: str | None = None
|
|
|
|
def set_code(self, code: grpc.StatusCode) -> None:
|
|
"""Set response status code."""
|
|
self.abort_code = code
|
|
|
|
def set_details(self, details: str) -> None:
|
|
"""Set response status details."""
|
|
self.abort_details = details
|
|
|
|
async def abort(self, code: grpc.StatusCode, details: str) -> None:
|
|
"""Simulate gRPC abort by raising exception."""
|
|
self.abort_code = code
|
|
self.abort_details = details
|
|
raise _AbortCalled(code, details)
|
|
|
|
|
|
@pytest.fixture
|
|
def servicer() -> NoteFlowServicer:
|
|
"""Create servicer with mock diarization engine but no DB support."""
|
|
return NoteFlowServicer(services=ServicesConfig(diarization_engine=_FakeDiarizationEngine()))
|
|
|
|
|
|
class TestDatabaseRequirement:
|
|
"""Tests verifying diarization requires database support."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refine_requires_database_support(self, servicer: NoteFlowServicer) -> None:
|
|
"""RefineSpeakerDiarization returns error when database unavailable."""
|
|
store = servicer.get_memory_store()
|
|
meeting = store.create("Test meeting")
|
|
meeting.start_recording()
|
|
meeting.begin_stopping()
|
|
meeting.stop_recording()
|
|
store.update(meeting)
|
|
|
|
context = _MockGrpcContext()
|
|
response = await servicer.RefineSpeakerDiarization(
|
|
noteflow_pb2.RefineSpeakerDiarizationRequest(meeting_id=str(meeting.id)),
|
|
context,
|
|
)
|
|
|
|
# Should return error response, not job_id
|
|
assert not response.job_id, "Should not return job_id without database"
|
|
assert response.status == noteflow_pb2.JOB_STATUS_FAILED, "Status should be FAILED"
|
|
assert context.abort_code == grpc.StatusCode.FAILED_PRECONDITION, "Error code should be FAILED_PRECONDITION"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_refine_error_mentions_database(self, servicer: NoteFlowServicer) -> None:
|
|
"""Error message should mention database requirement."""
|
|
store = servicer.get_memory_store()
|
|
meeting = store.create("Test meeting")
|
|
meeting.start_recording()
|
|
meeting.begin_stopping()
|
|
meeting.stop_recording()
|
|
store.update(meeting)
|
|
|
|
context = _MockGrpcContext()
|
|
response = await servicer.RefineSpeakerDiarization(
|
|
noteflow_pb2.RefineSpeakerDiarizationRequest(meeting_id=str(meeting.id)),
|
|
context,
|
|
)
|
|
|
|
# Error message should explain database is required
|
|
error_msg = response.error_message.lower()
|
|
assert "database" in error_msg, "Error should mention 'database'"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_status_requires_database(self, servicer: NoteFlowServicer) -> None:
|
|
"""GetDiarizationJobStatus aborts when database unavailable."""
|
|
context = _MockGrpcContext()
|
|
|
|
# Should abort because no DB support
|
|
with pytest.raises(_AbortCalled, match="database") as exc_info:
|
|
await servicer.GetDiarizationJobStatus(
|
|
noteflow_pb2.GetDiarizationJobStatusRequest(job_id="any-job-id"),
|
|
context,
|
|
)
|
|
|
|
# Verify abort was called with NOT_FOUND (no DB support means jobs can't be found)
|
|
assert exc_info.value.code == grpc.StatusCode.NOT_FOUND, "Should abort with NOT_FOUND"
|
|
assert "database" in exc_info.value.details.lower(), "Error should mention database"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_cancel_requires_database(self, servicer: NoteFlowServicer) -> None:
|
|
"""CancelDiarizationJob returns error when database unavailable."""
|
|
context = _MockGrpcContext()
|
|
|
|
response = await servicer.CancelDiarizationJob(
|
|
noteflow_pb2.CancelDiarizationJobRequest(job_id="any-job-id"),
|
|
context,
|
|
)
|
|
|
|
assert response.success is False, "Cancel should fail without database"
|
|
assert "database" in response.error_message.lower(), "Error should mention database"
|