Path: blob/master/webhooks/webhook_tester/front_rear_tester.py
1093 views
import argparse1import json2import os3from dataclasses import dataclass4from datetime import datetime, timezone5from typing import Any67import requests89"""10Expected output when running the tester against properly configured middleware:1112--- Scenario 1: Normal pair (front then rear, plate in DB, correct make/model) ---13Response: {"message":"Event buffered, waiting for pair"}14Response: {"message":"Processed camera pair"}1516--- Scenario 2: Solo front camera (plate in DB, correct make/model) ---17Response: {"message":"Processed camera pair"}1819--- Scenario 3: Solo rear camera (plate in DB, correct make/model) ---20Response: {"message":"Processed camera pair"}2122--- Scenario 4: Failed front camera (front fails, rear works, plate in DB, correct make/model) ---23Response: {"message":"Event buffered, waiting for pair"}2425--- Scenario 5: Failed rear camera (front works, rear fails, plate in DB, correct make/model) ---26Response: {"message":"Event buffered, waiting for pair"}2728--- Scenario 6: Overwrite unpaired event (not in DB) ---29Response: {"message":"Event buffered, waiting for pair"}30Response: {"message":"Event buffered, waiting for pair"}3132--- Scenario 7: Plate not in DB ---33Response: {"message":"Event buffered, waiting for pair"}3435--- Scenario 8: Make/model mismatch (plate in DB, wrong make/model) ---36Response: {"message":"Event buffered, waiting for pair"}3738--- Scenario 9: Camera not configured (plate in DB, correct make/model) ---39Response: {"message":"Camera not configured in any pair"}40"""4142CONFIG_PATH = os.path.join(43os.path.dirname(__file__), "../middleware/protocols/config/front_rear_config.json"44)4546# Plates known to be in the DB (from user-provided CSV)47IN_DB_PLATES = [48("34A23126", "TOYOTA", "YARIS"),49("34A36837", "MITSUBISHI", "LANCER"),50("89C20339", "ISUZU", "GIGA"),51]5253DEFAULT_REGION = "us-ca"545556@dataclass57class TestConfig:58"""Test configuration and camera pairs."""5960endpoint: str61token: str62normal_pair: dict[str, str] | None63solo_front: dict[str, str] | None64solo_rear: dict[str, str] | None65failed_front: dict[str, str] | None66failed_rear: dict[str, str] | None67in_db_plate: str68in_db_make: str69in_db_model: str70region: str = DEFAULT_REGION717273def load_config() -> dict[str, Any]:74"""Load middleware configuration from JSON file."""75with open(CONFIG_PATH) as f:76return json.load(f)777879def find_camera_pairs(config: dict[str, Any]) -> dict[str, dict[str, str] | None]:80"""Find and return all configured camera pairs."""81pairs = config.get("camera_pairs", [])8283return {84"normal_pair": next(85(86p87for p in pairs88if p.get("front") == "camera-front" and p.get("rear") == "camera-rear"89),90None,91),92"solo_front": next(93(94p95for p in pairs96if p.get("front") == "camera-solo-front" and not p.get("rear")97),98None,99),100"solo_rear": next(101(102p103for p in pairs104if not p.get("front") and p.get("rear") == "camera-solo-rear"105),106None,107),108"failed_front": next(109(110p111for p in pairs112if p.get("front") == "camera-failed-front"113and p.get("rear") == "camera-working-rear"114),115None,116),117"failed_rear": next(118(119p120for p in pairs121if p.get("front") == "camera-working-front"122and p.get("rear") == "camera-failed-rear"123),124None,125),126}127128129def format_timestamp(dt: datetime) -> str:130"""Format datetime as ISO8601 timestamp string."""131return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ")132133134def build_event(135camera_id: str,136plate: str,137region_code: str,138timestamp: str,139endpoint: str,140make: str = "Porsche",141model: str = "911",142orientation: str = "Front",143) -> dict[str, Any]:144"""Build a front_rear event payload matching stream structure."""145filename = f"{camera_id}_screenshots/image.jpg"146return {147"hook": {148"target": endpoint,149"id": camera_id,150"event": "recognition",151"filename": filename,152},153"data": {154"camera_id": camera_id,155"filename": filename,156"timestamp": timestamp,157"timestamp_local": timestamp,158"timestamp_camera": None,159"results": [160{161"box": {"xmax": 412, "xmin": 337, "ymax": 305, "ymin": 270},162"candidates": [163{"plate": plate, "score": 0.902},164{"plate": "plbrec", "score": 0.758},165],166"color": [167{"color": "red", "score": 0.699},168{"color": "black", "score": 0.134},169{"color": "blue", "score": 0.03},170],171"dscore": 0.757,172"model_make": [{"make": make, "model": model, "score": 0.43}],173"orientation": [174{"orientation": orientation, "score": 0.883},175{176"orientation": "Rear"177if orientation == "Front"178else "Front",179"score": 0.07,180},181{"orientation": "Unknown", "score": 0.047},182],183"plate": plate,184"region": {"code": region_code, "score": 0.179},185"score": 0.902,186"vehicle": {187"box": {"xmax": 590, "xmin": 155, "ymax": 373, "ymin": 71},188"score": 0.709,189"type": "Sedan",190},191"direction": 210,192"source_url": "/user-data/video.mp4",193"position_sec": 23.47,194}195],196},197}198199200def send_event(url: str, token: str, event: dict[str, Any]) -> requests.Response:201"""Send event to middleware endpoint with optional image attachment."""202files = {}203image_path = "./small.jpg"204if os.path.exists(image_path):205files["upload"] = ("small.jpg", open(image_path, "rb"), "image/jpeg")206207data = {"json": json.dumps(event)}208headers = {"Authorization": f"Token {token}"} if token else {}209210try:211resp = requests.post(url, data=data, files=files, headers=headers, timeout=30)212return resp213finally:214if "upload" in files:215files["upload"][1].close()216217218def print_result(219resp: requests.Response,220plate: str | None = None,221make: str | None = None,222model: str | None = None,223camera: str | None = None,224) -> None:225"""Print test result with event metadata."""226if plate or make or model or camera:227print(f"[camera={camera} plate={plate} make={make} model={model}]")228print(f"Status code: {resp.status_code}")229print(f"Response: {resp.text}")230231232def send_and_print(233test_cfg: TestConfig,234camera_id: str,235plate: str,236timestamp: str,237orientation: str,238make: str | None = None,239model: str | None = None,240description: str | None = None,241) -> None:242"""Helper to build, send event, and print result."""243if description:244print(description)245246event = build_event(247camera_id=camera_id,248plate=plate,249region_code=test_cfg.region,250timestamp=timestamp,251endpoint=test_cfg.endpoint,252make=make or "Porsche",253model=model or "911",254orientation=orientation,255)256257resp = send_event(test_cfg.endpoint, test_cfg.token, event)258print_result(resp, plate, make, model, camera_id)259260261def run_scenarios(args: argparse.Namespace, config: dict[str, Any]) -> None:262"""Execute all test scenarios against the middleware."""263now = datetime.now(timezone.utc)264timestamp = format_timestamp(now)265in_db_plate, in_db_make, in_db_model = IN_DB_PLATES[0]266267camera_pairs = find_camera_pairs(config)268test_cfg = TestConfig(269endpoint=args.endpoint,270token=args.token,271in_db_plate=in_db_plate,272in_db_make=in_db_make,273in_db_model=in_db_model,274region=DEFAULT_REGION,275normal_pair=camera_pairs["normal_pair"],276solo_front=camera_pairs["solo_front"],277solo_rear=camera_pairs["solo_rear"],278failed_front=camera_pairs["failed_front"],279failed_rear=camera_pairs["failed_rear"],280)281282# Scenario 1: Normal pair (front then rear, plate in DB, correct make/model)283if test_cfg.normal_pair:284print(285"\n--- Scenario 1: Normal pair (front then rear, plate in DB, correct make/model) ---"286)287print("Sending front event...")288send_and_print(289test_cfg,290test_cfg.normal_pair["front"],291test_cfg.in_db_plate,292timestamp,293"Front",294test_cfg.in_db_make,295test_cfg.in_db_model,296)297print("Sending rear event...")298send_and_print(299test_cfg,300test_cfg.normal_pair["rear"],301test_cfg.in_db_plate,302timestamp,303"Rear",304test_cfg.in_db_make,305test_cfg.in_db_model,306)307308# Scenario 2: Solo front (in DB plate, correct make/model)309if test_cfg.solo_front:310print(311"\n--- Scenario 2: Solo front camera (plate in DB, correct make/model) ---"312)313print("Sending solo front event...")314send_and_print(315test_cfg,316test_cfg.solo_front["front"],317test_cfg.in_db_plate,318timestamp,319"Front",320test_cfg.in_db_make,321test_cfg.in_db_model,322)323324# Scenario 3: Solo rear (in DB plate, correct make/model)325if test_cfg.solo_rear:326print(327"\n--- Scenario 3: Solo rear camera (plate in DB, correct make/model) ---"328)329print("Sending solo rear event...")330send_and_print(331test_cfg,332test_cfg.solo_rear["rear"],333test_cfg.in_db_plate,334timestamp,335"Rear",336test_cfg.in_db_make,337test_cfg.in_db_model,338)339340# Scenario 4: Failed front (in DB plate, correct make/model)341if test_cfg.failed_front:342print(343"\n--- Scenario 4: Failed front camera (front fails, rear works, plate in DB, correct make/model) ---"344)345print("Sending rear event only...")346send_and_print(347test_cfg,348test_cfg.failed_front["rear"],349test_cfg.in_db_plate,350timestamp,351"Rear",352test_cfg.in_db_make,353test_cfg.in_db_model,354)355356# Scenario 5: Failed rear (in DB plate, correct make/model)357if test_cfg.failed_rear:358print(359"\n--- Scenario 5: Failed rear camera (front works, rear fails, plate in DB, correct make/model) ---"360)361print("Sending front event only...")362send_and_print(363test_cfg,364test_cfg.failed_rear["front"],365test_cfg.in_db_plate,366timestamp,367"Front",368test_cfg.in_db_make,369test_cfg.in_db_model,370)371372# Scenario 6: Overwrite event (new plate before pair completes, not in DB)373if test_cfg.normal_pair:374print("\n--- Scenario 6: Overwrite unpaired event (not in DB) ---")375print("Sending front event with OLD123...")376send_and_print(377test_cfg, test_cfg.normal_pair["front"], "OLD123", timestamp, "Front"378)379print("Sending front event with NEW456 (should overwrite)...")380send_and_print(381test_cfg, test_cfg.normal_pair["front"], "NEW456", timestamp, "Front"382)383384# Scenario 7: Plate not in DB385if test_cfg.normal_pair:386print("\n--- Scenario 7: Plate not in DB ---")387print("Sending front event with unknown plate...")388send_and_print(389test_cfg, test_cfg.normal_pair["front"], "NOTINDB", timestamp, "Front"390)391392# Scenario 8: Make/model mismatch (in DB plate, wrong make/model)393if test_cfg.normal_pair:394print(395"\n--- Scenario 8: Make/model mismatch (plate in DB, wrong make/model) ---"396)397print("Sending front event with mismatched make/model...")398send_and_print(399test_cfg,400test_cfg.normal_pair["front"],401test_cfg.in_db_plate,402timestamp,403"Front",404"Toyota",405"Corolla",406)407408# Scenario 9: Camera not configured (in DB plate, correct make/model)409print(410"\n--- Scenario 9: Camera not configured (plate in DB, correct make/model) ---"411)412print("Sending event from unknown camera...")413send_and_print(414test_cfg,415"unknown-camera",416test_cfg.in_db_plate,417timestamp,418"Front",419test_cfg.in_db_make,420test_cfg.in_db_model,421)422423424def parse_args() -> argparse.Namespace:425"""Parse command line arguments."""426parser = argparse.ArgumentParser(427description="Test front-rear middleware event scenarios (config-driven)"428)429parser.add_argument("--endpoint", required=True, help="Webhook endpoint URL")430parser.add_argument("--token", required=True, help="Authorization token (required)")431return parser.parse_args()432433434if __name__ == "__main__":435args = parse_args()436config = load_config()437try:438run_scenarios(args, config)439except KeyboardInterrupt:440print("\nStopping...")441except Exception as e:442print(f"\n--> An error occurred: {e}")443print("--> The test failed.")444445446