"""Unit tests for individual analyzers (Essentia mocked, librosa used directly).""" from unittest.mock import MagicMock, patch import numpy as np import pytest from worker.analyzers.base import AnalysisResult from worker.analyzers.chroma import ChromaAnalyzer from worker.analyzers.loudness import LoudnessAnalyzer from worker.analyzers.mfcc import MFCCAnalyzer from worker.analyzers.spectral import SpectralAnalyzer def test_loudness_analyzer_returns_expected_fields(sine_440hz): audio, sr = sine_440hz result = LoudnessAnalyzer().analyze(audio, sr) assert isinstance(result, AnalysisResult) assert result.analyzer_name == "loudness" assert "avg_loudness_lufs" in result.fields assert "peak_loudness_dbfs" in result.fields assert "energy" in result.fields assert result.fields["energy"] is not None assert 0.0 <= result.fields["energy"] <= 1.0 def test_chroma_analyzer_returns_12_dimensions(sine_440hz): audio, sr = sine_440hz result = ChromaAnalyzer().analyze(audio, sr) assert result.analyzer_name == "chroma" chroma = result.fields["chroma_vector"] assert chroma is not None assert len(chroma) == 12 assert all(isinstance(v, float) for v in chroma) def test_mfcc_analyzer_returns_13_dimensions(sine_440hz): audio, sr = sine_440hz result = MFCCAnalyzer().analyze(audio, sr) assert result.analyzer_name == "mfcc" mfcc = result.fields["mfcc_mean"] assert mfcc is not None assert len(mfcc) == 13 def test_spectral_analyzer_returns_centroid(sine_440hz): audio, sr = sine_440hz result = SpectralAnalyzer().analyze(audio, sr) assert "spectral_centroid" in result.fields # 440 Hz tone should have centroid near 440 Hz centroid = result.fields["spectral_centroid"] assert centroid is not None assert 300 < centroid < 600 def test_bpm_analyzer_falls_back_to_librosa_when_essentia_unavailable(sine_440hz): audio, sr = sine_440hz from worker.analyzers.bpm import BPMAnalyzer with patch.dict("sys.modules", {"essentia": None, "essentia.standard": None}): with patch.object( BPMAnalyzer, "_essentia_bpm", side_effect=ImportError("no essentia"), ): result = BPMAnalyzer().analyze(audio, sr) assert result.analyzer_name == "bpm" assert "bpm" in result.fields # librosa result for a sine wave — rough estimate assert result.fields["bpm"] is not None assert result.fields["bpm"] > 0 def test_key_analyzer_returns_none_fields_when_essentia_unavailable(sine_440hz): audio, sr = sine_440hz from worker.analyzers.key import KeyAnalyzer with patch.object(KeyAnalyzer, "analyze", wraps=KeyAnalyzer().analyze): with patch.dict("sys.modules", {"essentia": None, "essentia.standard": None}): with patch( "worker.analyzers.key.__import__", side_effect=ImportError, ): result = KeyAnalyzer().analyze(audio, sr) # When Essentia fails, returns None fields (no crash) assert result.analyzer_name == "key"