802 lines
32 KiB
Python
802 lines
32 KiB
Python
import pytest
|
||
from spectral.database import Database
|
||
from spectral.main import app, get_db
|
||
from fastapi.testclient import TestClient
|
||
from fastapi import HTTPException
|
||
import json
|
||
from scipy.io import wavfile as wv
|
||
import os
|
||
from unittest.mock import Mock, patch
|
||
|
||
client = TestClient(app)
|
||
|
||
# Load the JSON file
|
||
data_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data")
|
||
with open(os.path.join(data_dir, "frames.json"), "r") as file:
|
||
frame_data = json.load(file)
|
||
|
||
typical_1_fs, typical_1_data = wv.read(
|
||
os.path.join(data_dir, "torgo-dataset/MC02_control_head_sentence1.wav")
|
||
)
|
||
typical_1_data = typical_1_data.tolist()
|
||
|
||
with open(
|
||
os.path.join(data_dir, "torgo-dataset/MC02_control_head_sentence1.wav"), mode="rb"
|
||
) as f:
|
||
control_sentence = f.read()
|
||
|
||
|
||
@pytest.fixture
|
||
def file_state():
|
||
return {
|
||
"id": 1,
|
||
"name": "something",
|
||
"cycleEnabled": True,
|
||
"frame": {
|
||
"startIndex": 1,
|
||
"endIndex": 200,
|
||
},
|
||
"transcriptions": [[]],
|
||
}
|
||
|
||
|
||
@pytest.fixture
|
||
def db_mock():
|
||
mock = Mock()
|
||
mock.fetch_file.return_value = {
|
||
"data": control_sentence,
|
||
"creationTime": 1,
|
||
"groundTruth": "hai test",
|
||
}
|
||
mock.get_transcriptions.return_value = [[{"value": "hi", "start": 0, "end": 1}]]
|
||
mock.__class__ = Database
|
||
|
||
yield mock
|
||
|
||
|
||
@pytest.fixture(autouse=True)
|
||
def override_dependency(db_mock):
|
||
app.dependency_overrides[get_db] = lambda: db_mock
|
||
yield
|
||
app.dependency_overrides = {}
|
||
|
||
|
||
def test_signal_correct_mode_file_not_found(db_mock, file_state):
|
||
db_mock.fetch_file.side_effect = HTTPException(
|
||
status_code=500, detail="database error"
|
||
)
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 404
|
||
), "Expected status code 404 when file is not found"
|
||
assert (
|
||
response.json()["detail"] == "File not found"
|
||
), "Expected detail message 'File not found'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_correct_simple_info(db_mock, file_state):
|
||
file_state["frame"] = None
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert response.status_code == 200, "Expected status code 200 for simple info mode"
|
||
result = response.json()
|
||
assert result["fileSize"] == 146158, "Expected file size to be 146158"
|
||
assert (
|
||
result["fileCreationDate"] == "1970-01-01T00:00:01Z"
|
||
), "Expected creation date to be '1970-01-01T00:00:01Z'"
|
||
assert result["frame"] is None, "Expected frame to be None"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_correct_spectrogram(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/spectrogram", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert response.status_code == 200, "Expected status code 200 for spectrogram mode"
|
||
assert response.json() is None, "Expected response to be None"
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file not to be called"
|
||
|
||
|
||
def test_signal_correct_waveform(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/waveform", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert response.status_code == 200, "Expected status code 200 for waveform mode"
|
||
result = response.json()
|
||
assert result is None, "Expected response to be None"
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file not to be called"
|
||
|
||
|
||
def test_signal_correct_vowel_space(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/vowel-space", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert response.status_code == 200, "Expected status code 200 for vowel-space mode"
|
||
assert response.json() == {
|
||
"f1": 1242.857422568559,
|
||
"f2": 2503.8350190318893,
|
||
}, "Expected f1 and f2 values"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_correct_transcription(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/transcription", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for transcription mode"
|
||
assert response.json() is None, "Expected response to be None"
|
||
|
||
|
||
def test_signal_mode_wrong_mode(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/wrongmode", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert response.status_code == 422, "Expected status code 422 for wrong mode"
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file not to be called"
|
||
|
||
|
||
def test_signal_mode_frame_start_index_missing(db_mock, file_state):
|
||
file_state["frame"] = {"endIndex": 1}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when startIndex is missing"
|
||
assert (
|
||
response.json()["detail"] == "no startIndex provided"
|
||
), "Expected detail message 'no startIndex provided'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_end_index_missing(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 1}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when endIndex is missing"
|
||
assert (
|
||
response.json()["detail"] == "no endIndex provided"
|
||
), "Expected detail message 'no endIndex provided'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_start_index_bigger_than_end_index(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 2, "endIndex": 1}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when startIndex is bigger than endIndex"
|
||
assert (
|
||
response.json()["detail"] == "startIndex should be strictly lower than endIndex"
|
||
), "Expected detail message 'startIndex should be strictly lower than endIndex'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_start_index_bigger_than_end_index_equal_numbers(
|
||
db_mock, file_state
|
||
):
|
||
file_state["frame"] = {"startIndex": 2, "endIndex": 2}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when startIndex is equal to endIndex"
|
||
assert (
|
||
response.json()["detail"] == "startIndex should be strictly lower than endIndex"
|
||
), "Expected detail message 'startIndex should be strictly lower than endIndex'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_negative_start_index(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": -1, "endIndex": 2}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when startIndex is negative"
|
||
assert (
|
||
response.json()["detail"] == "startIndex should be larger or equal to 0"
|
||
), "Expected detail message 'startIndex should be larger or equal to 0'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_too_large_end_index(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 0, "endIndex": 73041}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 400
|
||
), "Expected status code 400 when endIndex is too large"
|
||
assert (
|
||
response.json()["detail"] == "endIndex should be lower than the file length"
|
||
), "Expected detail message 'endIndex should be lower than the file length'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_frame_too_large_boundary(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 0, "endIndex": 73040}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 when frame boundaries are valid"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_simple_info_with_frame(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 22500, "endIndex": 23250}
|
||
response = client.get(
|
||
"/signals/modes/simple-info", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for simple info mode with frame"
|
||
result = response.json()
|
||
assert result["fileSize"] == 146158, "Expected file size to be 146158"
|
||
assert (
|
||
result["fileCreationDate"] == "1970-01-01T00:00:01Z"
|
||
), "Expected creation date to be '1970-01-01T00:00:01Z'"
|
||
assert result["averagePitch"] == pytest.approx(
|
||
34.38, 0.1
|
||
), "Expected average pitch to be approximately 34.38"
|
||
assert result["duration"] == pytest.approx(
|
||
4.565, 0.01
|
||
), "Expected duration to be approximately 4.565"
|
||
assert result["frame"] is not None, "Expected frame to be not None"
|
||
assert result["frame"]["duration"] == pytest.approx(
|
||
0.046875
|
||
), "Expected frame duration to be approximately 0.046875"
|
||
assert result["frame"]["f1"] == pytest.approx(
|
||
623.19, 0.1
|
||
), "Expected frame f1 to be approximately 623.19"
|
||
assert result["frame"]["f2"] == pytest.approx(
|
||
1635.4, 0.1
|
||
), "Expected frame f2 to be approximately 1635.4"
|
||
assert result["frame"]["pitch"] == pytest.approx(
|
||
591.6, 0.1
|
||
), "Expected frame pitch to be approximately 591.6"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_vowel_space_mode_with_frame(db_mock, file_state):
|
||
file_state["frame"] = {"startIndex": 22500, "endIndex": 23250}
|
||
response = client.get(
|
||
"/signals/modes/vowel-space", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for vowel-space mode with frame"
|
||
result = response.json()
|
||
assert result["f1"] == pytest.approx(
|
||
623.19, 0.1
|
||
), "Expected frame f1 to be approximately 623.19"
|
||
assert result["f2"] == pytest.approx(
|
||
1635.4, 0.1
|
||
), "Expected frame f2 to be approximately 1635.4"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_signal_mode_transcription_db_problem(db_mock):
|
||
db_mock.fetch_file.side_effect = HTTPException(
|
||
status_code=500, detail="database error"
|
||
)
|
||
response = client.get("/transcription/deepgram/1")
|
||
assert (
|
||
response.status_code == 404
|
||
), "Expected status code 404 when there is a database error"
|
||
assert (
|
||
response.json()["detail"] == "File not found"
|
||
), "Expected detail message 'File not found'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_transcription_model_found(db_mock):
|
||
with patch(
|
||
"spectral.transcription.transcription.deepgram_transcription"
|
||
) as mock_deepgram_transcription:
|
||
mock_deepgram_transcription.return_value = [
|
||
{"value": "word1", "start": 0.5, "end": 1.0},
|
||
{"value": "word2", "start": 1.5, "end": 2.0},
|
||
]
|
||
response = client.get("/transcription/deepgram/1")
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for deepgram transcription"
|
||
result = response.json()
|
||
assert result == [
|
||
{"value": "", "start": 0, "end": 0.5},
|
||
{"value": "word1", "start": 0.5, "end": 1.0},
|
||
{"value": "", "start": 1.0, "end": 1.5},
|
||
{"value": "word2", "start": 1.5, "end": 2.0},
|
||
{"end": 4.565, "start": 2.0, "value": ""},
|
||
], "Expected transcription result to match the mock return value"
|
||
assert (
|
||
db_mock.fetch_file.call_count == 1
|
||
), "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_transcription_model_not_found(db_mock):
|
||
response = client.get("/transcription/non_existant_model/1")
|
||
assert (
|
||
response.status_code == 404
|
||
), "Expected status code 404 when model is not found"
|
||
assert (
|
||
response.json()["detail"] == "Model was not found"
|
||
), "Expected detail message 'Model was not found'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_analyze_signal_mode_invalid_id(db_mock, file_state):
|
||
file_state["id"] = "invalid_id"
|
||
db_mock.fetch_file.side_effect = Exception("Database error")
|
||
response = client.get(
|
||
"/signals/modes/vowel-space", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 404
|
||
), "Expected status code 404 when file ID is invalid"
|
||
assert (
|
||
response.json()["detail"] == "File not found"
|
||
), "Expected detail message 'File not found'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_transcribe_file_invalid_model(db_mock):
|
||
response = client.get("/transcription/invalid_model/1")
|
||
assert (
|
||
response.status_code == 404
|
||
), "Expected status code 404 when transcription model is invalid"
|
||
assert (
|
||
response.json()["detail"] == "Model was not found"
|
||
), "Expected detail message 'Model was not found'"
|
||
assert db_mock.fetch_file.call_count == 1, "Expected fetch_file to be called once"
|
||
|
||
|
||
@pytest.mark.skip(reason="Not implemented")
|
||
def test_transcribe_file_no_api_key(db_mock):
|
||
with patch("spectral.transcription.models.deepgram.os.getenv") as mock_getenv:
|
||
mock_getenv.return_value = None
|
||
response = client.get("/transcription/deepgram/1")
|
||
assert (
|
||
response.status_code == 500
|
||
), "Expected status code 500 when API key is missing"
|
||
assert (
|
||
db_mock.fetch_file.call_count == 1
|
||
), "Expected fetch_file to be called once"
|
||
|
||
|
||
@pytest.fixture
|
||
def mock_db(mocker):
|
||
mock_db_class = mocker.patch("spectral.database.Database")
|
||
mock_db_instance = mock_db_class.return_value
|
||
mock_db_instance.connection = Mock()
|
||
mock_db_instance.close = Mock()
|
||
return mock_db_instance
|
||
|
||
|
||
def test_error_rate_no_reference(db_mock, file_state):
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 when ground truth is missing"
|
||
assert response.json() is None, "Expected response to be None"
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file to be called never"
|
||
|
||
|
||
def test_error_rate_reference_None(file_state):
|
||
file_state["reference"] = None
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 when ground truth is missing"
|
||
assert response.json() is None, "Expected response to be None"
|
||
|
||
|
||
def test_error_rate_no_reference_caption(db_mock, file_state):
|
||
file_state["reference"] = {}
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json() is None
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_no_hypothesis(db_mock, file_state):
|
||
file_state["reference"] = {"captions": [{"value": "Hi"}]}
|
||
file_state["hypothesis"] = None
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json() is None
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_hypothesis_None(db_mock, file_state):
|
||
file_state["reference"] = {"captions": [{"value": "Hi"}]}
|
||
file_state["hypothesis"] = None
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json() is None
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_no_hypothesis_caption(db_mock, file_state):
|
||
file_state["reference"] = {"captions": [{"value": "Hi"}]}
|
||
file_state["hypothesis"] = {}
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json() is None
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_empty_reference_array(db_mock, file_state):
|
||
file_state["reference"] = {"captions": []}
|
||
file_state["hypothesis"] = {"captions": [{"value": "Hi"}]}
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
assert response.json() is None
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_empty_hypothesis_array(db_mock, file_state):
|
||
file_state["reference"] = {"captions": [{"value": "Hi"}]}
|
||
file_state["hypothesis"] = {"captions": []}
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
|
||
assert response.status_code == 200
|
||
print(response.json())
|
||
assert response.json() == {
|
||
"wordLevel": {
|
||
"wer": 1.0,
|
||
"mer": 1.0,
|
||
"wil": 1.0,
|
||
"wip": 0.0,
|
||
"hits": 0,
|
||
"substitutions": 0,
|
||
"insertions": 0,
|
||
"deletions": 1,
|
||
"reference": ["Hi"],
|
||
"hypothesis": [],
|
||
"alignments": [
|
||
{
|
||
"type": "delete",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 1,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 0,
|
||
}
|
||
],
|
||
},
|
||
"characterLevel": {
|
||
"cer": 1.0,
|
||
"hits": 0,
|
||
"substitutions": 0,
|
||
"insertions": 0,
|
||
"deletions": 2,
|
||
"reference": ["H", "i"],
|
||
"hypothesis": [],
|
||
"alignments": [
|
||
{
|
||
"type": "delete",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 2,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 0,
|
||
}
|
||
],
|
||
},
|
||
}, "the response was not the same"
|
||
assert db_mock.get_transcriptions.call_count == 0
|
||
|
||
|
||
def test_error_rate_with_reference_no_hypothesis(db_mock, file_state):
|
||
file_state["reference"] = {}
|
||
file_state["reference"]["captions"] = [{"value": "hai test"}]
|
||
file_state["hypothesis"] = {}
|
||
file_state["hypothesis"]["captions"] = []
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 when ground truth is provided"
|
||
result = response.json()
|
||
word_level = result["wordLevel"]
|
||
assert word_level["wer"] == 1.0, "Expected word error rate (WER) to be 1.0"
|
||
assert word_level["mer"] == 1.0, "Expected match error rate (MER) to be 1.0"
|
||
assert word_level["wil"] == 1.0, "Expected word information lost (WIL) to be 1.0"
|
||
assert word_level["wip"] == 0, "Expected word information preserved (WIP) to be 0"
|
||
assert word_level["hits"] == 0, "Expected hits to be 0"
|
||
assert word_level["substitutions"] == 0, "Expected substitutions to be 0"
|
||
assert word_level["insertions"] == 0, "Expected insertions to be 0"
|
||
assert word_level["deletions"] == 2, "Expected deletions to be 2"
|
||
assert word_level["reference"] == [
|
||
"hai",
|
||
"test",
|
||
], "Expected reference to be ['hai', 'test']"
|
||
assert word_level["hypothesis"] == [], "Expected hypothesis to be empty"
|
||
assert len(word_level["alignments"]) == 1, "Expected one alignment"
|
||
assert word_level["alignments"][0] == {
|
||
"type": "delete",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 2,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 0,
|
||
}, "Expected delete alignment"
|
||
|
||
character_level = result["characterLevel"]
|
||
|
||
assert character_level["cer"] == 1, "Expected character error rate (CER) to be 1"
|
||
assert character_level["hits"] == 0, "Expected hits to be 0"
|
||
assert character_level["substitutions"] == 0, "Expected substitutions to be 0"
|
||
assert character_level["insertions"] == 0, "Expected insertions to be 0"
|
||
assert character_level["deletions"] == 8, "Expected deletions to be 8"
|
||
assert character_level["reference"] == [
|
||
"h",
|
||
"a",
|
||
"i",
|
||
" ",
|
||
"t",
|
||
"e",
|
||
"s",
|
||
"t",
|
||
], "Expected reference to be ['h', 'a', 'i', ' ', 't', 'e', 's', 't']"
|
||
assert character_level["hypothesis"] == [], "Expected hypothesis to be empty"
|
||
assert len(character_level["alignments"]) == 1, "Expected one alignment"
|
||
assert character_level["alignments"][0] == {
|
||
"type": "delete",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 8,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 0,
|
||
}, "Expected delete alignment"
|
||
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_error_rate_with_reference_and_hypothesis(db_mock, file_state):
|
||
file_state["reference"] = {}
|
||
file_state["reference"]["captions"] = [{"value": "hai test"}]
|
||
file_state["hypothesis"] = {}
|
||
file_state["hypothesis"]["captions"] = [{"value": "hi"}]
|
||
response = client.get(
|
||
"/signals/modes/error-rate", params={"fileState": json.dumps(file_state)}
|
||
)
|
||
result = response.json()
|
||
word_level = result["wordLevel"]
|
||
assert word_level["wer"] == 1.0, "Expected word error rate (WER) to be 1.0"
|
||
assert word_level["mer"] == 1.0, "Expected match error rate (MER) to be 1.0"
|
||
assert word_level["wil"] == 1.0, "Expected word information lost (WIL) to be 1.0"
|
||
assert word_level["wip"] == 0, "Expected word information preserved (WIP) to be 0"
|
||
assert word_level["hits"] == 0, "Expected hits to be 0"
|
||
assert word_level["substitutions"] == 1, "Expected substitutions to be 1"
|
||
assert word_level["insertions"] == 0, "Expected insertions to be 0"
|
||
assert word_level["deletions"] == 1, "Expected deletions to be 1"
|
||
assert word_level["reference"] == [
|
||
"hai",
|
||
"test",
|
||
], "Expected reference to be ['hai', 'test']"
|
||
assert word_level["hypothesis"] == ["hi"], "Expected hypothesis to be ['hi']"
|
||
assert len(word_level["alignments"]) == 2, "Expected two alignments"
|
||
assert word_level["alignments"][0] == {
|
||
"type": "substitute",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 1,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 1,
|
||
}, "Expected substitute alignment"
|
||
assert word_level["alignments"][1] == {
|
||
"type": "delete",
|
||
"referenceStartIndex": 1,
|
||
"referenceEndIndex": 2,
|
||
"hypothesisStartIndex": 1,
|
||
"hypothesisEndIndex": 1,
|
||
}, "Expected delete alignment"
|
||
|
||
character_level = result["characterLevel"]
|
||
|
||
assert (
|
||
character_level["cer"] == 0.75
|
||
), "Expected character error rate (CER) to be 0.75"
|
||
assert character_level["hits"] == 2, "Expected hits to be 2"
|
||
assert character_level["substitutions"] == 0, "Expected substitutions to be 0"
|
||
assert character_level["insertions"] == 0, "Expected insertions to be 0"
|
||
assert character_level["deletions"] == 6, "Expected deletions to be 6"
|
||
assert len(character_level["alignments"]) == 4, "Expected four alignments"
|
||
assert character_level["reference"] == [
|
||
"h",
|
||
"a",
|
||
"i",
|
||
" ",
|
||
"t",
|
||
"e",
|
||
"s",
|
||
"t",
|
||
], "Expected reference to be ['h', 'a', 'i', ' ', 't', 'e', 's', 't']"
|
||
assert character_level["hypothesis"] == [
|
||
"h",
|
||
"i",
|
||
], "Expected hypothesis to be ['h', 'i']"
|
||
assert character_level["alignments"][0] == {
|
||
"type": "equal",
|
||
"referenceStartIndex": 0,
|
||
"referenceEndIndex": 1,
|
||
"hypothesisStartIndex": 0,
|
||
"hypothesisEndIndex": 1,
|
||
}, "Expected equal alignment"
|
||
assert character_level["alignments"][1] == {
|
||
"type": "delete",
|
||
"referenceStartIndex": 1,
|
||
"referenceEndIndex": 2,
|
||
"hypothesisStartIndex": 1,
|
||
"hypothesisEndIndex": 1,
|
||
}, "Expected delete alignment"
|
||
assert character_level["alignments"][2] == {
|
||
"type": "equal",
|
||
"referenceStartIndex": 2,
|
||
"referenceEndIndex": 3,
|
||
"hypothesisStartIndex": 1,
|
||
"hypothesisEndIndex": 2,
|
||
}, "Expected equal alignment"
|
||
assert character_level["alignments"][3] == {
|
||
"type": "delete",
|
||
"referenceStartIndex": 3,
|
||
"referenceEndIndex": 8,
|
||
"hypothesisStartIndex": 2,
|
||
"hypothesisEndIndex": 2,
|
||
}, "Expected delete alignment"
|
||
|
||
assert db_mock.fetch_file.call_count == 0, "Expected fetch_file to be called never"
|
||
|
||
|
||
def test_phone_transcription(db_mock, file_state):
|
||
with patch(
|
||
"spectral.transcription.models.allosaurus.deepgram_transcription"
|
||
) as mock_deepgram_transcription:
|
||
mock_deepgram_transcription.return_value = [
|
||
{"value": "", "start": 0.0, "end": 1.04},
|
||
{"value": "the", "start": 1.04, "end": 1.36},
|
||
{"value": "quick", "start": 1.36, "end": 1.68},
|
||
{"value": "brown", "start": 1.68, "end": 2.0},
|
||
{"value": "fox", "start": 2.0, "end": 2.3999999},
|
||
{"value": "jumps", "start": 2.3999999, "end": 2.72},
|
||
{"value": "over", "start": 2.72, "end": 3.04},
|
||
{"value": "the", "start": 3.04, "end": 3.1999998},
|
||
{"value": "lazy", "start": 3.1999998, "end": 3.52},
|
||
{"value": "dog", "start": 3.52, "end": 4.02},
|
||
{"value": "", "start": 4.02, "end": 4.565},
|
||
]
|
||
response = client.get("/transcription/allosaurus/1")
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for allosaurus transcription"
|
||
result = response.json()
|
||
assert result == [
|
||
{"value": "", "start": 0, "end": 1.04},
|
||
{"value": "ð", "start": 1.04, "end": 1.275},
|
||
{"value": "ə", "start": 1.275, "end": 1.36},
|
||
{"value": "k", "start": 1.36, "end": 1.44},
|
||
{"value": "uə", "start": 1.44, "end": 1.5},
|
||
{"value": "ɪ", "start": 1.5, "end": 1.56},
|
||
{"value": "tʰ", "start": 1.56, "end": 1.68},
|
||
{"value": "b̥", "start": 1.68, "end": 1.77},
|
||
{"value": "a", "start": 1.77, "end": 1.845},
|
||
{"value": "l̪", "start": 1.845, "end": 2.0},
|
||
{"value": "f", "start": 2.0, "end": 2.085},
|
||
{"value": "ɑ", "start": 2.085, "end": 2.19},
|
||
{"value": "k", "start": 2.19, "end": 2.31},
|
||
{"value": "s", "start": 2.31, "end": 2.3999999},
|
||
{"value": "d͡ʒ", "start": 2.3999999, "end": 2.505},
|
||
{"value": "ʌ", "start": 2.505, "end": 2.565},
|
||
{"value": "m", "start": 2.565, "end": 2.6100000000000003},
|
||
{"value": "p", "start": 2.6100000000000003, "end": 2.67},
|
||
{"value": "ʂ", "start": 2.67, "end": 2.72},
|
||
{"value": "ə", "start": 2.72, "end": 2.76},
|
||
{"value": "o", "start": 2.76, "end": 2.8049999999999997},
|
||
{"value": "w", "start": 2.8049999999999997, "end": 2.8499999999999996},
|
||
{"value": "v", "start": 2.8499999999999996, "end": 2.895},
|
||
{"value": "ɾ", "start": 2.895, "end": 2.9400000000000004},
|
||
{"value": "u", "start": 2.9400000000000004, "end": 3.04},
|
||
{"value": "ð", "start": 3.04, "end": 3.075},
|
||
{"value": "ə", "start": 3.075, "end": 3.135},
|
||
{"value": "l", "start": 3.135, "end": 3.1999998},
|
||
{"value": "i", "start": 3.1999998, "end": 3.3},
|
||
{"value": "z", "start": 3.3, "end": 3.3899999999999997},
|
||
{"value": "i", "start": 3.3899999999999997, "end": 3.52},
|
||
{"value": "d", "start": 3.52, "end": 3.5700000000000003},
|
||
{"value": "ʌ", "start": 3.5700000000000003, "end": 3.675},
|
||
{"value": "g", "start": 3.675, "end": 4.02},
|
||
{"value": "", "start": 4.02, "end": 4.565},
|
||
], "Expected phonetic transcription result to match the mock return value"
|
||
assert (
|
||
db_mock.fetch_file.call_count == 1
|
||
), "Expected fetch_file to be called once"
|
||
|
||
|
||
def test_phone_transcription_no_words(db_mock, file_state):
|
||
with patch(
|
||
"spectral.transcription.models.deepgram.deepgram_transcription"
|
||
) as mock_deepgram_transcription:
|
||
mock_deepgram_transcription.return_value = []
|
||
response = client.get("/transcription/allosaurus/1")
|
||
assert (
|
||
response.status_code == 200
|
||
), "Expected status code 200 for allosaurus transcription with no words"
|
||
result = response.json()
|
||
assert (
|
||
result
|
||
== [
|
||
{"value": "ð", "start": 0, "end": 1.275},
|
||
{"value": "ə", "start": 1.275, "end": 1.35},
|
||
{"value": "k", "start": 1.35, "end": 1.44},
|
||
{"value": "uə", "start": 1.44, "end": 1.5},
|
||
{"value": "ɪ", "start": 1.5, "end": 1.56},
|
||
{"value": "tʰ", "start": 1.56, "end": 1.665},
|
||
{"value": "b̥", "start": 1.665, "end": 1.77},
|
||
{"value": "a", "start": 1.77, "end": 1.845},
|
||
{"value": "l̪", "start": 1.845, "end": 1.9649999999999999},
|
||
{"value": "f", "start": 1.9649999999999999, "end": 2.085},
|
||
{"value": "ɑ", "start": 2.085, "end": 2.19},
|
||
{"value": "k", "start": 2.19, "end": 2.31},
|
||
{"value": "s", "start": 2.31, "end": 2.415},
|
||
{"value": "d͡ʒ", "start": 2.415, "end": 2.505},
|
||
{"value": "ʌ", "start": 2.505, "end": 2.565},
|
||
{"value": "m", "start": 2.565, "end": 2.6100000000000003},
|
||
{"value": "p", "start": 2.6100000000000003, "end": 2.67},
|
||
{"value": "ʂ", "start": 2.67, "end": 2.715},
|
||
{"value": "ə", "start": 2.715, "end": 2.76},
|
||
{"value": "o", "start": 2.76, "end": 2.8049999999999997},
|
||
{"value": "w", "start": 2.8049999999999997, "end": 2.8499999999999996},
|
||
{"value": "v", "start": 2.8499999999999996, "end": 2.895},
|
||
{"value": "ɾ", "start": 2.895, "end": 2.9400000000000004},
|
||
{"value": "u", "start": 2.9400000000000004, "end": 3.015},
|
||
{"value": "ð", "start": 3.015, "end": 3.075},
|
||
{"value": "ə", "start": 3.075, "end": 3.135},
|
||
{"value": "l", "start": 3.135, "end": 3.21},
|
||
{"value": "i", "start": 3.21, "end": 3.3},
|
||
{"value": "z", "start": 3.3, "end": 3.3899999999999997},
|
||
{"value": "i", "start": 3.3899999999999997, "end": 3.48},
|
||
{"value": "d", "start": 3.48, "end": 3.5700000000000003},
|
||
{"value": "ʌ", "start": 3.5700000000000003, "end": 3.675},
|
||
{"value": "g", "start": 3.675, "end": 4.565},
|
||
]
|
||
), "Expected phonetic transcription result to match the mock return value when no words are provided"
|
||
assert (
|
||
db_mock.fetch_file.call_count == 1
|
||
), "Expected fetch_file to be called once"
|