Refactor: PHASE 8: Testing & Integration

This commit is contained in:
ziesorx 2025-09-12 18:55:23 +07:00
parent af34f4fd08
commit 9e8c6804a7
32 changed files with 17128 additions and 0 deletions

View file

@ -0,0 +1,959 @@
"""
Unit tests for action execution functionality.
"""
import pytest
import asyncio
import json
import base64
import numpy as np
from unittest.mock import Mock, MagicMock, patch, AsyncMock
from datetime import datetime, timedelta
from detector_worker.pipeline.action_executor import (
ActionExecutor,
ActionResult,
ActionType,
RedisAction,
PostgreSQLAction,
FileAction
)
from detector_worker.detection.detection_result import DetectionResult, BoundingBox
from detector_worker.core.exceptions import ActionError, RedisError, DatabaseError
class TestActionResult:
"""Test action execution result."""
def test_creation_success(self):
"""Test successful action result creation."""
result = ActionResult(
action_type=ActionType.REDIS_SAVE,
success=True,
execution_time=0.05,
metadata={"key": "saved_image_key", "expiry": 600}
)
assert result.action_type == ActionType.REDIS_SAVE
assert result.success is True
assert result.execution_time == 0.05
assert result.metadata["key"] == "saved_image_key"
assert result.error is None
def test_creation_failure(self):
"""Test failed action result creation."""
result = ActionResult(
action_type=ActionType.POSTGRESQL_INSERT,
success=False,
error="Database connection failed",
execution_time=0.02
)
assert result.action_type == ActionType.POSTGRESQL_INSERT
assert result.success is False
assert result.error == "Database connection failed"
assert result.metadata == {}
class TestRedisAction:
"""Test Redis action implementations."""
def test_creation(self):
"""Test Redis action creation."""
action_config = {
"type": "redis_save_image",
"region": "car",
"key": "inference:{display_id}:{timestamp}:{session_id}",
"expire_seconds": 600
}
action = RedisAction(action_config)
assert action.action_type == ActionType.REDIS_SAVE
assert action.region == "car"
assert action.key_template == "inference:{display_id}:{timestamp}:{session_id}"
assert action.expire_seconds == 600
def test_resolve_key_template(self):
"""Test key template resolution."""
action_config = {
"type": "redis_save_image",
"region": "car",
"key": "inference:{display_id}:{timestamp}:{session_id}:{filename}",
"expire_seconds": 600
}
action = RedisAction(action_config)
context = {
"display_id": "display_001",
"timestamp": "1640995200000",
"session_id": "session_123",
"filename": "detection.jpg"
}
resolved_key = action.resolve_key(context)
expected_key = "inference:display_001:1640995200000:session_123:detection.jpg"
assert resolved_key == expected_key
def test_resolve_key_missing_variable(self):
"""Test key resolution with missing variable."""
action_config = {
"type": "redis_save_image",
"region": "car",
"key": "inference:{display_id}:{missing_var}",
"expire_seconds": 600
}
action = RedisAction(action_config)
context = {"display_id": "display_001"}
with pytest.raises(ActionError):
action.resolve_key(context)
class TestPostgreSQLAction:
"""Test PostgreSQL action implementations."""
def test_creation_insert(self):
"""Test PostgreSQL insert action creation."""
action_config = {
"type": "postgresql_insert",
"table": "detections",
"fields": {
"camera_id": "{camera_id}",
"session_id": "{session_id}",
"detection_class": "{class}",
"confidence": "{confidence}",
"bbox_x1": "{bbox.x1}",
"created_at": "NOW()"
}
}
action = PostgreSQLAction(action_config)
assert action.action_type == ActionType.POSTGRESQL_INSERT
assert action.table == "detections"
assert len(action.fields) == 6
assert action.key_field is None
def test_creation_update(self):
"""Test PostgreSQL update action creation."""
action_config = {
"type": "postgresql_update_combined",
"table": "car_info",
"key_field": "session_id",
"fields": {
"car_brand": "{car_brand_cls.brand}",
"car_body_type": "{car_bodytype_cls.body_type}",
"updated_at": "NOW()"
},
"waitForBranches": ["car_brand_cls", "car_bodytype_cls"]
}
action = PostgreSQLAction(action_config)
assert action.action_type == ActionType.POSTGRESQL_UPDATE
assert action.table == "car_info"
assert action.key_field == "session_id"
assert action.wait_for_branches == ["car_brand_cls", "car_bodytype_cls"]
def test_resolve_field_values(self):
"""Test field value resolution."""
action_config = {
"type": "postgresql_insert",
"table": "detections",
"fields": {
"camera_id": "{camera_id}",
"detection_class": "{class}",
"confidence": "{confidence}",
"brand": "{car_brand_cls.brand}"
}
}
action = PostgreSQLAction(action_config)
context = {
"camera_id": "camera_001",
"class": "car",
"confidence": 0.85
}
branch_results = {
"car_brand_cls": {"brand": "Toyota", "confidence": 0.78}
}
resolved_fields = action.resolve_field_values(context, branch_results)
assert resolved_fields["camera_id"] == "camera_001"
assert resolved_fields["detection_class"] == "car"
assert resolved_fields["confidence"] == 0.85
assert resolved_fields["brand"] == "Toyota"
class TestFileAction:
"""Test file action implementations."""
def test_creation(self):
"""Test file action creation."""
action_config = {
"type": "save_image",
"path": "/tmp/detections/{camera_id}_{timestamp}.jpg",
"region": "car",
"format": "jpeg",
"quality": 85
}
action = FileAction(action_config)
assert action.action_type == ActionType.SAVE_IMAGE
assert action.path_template == "/tmp/detections/{camera_id}_{timestamp}.jpg"
assert action.region == "car"
assert action.format == "jpeg"
assert action.quality == 85
def test_resolve_path_template(self):
"""Test path template resolution."""
action_config = {
"type": "save_image",
"path": "/tmp/detections/{camera_id}/{date}/{timestamp}.jpg"
}
action = FileAction(action_config)
context = {
"camera_id": "camera_001",
"timestamp": "1640995200000",
"date": "2022-01-01"
}
resolved_path = action.resolve_path(context)
expected_path = "/tmp/detections/camera_001/2022-01-01/1640995200000.jpg"
assert resolved_path == expected_path
class TestActionExecutor:
"""Test action execution functionality."""
def test_initialization(self):
"""Test action executor initialization."""
executor = ActionExecutor()
assert executor.redis_client is None
assert executor.db_manager is None
assert executor.max_concurrent_actions == 10
assert executor.action_timeout == 30.0
def test_initialization_with_clients(self, mock_redis_client, mock_database_connection):
"""Test initialization with client instances."""
executor = ActionExecutor(
redis_client=mock_redis_client,
db_manager=mock_database_connection
)
assert executor.redis_client is mock_redis_client
assert executor.db_manager is mock_database_connection
@pytest.mark.asyncio
async def test_execute_actions_empty_list(self):
"""Test executing empty action list."""
executor = ActionExecutor()
context = {
"camera_id": "camera_001",
"session_id": "session_123"
}
results = await executor.execute_actions([], {}, context)
assert results == []
@pytest.mark.asyncio
async def test_execute_redis_save_action(self, mock_redis_client, mock_frame):
"""Test executing Redis save image action."""
executor = ActionExecutor(redis_client=mock_redis_client)
actions = [
{
"type": "redis_save_image",
"region": "car",
"key": "inference:{camera_id}:{session_id}",
"expire_seconds": 600
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"session_id": "session_123",
"frame_data": mock_frame
}
# Mock successful Redis operations
mock_redis_client.set.return_value = True
mock_redis_client.expire.return_value = True
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.REDIS_SAVE
# Verify Redis calls
mock_redis_client.set.assert_called_once()
mock_redis_client.expire.assert_called_once()
@pytest.mark.asyncio
async def test_execute_postgresql_insert_action(self, mock_database_connection):
"""Test executing PostgreSQL insert action."""
# Mock database manager
mock_db_manager = Mock()
mock_db_manager.execute_query = AsyncMock(return_value=True)
executor = ActionExecutor(db_manager=mock_db_manager)
actions = [
{
"type": "postgresql_insert",
"table": "detections",
"fields": {
"camera_id": "{camera_id}",
"session_id": "{session_id}",
"detection_class": "{class}",
"confidence": "{confidence}",
"created_at": "NOW()"
}
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"session_id": "session_123",
"class": "car",
"confidence": 0.9
}
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.POSTGRESQL_INSERT
# Verify database call
mock_db_manager.execute_query.assert_called_once()
call_args = mock_db_manager.execute_query.call_args[0]
assert "INSERT INTO detections" in call_args[0]
@pytest.mark.asyncio
async def test_execute_postgresql_update_action(self, mock_database_connection):
"""Test executing PostgreSQL update action."""
mock_db_manager = Mock()
mock_db_manager.execute_query = AsyncMock(return_value=True)
executor = ActionExecutor(db_manager=mock_db_manager)
actions = [
{
"type": "postgresql_update_combined",
"table": "car_info",
"key_field": "session_id",
"fields": {
"car_brand": "{car_brand_cls.brand}",
"car_body_type": "{car_bodytype_cls.body_type}",
"updated_at": "NOW()"
},
"waitForBranches": ["car_brand_cls", "car_bodytype_cls"]
}
]
regions = {}
context = {
"session_id": "session_123"
}
branch_results = {
"car_brand_cls": {"brand": "Toyota"},
"car_bodytype_cls": {"body_type": "Sedan"}
}
results = await executor.execute_actions(actions, regions, context, branch_results)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.POSTGRESQL_UPDATE
# Verify database call
mock_db_manager.execute_query.assert_called_once()
call_args = mock_db_manager.execute_query.call_args[0]
assert "UPDATE car_info SET" in call_args[0]
assert "WHERE session_id" in call_args[0]
@pytest.mark.asyncio
async def test_execute_file_save_action(self, mock_frame):
"""Test executing file save action."""
executor = ActionExecutor()
actions = [
{
"type": "save_image",
"path": "/tmp/test_{camera_id}_{timestamp}.jpg",
"region": "car",
"format": "jpeg",
"quality": 85
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"timestamp": "1640995200000",
"frame_data": mock_frame
}
with patch('cv2.imwrite') as mock_imwrite:
mock_imwrite.return_value = True
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.SAVE_IMAGE
# Verify file save call
mock_imwrite.assert_called_once()
call_args = mock_imwrite.call_args
assert "/tmp/test_camera_001_1640995200000.jpg" in call_args[0][0]
@pytest.mark.asyncio
async def test_execute_actions_parallel(self, mock_redis_client):
"""Test parallel execution of multiple actions."""
executor = ActionExecutor(redis_client=mock_redis_client)
# Multiple Redis actions
actions = [
{
"type": "redis_save_image",
"region": "car",
"key": "inference:car:{session_id}",
"expire_seconds": 600
},
{
"type": "redis_publish",
"channel": "detections",
"message": "{camera_id}:car_detected"
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"session_id": "session_123",
"frame_data": np.zeros((480, 640, 3), dtype=np.uint8)
}
# Mock Redis operations
mock_redis_client.set.return_value = True
mock_redis_client.expire.return_value = True
mock_redis_client.publish.return_value = 1
import time
start_time = time.time()
results = await executor.execute_actions(actions, regions, context)
execution_time = time.time() - start_time
assert len(results) == 2
assert all(result.success for result in results)
# Should execute in parallel (faster than sequential)
assert execution_time < 0.1 # Allow some overhead
@pytest.mark.asyncio
async def test_execute_actions_error_handling(self, mock_redis_client):
"""Test error handling in action execution."""
executor = ActionExecutor(redis_client=mock_redis_client)
actions = [
{
"type": "redis_save_image",
"region": "car",
"key": "inference:{session_id}",
"expire_seconds": 600
},
{
"type": "redis_save_image", # This one will fail
"region": "truck", # Region not detected
"key": "inference:truck:{session_id}",
"expire_seconds": 600
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
# No truck region
}
context = {
"session_id": "session_123",
"frame_data": np.zeros((480, 640, 3), dtype=np.uint8)
}
# Mock Redis operations
mock_redis_client.set.return_value = True
mock_redis_client.expire.return_value = True
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 2
assert results[0].success is True # Car action succeeds
assert results[1].success is False # Truck action fails
assert "Region 'truck' not found" in results[1].error
@pytest.mark.asyncio
async def test_execute_actions_timeout(self, mock_redis_client):
"""Test action execution timeout."""
config = {"action_timeout": 0.001} # Very short timeout
executor = ActionExecutor(redis_client=mock_redis_client, config=config)
def slow_redis_operation(*args, **kwargs):
import time
time.sleep(1) # Longer than timeout
return True
mock_redis_client.set.side_effect = slow_redis_operation
actions = [
{
"type": "redis_save_image",
"region": "car",
"key": "inference:{session_id}",
"expire_seconds": 600
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"session_id": "session_123",
"frame_data": np.zeros((480, 640, 3), dtype=np.uint8)
}
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is False
assert "timeout" in results[0].error.lower()
@pytest.mark.asyncio
async def test_execute_redis_publish_action(self, mock_redis_client):
"""Test executing Redis publish action."""
executor = ActionExecutor(redis_client=mock_redis_client)
actions = [
{
"type": "redis_publish",
"channel": "detections:{camera_id}",
"message": {
"camera_id": "{camera_id}",
"detection_class": "{class}",
"confidence": "{confidence}",
"timestamp": "{timestamp}"
}
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.9,
"detection": DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"class": "car",
"confidence": 0.9,
"timestamp": "1640995200000"
}
mock_redis_client.publish.return_value = 1
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.REDIS_PUBLISH
# Verify publish call
mock_redis_client.publish.assert_called_once()
call_args = mock_redis_client.publish.call_args
assert call_args[0][0] == "detections:camera_001" # Channel
# Message should be JSON
message = call_args[0][1]
parsed_message = json.loads(message)
assert parsed_message["camera_id"] == "camera_001"
assert parsed_message["detection_class"] == "car"
@pytest.mark.asyncio
async def test_execute_conditional_action(self):
"""Test executing conditional actions."""
executor = ActionExecutor()
actions = [
{
"type": "conditional",
"condition": "{confidence} > 0.8",
"actions": [
{
"type": "log",
"message": "High confidence detection: {class} ({confidence})"
}
]
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.95, # High confidence
"detection": DetectionResult("car", 0.95, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"class": "car",
"confidence": 0.95
}
with patch('logging.info') as mock_log:
results = await executor.execute_actions(actions, regions, context)
assert len(results) == 1
assert results[0].success is True
# Should have logged the message
mock_log.assert_called_once()
log_message = mock_log.call_args[0][0]
assert "High confidence detection: car (0.95)" in log_message
def test_crop_region_from_frame(self, mock_frame):
"""Test cropping region from frame."""
executor = ActionExecutor()
detection = DetectionResult("car", 0.9, BoundingBox(100, 200, 300, 400), 1001)
cropped = executor._crop_region_from_frame(mock_frame, detection.bbox)
assert cropped.shape == (200, 200, 3) # 400-200, 300-100
def test_encode_image_base64(self, mock_frame):
"""Test encoding image to base64."""
executor = ActionExecutor()
# Crop a small region
cropped_frame = mock_frame[200:400, 100:300] # 200x200 region
with patch('cv2.imencode') as mock_imencode:
# Mock successful encoding
mock_imencode.return_value = (True, np.array([1, 2, 3, 4], dtype=np.uint8))
encoded = executor._encode_image_base64(cropped_frame, format="jpeg")
# Should return base64 string
assert isinstance(encoded, str)
assert len(encoded) > 0
# Verify encoding call
mock_imencode.assert_called_once()
assert mock_imencode.call_args[0][0] == '.jpg'
def test_build_insert_query(self):
"""Test building INSERT SQL query."""
executor = ActionExecutor()
table = "detections"
fields = {
"camera_id": "camera_001",
"detection_class": "car",
"confidence": 0.9,
"created_at": "NOW()"
}
query, values = executor._build_insert_query(table, fields)
assert "INSERT INTO detections" in query
assert "camera_id, detection_class, confidence, created_at" in query
assert "VALUES (%s, %s, %s, NOW())" in query
assert values == ["camera_001", "car", 0.9]
def test_build_update_query(self):
"""Test building UPDATE SQL query."""
executor = ActionExecutor()
table = "car_info"
fields = {
"car_brand": "Toyota",
"car_body_type": "Sedan",
"updated_at": "NOW()"
}
key_field = "session_id"
key_value = "session_123"
query, values = executor._build_update_query(table, fields, key_field, key_value)
assert "UPDATE car_info SET" in query
assert "car_brand = %s" in query
assert "car_body_type = %s" in query
assert "updated_at = NOW()" in query
assert "WHERE session_id = %s" in query
assert values == ["Toyota", "Sedan", "session_123"]
def test_evaluate_condition(self):
"""Test evaluating conditional expressions."""
executor = ActionExecutor()
context = {
"confidence": 0.85,
"class": "car",
"area": 40000
}
# Simple comparisons
assert executor._evaluate_condition("{confidence} > 0.8", context) is True
assert executor._evaluate_condition("{confidence} < 0.8", context) is False
assert executor._evaluate_condition("{confidence} >= 0.85", context) is True
assert executor._evaluate_condition("{confidence} == 0.85", context) is True
# String comparisons
assert executor._evaluate_condition("{class} == 'car'", context) is True
assert executor._evaluate_condition("{class} != 'truck'", context) is True
# Complex conditions
assert executor._evaluate_condition("{confidence} > 0.8 and {area} > 30000", context) is True
assert executor._evaluate_condition("{confidence} > 0.9 or {area} > 30000", context) is True
assert executor._evaluate_condition("{confidence} > 0.9 and {area} < 30000", context) is False
def test_validate_action_config(self):
"""Test action configuration validation."""
executor = ActionExecutor()
# Valid Redis action
valid_redis = {
"type": "redis_save_image",
"region": "car",
"key": "inference:{session_id}",
"expire_seconds": 600
}
assert executor._validate_action_config(valid_redis) is True
# Invalid action (missing required fields)
invalid_action = {
"type": "redis_save_image"
# Missing region and key
}
with pytest.raises(ActionError):
executor._validate_action_config(invalid_action)
# Unknown action type
unknown_action = {
"type": "unknown_action_type",
"some_field": "value"
}
with pytest.raises(ActionError):
executor._validate_action_config(unknown_action)
class TestActionExecutorIntegration:
"""Integration tests for action execution."""
@pytest.mark.asyncio
async def test_complete_detection_workflow(self, mock_redis_client, mock_frame):
"""Test complete detection workflow with multiple actions."""
# Mock database manager
mock_db_manager = Mock()
mock_db_manager.execute_query = AsyncMock(return_value=True)
executor = ActionExecutor(
redis_client=mock_redis_client,
db_manager=mock_db_manager
)
# Complete action workflow
actions = [
# Save cropped image to Redis
{
"type": "redis_save_image",
"region": "car",
"key": "inference:{camera_id}:{timestamp}:{session_id}:car",
"expire_seconds": 600
},
# Insert initial detection record
{
"type": "postgresql_insert",
"table": "car_detections",
"fields": {
"camera_id": "{camera_id}",
"session_id": "{session_id}",
"detection_class": "{class}",
"confidence": "{confidence}",
"bbox_x1": "{bbox.x1}",
"bbox_y1": "{bbox.y1}",
"bbox_x2": "{bbox.x2}",
"bbox_y2": "{bbox.y2}",
"created_at": "NOW()"
}
},
# Publish detection event
{
"type": "redis_publish",
"channel": "detections:{camera_id}",
"message": {
"event": "car_detected",
"camera_id": "{camera_id}",
"session_id": "{session_id}",
"timestamp": "{timestamp}"
}
}
]
regions = {
"car": {
"bbox": [100, 200, 300, 400],
"confidence": 0.92,
"detection": DetectionResult("car", 0.92, BoundingBox(100, 200, 300, 400), 1001)
}
}
context = {
"camera_id": "camera_001",
"session_id": "session_123",
"timestamp": "1640995200000",
"class": "car",
"confidence": 0.92,
"bbox": {"x1": 100, "y1": 200, "x2": 300, "y2": 400},
"frame_data": mock_frame
}
# Mock all Redis operations
mock_redis_client.set.return_value = True
mock_redis_client.expire.return_value = True
mock_redis_client.publish.return_value = 1
results = await executor.execute_actions(actions, regions, context)
# All actions should succeed
assert len(results) == 3
assert all(result.success for result in results)
# Verify all operations were called
mock_redis_client.set.assert_called_once() # Image save
mock_redis_client.expire.assert_called_once() # Set expiry
mock_redis_client.publish.assert_called_once() # Publish event
mock_db_manager.execute_query.assert_called_once() # Database insert
@pytest.mark.asyncio
async def test_branch_dependent_actions(self, mock_database_connection):
"""Test actions that depend on branch results."""
mock_db_manager = Mock()
mock_db_manager.execute_query = AsyncMock(return_value=True)
executor = ActionExecutor(db_manager=mock_db_manager)
# Action that waits for classification branches
actions = [
{
"type": "postgresql_update_combined",
"table": "car_info",
"key_field": "session_id",
"fields": {
"car_brand": "{car_brand_cls.brand}",
"car_body_type": "{car_bodytype_cls.body_type}",
"car_color": "{car_color_cls.color}",
"confidence_brand": "{car_brand_cls.confidence}",
"confidence_bodytype": "{car_bodytype_cls.confidence}",
"updated_at": "NOW()"
},
"waitForBranches": ["car_brand_cls", "car_bodytype_cls", "car_color_cls"]
}
]
regions = {}
context = {
"session_id": "session_123"
}
# Simulated branch results
branch_results = {
"car_brand_cls": {"brand": "Toyota", "confidence": 0.87},
"car_bodytype_cls": {"body_type": "Sedan", "confidence": 0.82},
"car_color_cls": {"color": "Red", "confidence": 0.79}
}
results = await executor.execute_actions(actions, regions, context, branch_results)
assert len(results) == 1
assert results[0].success is True
assert results[0].action_type == ActionType.POSTGRESQL_UPDATE
# Verify database call with all branch data
mock_db_manager.execute_query.assert_called_once()
call_args = mock_db_manager.execute_query.call_args
query = call_args[0][0]
values = call_args[0][1]
assert "UPDATE car_info SET" in query
assert "car_brand = %s" in query
assert "car_body_type = %s" in query
assert "car_color = %s" in query
assert "WHERE session_id = %s" in query
assert "Toyota" in values
assert "Sedan" in values
assert "Red" in values
assert "session_123" in values