phase 5
This commit is contained in:
62
tests/detect/manual/run_graph.py
Normal file
62
tests/detect/manual/run_graph.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run the full LangGraph detection pipeline on a test video.
|
||||
|
||||
Usage:
|
||||
python tests/detect/manual/run_graph.py [--job JOB_ID] [--port PORT]
|
||||
|
||||
Opens: http://mpr.local.ar/detection/?job=<JOB_ID>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--job", default="graph-test")
|
||||
parser.add_argument("--port", type=int, default=6382)
|
||||
args = parser.parse_args()
|
||||
|
||||
os.environ["REDIS_URL"] = f"redis://localhost:{args.port}/0"
|
||||
logging.basicConfig(level=logging.INFO, format="%(levelname)-7s %(name)s — %(message)s")
|
||||
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
from detect.graph import get_pipeline
|
||||
from detect.state import DetectState
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
VIDEO = "media/out/chunks/95043d50-4df6-4ac8-bbd5-2ba873117c6e/chunk_0000.mp4"
|
||||
|
||||
|
||||
def main():
|
||||
logger.info("Job: %s", args.job)
|
||||
logger.info("Open: http://mpr.local.ar/detection/?job=%s", args.job)
|
||||
input("\nPress Enter to start...")
|
||||
|
||||
pipeline = get_pipeline()
|
||||
|
||||
initial_state = DetectState(
|
||||
video_path=VIDEO,
|
||||
job_id=args.job,
|
||||
profile_name="soccer_broadcast",
|
||||
)
|
||||
|
||||
logger.info("Running pipeline...")
|
||||
result = pipeline.invoke(initial_state)
|
||||
|
||||
frames = result.get("frames", [])
|
||||
filtered = result.get("filtered_frames", [])
|
||||
report = result.get("report")
|
||||
|
||||
logger.info("Frames extracted: %d", len(frames))
|
||||
logger.info("Frames after filter: %d", len(filtered))
|
||||
if report:
|
||||
logger.info("Brands found: %d", len(report.brands))
|
||||
logger.info("Done.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
79
tests/detect/test_graph.py
Normal file
79
tests/detect/test_graph.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Tests for the LangGraph detection pipeline."""
|
||||
|
||||
import pytest
|
||||
|
||||
from detect.graph import NODES, build_graph, get_pipeline
|
||||
from detect.models import PipelineStats
|
||||
from detect.state import DetectState
|
||||
|
||||
VIDEO = "media/out/chunks/95043d50-4df6-4ac8-bbd5-2ba873117c6e/chunk_0000.mp4"
|
||||
|
||||
|
||||
def test_graph_compiles():
|
||||
pipeline = get_pipeline()
|
||||
assert pipeline is not None
|
||||
|
||||
|
||||
def test_graph_has_all_nodes():
|
||||
graph = build_graph()
|
||||
for node in NODES:
|
||||
assert node in graph.nodes
|
||||
|
||||
|
||||
def test_graph_runs_end_to_end(monkeypatch):
|
||||
"""Run the full graph with mocked event emission."""
|
||||
events = []
|
||||
monkeypatch.setattr("detect.emit.push_detect_event",
|
||||
lambda job_id, etype, data: events.append((etype, data)))
|
||||
|
||||
pipeline = get_pipeline()
|
||||
initial_state = DetectState(
|
||||
video_path=VIDEO,
|
||||
job_id="test-graph",
|
||||
profile_name="soccer_broadcast",
|
||||
)
|
||||
|
||||
result = pipeline.invoke(initial_state)
|
||||
|
||||
# All nodes should have transitioned
|
||||
graph_events = [e for e in events if e[0] == "graph_update"]
|
||||
assert len(graph_events) > 0
|
||||
|
||||
# Should have frames
|
||||
assert len(result["frames"]) > 0
|
||||
assert len(result["filtered_frames"]) > 0
|
||||
|
||||
# Report should exist
|
||||
assert result["report"] is not None
|
||||
assert result["report"].content_type == "soccer_broadcast"
|
||||
|
||||
# job_complete should have been emitted
|
||||
complete_events = [e for e in events if e[0] == "job_complete"]
|
||||
assert len(complete_events) == 1
|
||||
|
||||
|
||||
def test_graph_node_transitions(monkeypatch):
|
||||
"""Verify each node emits running → done transitions."""
|
||||
events = []
|
||||
monkeypatch.setattr("detect.emit.push_detect_event",
|
||||
lambda job_id, etype, data: events.append((etype, data)))
|
||||
|
||||
pipeline = get_pipeline()
|
||||
initial_state = DetectState(
|
||||
video_path=VIDEO,
|
||||
job_id="test-transitions",
|
||||
profile_name="soccer_broadcast",
|
||||
)
|
||||
|
||||
pipeline.invoke(initial_state)
|
||||
|
||||
graph_events = [e[1] for e in events if e[0] == "graph_update"]
|
||||
|
||||
# Each node should appear as "running" then "done"
|
||||
for node_name in NODES:
|
||||
running = [e for e in graph_events
|
||||
if any(n["id"] == node_name and n["status"] == "running" for n in e["nodes"])]
|
||||
done = [e for e in graph_events
|
||||
if any(n["id"] == node_name and n["status"] == "done" for n in e["nodes"])]
|
||||
assert len(running) >= 1, f"{node_name} never entered 'running'"
|
||||
assert len(done) >= 1, f"{node_name} never reached 'done'"
|
||||
Reference in New Issue
Block a user