File size: 10,436 Bytes
b1939df |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
import pytest
import os
import shutil
import os
import sys
# Add the parent directory to sys.path to find the src module
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.video_processing_tool import VideoProcessingTool
# --- Test Configuration ---
# URLs for testing different functionalities
# Ensure these videos are publicly accessible and have expected features (transcripts, specific objects)
# Using videos from the common_questions.json for relevance
VIDEO_URL_TRANSCRIPT_DIALOGUE = "https://www.youtube.com/watch?v=1htKBjuUWec" # Stargate SG-1 "Isn't that hot?"
VIDEO_URL_OBJECT_COUNT = "https://www.youtube.com/watch?v=L1vXCYZAYYM" # Birds video
VIDEO_URL_NO_TRANSCRIPT = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Rick Astley (often no official transcript)
VIDEO_URL_SHORT_GENERAL = "https://www.youtube.com/watch?v=jNQXAC9IVRw" # Short Creative Commons video (Big Buck Bunny)
# --- Fixtures ---
@pytest.fixture(scope="session")
def model_files():
"""Creates dummy model files for testing CV functionality if real ones aren't provided."""
# Using a sub-directory within the test directory for these files
test_model_dir = os.path.join(os.path.dirname(__file__), "test_cv_models")
os.makedirs(test_model_dir, exist_ok=True)
cfg_path = os.path.join(test_model_dir, "dummy-yolov3-tiny.cfg")
weights_path = os.path.join(test_model_dir, "dummy-yolov3-tiny.weights")
names_path = os.path.join(test_model_dir, "dummy-coco.names")
# Create minimal dummy files if they don't exist
# These won't make OpenCV's DNN module load a functional model but allow testing the file handling logic.
# For actual DNN model loading, valid model files are required.
if not os.path.exists(cfg_path): open(cfg_path, 'w').write("[net]\nwidth=416\nheight=416")
if not os.path.exists(weights_path): open(weights_path, 'wb').write(b'dummyweights')
if not os.path.exists(names_path): open(names_path, 'w').write("bird\ncat\ndog\nperson\n")
yield {
"cfg": cfg_path,
"weights": weights_path,
"names": names_path,
"dir": test_model_dir
}
# Cleanup: remove the dummy model directory after tests
# shutil.rmtree(test_model_dir, ignore_errors=True) # Keep for inspection if tests fail
@pytest.fixture
def video_tool(model_files):
"""Initializes VideoProcessingTool with dummy model paths for testing."""
# Using a specific temp directory for test artifacts
test_temp_dir_base = os.path.join(os.path.dirname(__file__), "test_temp_videos")
os.makedirs(test_temp_dir_base, exist_ok=True)
tool = VideoProcessingTool(
model_cfg_path=model_files["cfg"],
model_weights_path=model_files["weights"],
class_names_path=model_files["names"],
temp_dir_base=test_temp_dir_base
)
yield tool
tool.cleanup() # Ensure temp files for this tool instance are removed
# Optional: Clean up the base test_temp_dir if it's empty or after all tests
# if os.path.exists(test_temp_dir_base) and not os.listdir(test_temp_dir_base):
# shutil.rmtree(test_temp_dir_base)
# --- Test Cases ---
def test_extract_video_id(video_tool):
assert video_tool._extract_video_id("https://www.youtube.com/watch?v=1htKBjuUWec") == "1htKBjuUWec"
assert video_tool._extract_video_id("https://youtu.be/1htKBjuUWec") == "1htKBjuUWec"
assert video_tool._extract_video_id("https://www.youtube.com/embed/1htKBjuUWec") == "1htKBjuUWec"
assert video_tool._extract_video_id("https://www.youtube.com/watch?v=1htKBjuUWec&t=10s") == "1htKBjuUWec"
assert video_tool._extract_video_id("invalid_url") is None
@pytest.mark.integration # Marks as integration test (requires network)
def test_download_video(video_tool):
result = video_tool.download_video(VIDEO_URL_SHORT_GENERAL, resolution="240p")
assert result.get("success"), f"Download failed: {result.get('error')}"
assert "file_path" in result
assert os.path.exists(result["file_path"])
assert result["file_path"].endswith(".mp4") or result["file_path"].startswith(video_tool._extract_video_id(VIDEO_URL_SHORT_GENERAL))
@pytest.mark.integration
def test_get_video_transcript_success(video_tool):
result = video_tool.get_video_transcript(VIDEO_URL_TRANSCRIPT_DIALOGUE)
assert result.get("success"), f"Transcript fetch failed: {result.get('error')}"
assert "transcript" in result and len(result["transcript"]) > 0
assert "transcript_entries" in result and len(result["transcript_entries"]) > 0
# Making the check case-insensitive and more flexible
assert "isn't that hot" in result["transcript"].lower() # Check for expected content (removed ?)
@pytest.mark.integration
def test_get_video_transcript_no_transcript(video_tool):
# This video is unlikely to have official transcripts in many languages
# However, YouTube might auto-generate. The API should handle it gracefully.
result = video_tool.get_video_transcript(VIDEO_URL_NO_TRANSCRIPT, languages=['xx-YY']) # Non-existent language
assert not result.get("success")
assert "error" in result
assert "No transcript found" in result["error"] or "Transcripts are disabled" in result["error"]
@pytest.mark.integration
def test_find_dialogue_response_success(video_tool):
transcript_data = video_tool.get_video_transcript(VIDEO_URL_TRANSCRIPT_DIALOGUE)
assert transcript_data.get("success"), f"Transcript fetch failed for dialogue test: {transcript_data.get('error')}"
result = video_tool.find_dialogue_response(transcript_data["transcript_entries"], "Isn't that hot?")
assert result.get("success"), f"Dialogue search failed: {result.get('error')}"
assert "response_text" in result
# The expected response is "Extremely" but can vary slightly with transcript generation
assert "Extremely".lower() in result["response_text"].lower()
@pytest.mark.integration
def test_find_dialogue_response_not_found(video_tool):
transcript_data = video_tool.get_video_transcript(VIDEO_URL_TRANSCRIPT_DIALOGUE)
assert transcript_data.get("success")
result = video_tool.find_dialogue_response(transcript_data["transcript_entries"], "This phrase is not in the video")
assert not result.get("success")
assert "not found in transcript" in result.get("error", "")
@pytest.mark.integration
@pytest.mark.cv_dependent # Marks tests that rely on (even dummy) CV model setup
def test_object_counting_interface(video_tool):
"""Tests the object counting call, expecting it to run with dummy models even if counts are zero."""
if not video_tool.object_detection_model:
pytest.skip("CV model not loaded, skipping object count test.")
download_result = video_tool.download_video(VIDEO_URL_OBJECT_COUNT, resolution="240p") # Use a short video
assert download_result.get("success"), f"Video download failed for object counting: {download_result.get('error')}"
video_path = download_result["file_path"]
result = video_tool.count_objects_in_video(video_path, target_classes=["bird"], confidence_threshold=0.1, frame_skip=30)
# With dummy models, we don't expect actual detections, but the function should complete.
assert result.get("success"), f"Object counting failed: {result.get('error')}"
assert "max_simultaneous_bird" in result # Even if it's 0
# If using real models and a video with birds, you would assert result["max_simultaneous_bird"] > 0
@pytest.mark.integration
@pytest.mark.cv_dependent
def test_process_video_object_count_flow(video_tool):
if not video_tool.object_detection_model:
pytest.skip("CV model not loaded, skipping process_video object count test.")
query_params = {
"target_classes": ["bird"],
"resolution": "240p",
"confidence_threshold": 0.1,
"frame_skip": 30 # Process fewer frames for faster test
}
result = video_tool.process_video(VIDEO_URL_OBJECT_COUNT, "object_count", query_params=query_params)
assert result.get("success"), f"process_video for object_count failed: {result.get('error')}"
assert "max_simultaneous_bird" in result
@pytest.mark.integration
def test_process_video_dialogue_flow(video_tool):
query_params = {"query_phrase": "Isn't that hot?"}
result = video_tool.process_video(VIDEO_URL_TRANSCRIPT_DIALOGUE, "dialogue_response", query_params=query_params)
assert result.get("success"), f"process_video for dialogue_response failed: {result.get('error')}"
assert "extremely" in result.get("response_text", "").lower()
@pytest.mark.integration
def test_process_video_transcript_flow(video_tool):
result = video_tool.process_video(VIDEO_URL_TRANSCRIPT_DIALOGUE, "transcript")
assert result.get("success"), f"process_video for transcript failed: {result.get('error')}"
assert "transcript" in result and len(result["transcript"]) > 0
def test_cleanup_removes_temp_dir(model_files): # Test cleanup more directly
test_temp_dir_base = os.path.join(os.path.dirname(__file__), "test_temp_cleanup")
os.makedirs(test_temp_dir_base, exist_ok=True)
tool = VideoProcessingTool(
model_cfg_path=model_files["cfg"],
model_weights_path=model_files["weights"],
class_names_path=model_files["names"],
temp_dir_base=test_temp_dir_base
)
# Create a dummy file in its temp dir
temp_file_in_tool_dir = os.path.join(tool.temp_dir, "dummy.txt")
open(temp_file_in_tool_dir, 'w').write("test")
assert os.path.exists(tool.temp_dir)
assert os.path.exists(temp_file_in_tool_dir)
tool_temp_dir_path = tool.temp_dir # Store path before cleanup
tool.cleanup()
assert not os.path.exists(tool_temp_dir_path), f"Temp directory {tool_temp_dir_path} was not removed."
# shutil.rmtree(test_temp_dir_base, ignore_errors=True) # Clean up the base for this specific test
# To run these tests:
# 1. Ensure you have pytest installed (`pip install pytest`).
# 2. Ensure required libraries for VideoProcessingTool are installed (yt_dlp, youtube_transcript_api, opencv-python).
# 3. Navigate to the directory containing this test file and `src` directory.
# 4. Run `pytest` or `python -m pytest` in your terminal.
# 5. For tests requiring network (integration), ensure you have an internet connection.
# 6. For CV dependent tests to be meaningful beyond interface checks, replace dummy model files with actual ones.
|