BE: test fixes

Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
jokob-sk
2025-11-21 05:43:30 +11:00
parent 5f0b670a82
commit 8503cb86f1
2 changed files with 223 additions and 573 deletions
+92 -303
View File
@@ -1,56 +1,38 @@
#!/usr/bin/env python3
"""
NetAlertX SQL Injection Fix - Integration Testing
Validates the complete implementation as requested by maintainer jokob-sk
"""
import sys
import os import os
import sqlite3 import sqlite3
import json
import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile import tempfile
import subprocess import pytest
from unittest.mock import Mock, patch
# Add server paths # Add server paths
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server', 'db')) sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'server', 'db'))
# Import our modules
from db.sql_safe_builder import SafeConditionBuilder, create_safe_condition_builder from db.sql_safe_builder import SafeConditionBuilder, create_safe_condition_builder
from messaging.reporting import get_notifications from messaging.reporting import get_notifications
class NetAlertXIntegrationTest(unittest.TestCase): # -----------------------------
""" # Fixtures
Comprehensive integration tests to validate: # -----------------------------
1. Fresh install compatibility @pytest.fixture
2. Existing DB/config compatibility def test_db_path():
3. Notification system integration path = tempfile.mktemp(suffix=".db")
4. Settings persistence yield path
5. Device operations if os.path.exists(path):
6. Plugin functionality os.remove(path)
7. Error handling
"""
def setUp(self): @pytest.fixture
"""Set up test environment""" def builder():
self.test_db_path = tempfile.mktemp(suffix='.db') return create_safe_condition_builder()
self.builder = SafeConditionBuilder()
self.create_test_database()
def tearDown(self): @pytest.fixture
"""Clean up test environment""" def test_db(test_db_path):
if os.path.exists(self.test_db_path): conn = sqlite3.connect(test_db_path)
os.remove(self.test_db_path) cur = conn.cursor()
def create_test_database(self): # Minimal schema for integration testing
"""Create test database with NetAlertX schema""" cur.execute('''
conn = sqlite3.connect(self.test_db_path)
cursor = conn.cursor()
# Create minimal schema for testing
cursor.execute('''
CREATE TABLE IF NOT EXISTS Events_Devices ( CREATE TABLE IF NOT EXISTS Events_Devices (
eve_MAC TEXT, eve_MAC TEXT,
eve_DateTime TEXT, eve_DateTime TEXT,
@@ -62,7 +44,7 @@ class NetAlertXIntegrationTest(unittest.TestCase):
) )
''') ''')
cursor.execute(''' cur.execute('''
CREATE TABLE IF NOT EXISTS Devices ( CREATE TABLE IF NOT EXISTS Devices (
devMac TEXT PRIMARY KEY, devMac TEXT PRIMARY KEY,
devName TEXT, devName TEXT,
@@ -72,7 +54,7 @@ class NetAlertXIntegrationTest(unittest.TestCase):
) )
''') ''')
cursor.execute(''' cur.execute('''
CREATE TABLE IF NOT EXISTS Events ( CREATE TABLE IF NOT EXISTS Events (
eve_MAC TEXT, eve_MAC TEXT,
eve_DateTime TEXT, eve_DateTime TEXT,
@@ -81,7 +63,7 @@ class NetAlertXIntegrationTest(unittest.TestCase):
) )
''') ''')
cursor.execute(''' cur.execute('''
CREATE TABLE IF NOT EXISTS Plugins_Events ( CREATE TABLE IF NOT EXISTS Plugins_Events (
Plugin TEXT, Plugin TEXT,
Object_PrimaryId TEXT, Object_PrimaryId TEXT,
@@ -101,207 +83,111 @@ class NetAlertXIntegrationTest(unittest.TestCase):
('11:22:33:44:55:66', '2024-01-01 12:01:00', '192.168.1.101', 'Connected', 'Test Device 2', 'Another Comment', 1), ('11:22:33:44:55:66', '2024-01-01 12:01:00', '192.168.1.101', 'Connected', 'Test Device 2', 'Another Comment', 1),
('77:88:99:aa:bb:cc', '2024-01-01 12:02:00', '192.168.1.102', 'Disconnected', 'Test Device 3', 'Third Comment', 1), ('77:88:99:aa:bb:cc', '2024-01-01 12:02:00', '192.168.1.102', 'Disconnected', 'Test Device 3', 'Third Comment', 1),
] ]
cur.executemany('''
cursor.executemany('''
INSERT INTO Events_Devices (eve_MAC, eve_DateTime, devLastIP, eve_EventType, devName, devComments, eve_PendingAlertEmail) INSERT INTO Events_Devices (eve_MAC, eve_DateTime, devLastIP, eve_EventType, devName, devComments, eve_PendingAlertEmail)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
''', test_data) ''', test_data)
conn.commit() conn.commit()
conn.close() conn.close()
return test_db_path
def test_1_fresh_install_compatibility(self): # -----------------------------
"""Test 1: Fresh install (no DB/config)""" # Tests
print("\n=== TEST 1: Fresh Install Compatibility ===") # -----------------------------
# Test SafeConditionBuilder initialization def test_fresh_install_compatibility(builder):
builder = create_safe_condition_builder()
self.assertIsInstance(builder, SafeConditionBuilder)
# Test empty condition handling
condition, params = builder.get_safe_condition_legacy("") condition, params = builder.get_safe_condition_legacy("")
self.assertEqual(condition, "") assert condition == ""
self.assertEqual(params, {}) assert params == {}
# Test basic valid condition
condition, params = builder.get_safe_condition_legacy("AND devName = 'TestDevice'") condition, params = builder.get_safe_condition_legacy("AND devName = 'TestDevice'")
self.assertIn("devName = :", condition) assert "devName = :" in condition
self.assertIn('TestDevice', list(params.values())) assert 'TestDevice' in params.values()
print("✅ Fresh install compatibility: PASSED") def test_existing_db_compatibility():
def test_2_existing_db_compatibility(self):
"""Test 2: Existing DB/config compatibility"""
print("\n=== TEST 2: Existing DB/Config Compatibility ===")
# Mock database connection
mock_db = Mock() mock_db = Mock()
mock_sql = Mock()
mock_db.sql = mock_sql
mock_db.get_table_as_json = Mock()
# Mock return value for get_table_as_json
mock_result = Mock() mock_result = Mock()
mock_result.columnNames = ['MAC', 'Datetime', 'IP', 'Event Type', 'Device name', 'Comments'] mock_result.columnNames = ['MAC', 'Datetime', 'IP', 'Event Type', 'Device name', 'Comments']
mock_result.json = {'data': []} mock_result.json = {'data': []}
mock_db.get_table_as_json.return_value = mock_result mock_db.get_table_as_json.return_value = mock_result
# Mock settings with patch('messaging.reporting.get_setting_value') as s:
with patch('messaging.reporting.get_setting_value') as mock_settings: s.side_effect = lambda k: {
mock_settings.side_effect = lambda key: {
'NTFPRCS_INCLUDED_SECTIONS': ['new_devices', 'events'], 'NTFPRCS_INCLUDED_SECTIONS': ['new_devices', 'events'],
'NTFPRCS_new_dev_condition': "AND devName = 'TestDevice'", 'NTFPRCS_new_dev_condition': "AND devName = 'TestDevice'",
'NTFPRCS_event_condition': "AND devComments LIKE '%test%'", 'NTFPRCS_event_condition': "AND devComments LIKE '%test%'",
'NTFPRCS_alert_down_time': '60' 'NTFPRCS_alert_down_time': '60'
}.get(key, '') }.get(k, '')
with patch('messaging.reporting.get_timezone_offset', return_value='+00:00'): with patch('messaging.reporting.get_timezone_offset', return_value='+00:00'):
# Test get_notifications function
result = get_notifications(mock_db) result = get_notifications(mock_db)
# Verify structure assert 'new_devices' in result
self.assertIn('new_devices', result) assert 'events' in result
self.assertIn('events', result) assert 'new_devices_meta' in result
self.assertIn('new_devices_meta', result) assert 'events_meta' in result
self.assertIn('events_meta', result) assert mock_db.get_table_as_json.called
# Verify parameterized queries were called def test_notification_system_integration(builder):
self.assertTrue(mock_db.get_table_as_json.called)
# Check that calls used parameters (not direct concatenation)
calls = mock_db.get_table_as_json.call_args_list
for call in calls:
args, kwargs = call
if len(args) > 1: # Has parameters
self.assertIsInstance(args[1], dict) # Parameters should be dict
print("✅ Existing DB/config compatibility: PASSED")
def test_3_notification_system_integration(self):
"""Test 3: Notification testing integration"""
print("\n=== TEST 3: Notification System Integration ===")
# Test that SafeConditionBuilder integrates with notification queries
builder = create_safe_condition_builder()
# Test email notification conditions
email_condition = "AND devName = 'EmailTestDevice'" email_condition = "AND devName = 'EmailTestDevice'"
condition, params = builder.get_safe_condition_legacy(email_condition) condition, params = builder.get_safe_condition_legacy(email_condition)
self.assertIn("devName = :", condition) assert "devName = :" in condition
self.assertIn('EmailTestDevice', list(params.values())) assert 'EmailTestDevice' in params.values()
# Test Apprise notification conditions
apprise_condition = "AND eve_EventType = 'Connected'" apprise_condition = "AND eve_EventType = 'Connected'"
condition, params = builder.get_safe_condition_legacy(apprise_condition) condition, params = builder.get_safe_condition_legacy(apprise_condition)
self.assertIn("eve_EventType = :", condition) assert "eve_EventType = :" in condition
self.assertIn('Connected', list(params.values())) assert 'Connected' in params.values()
# Test webhook notification conditions
webhook_condition = "AND devComments LIKE '%webhook%'" webhook_condition = "AND devComments LIKE '%webhook%'"
condition, params = builder.get_safe_condition_legacy(webhook_condition) condition, params = builder.get_safe_condition_legacy(webhook_condition)
self.assertIn("devComments LIKE :", condition) assert "devComments LIKE :" in condition
self.assertIn('%webhook%', list(params.values())) assert '%webhook%' in params.values()
# Test MQTT notification conditions
mqtt_condition = "AND eve_MAC = 'aa:bb:cc:dd:ee:ff'" mqtt_condition = "AND eve_MAC = 'aa:bb:cc:dd:ee:ff'"
condition, params = builder.get_safe_condition_legacy(mqtt_condition) condition, params = builder.get_safe_condition_legacy(mqtt_condition)
self.assertIn("eve_MAC = :", condition) assert "eve_MAC = :" in condition
self.assertIn('aa:bb:cc:dd:ee:ff', list(params.values())) assert 'aa:bb:cc:dd:ee:ff' in params.values()
print("✅ Notification system integration: PASSED") def test_settings_persistence(builder):
def test_4_settings_persistence(self):
"""Test 4: Settings persistence"""
print("\n=== TEST 4: Settings Persistence ===")
# Test various setting formats that should be supported
test_settings = [ test_settings = [
"AND devName = 'Persistent Device'", "AND devName = 'Persistent Device'",
"AND devComments = {s-quote}Legacy Quote{s-quote}", "AND devComments = {s-quote}Legacy Quote{s-quote}",
"AND eve_EventType IN ('Connected', 'Disconnected')", "AND eve_EventType IN ('Connected', 'Disconnected')",
"AND devLastIP = '192.168.1.1'", "AND devLastIP = '192.168.1.1'",
"" # Empty setting should work ""
] ]
builder = create_safe_condition_builder()
for setting in test_settings: for setting in test_settings:
try:
condition, params = builder.get_safe_condition_legacy(setting) condition, params = builder.get_safe_condition_legacy(setting)
# Should not raise exception assert isinstance(condition, str)
self.assertIsInstance(condition, str) assert isinstance(params, dict)
self.assertIsInstance(params, dict)
except Exception as e:
if setting != "": # Empty is allowed to "fail" gracefully
self.fail(f"Setting '{setting}' failed: {e}")
print("✅ Settings persistence: PASSED")
def test_5_device_operations(self):
"""Test 5: Device operations"""
print("\n=== TEST 5: Device Operations ===")
# Test device-related conditions
builder = create_safe_condition_builder()
def test_device_operations(builder):
device_conditions = [ device_conditions = [
"AND devName = 'Updated Device'", "AND devName = 'Updated Device'",
"AND devMac = 'aa:bb:cc:dd:ee:ff'", "AND devMac = 'aa:bb:cc:dd:ee:ff'",
"AND devComments = 'Device updated successfully'", "AND devComments = 'Device updated successfully'",
"AND devLastIP = '192.168.1.200'" "AND devLastIP = '192.168.1.200'"
] ]
for cond in device_conditions:
safe_condition, params = builder.get_safe_condition_legacy(cond)
assert len(params) > 0 or safe_condition == ""
assert "'" not in safe_condition
for condition in device_conditions: def test_plugin_functionality(builder):
safe_condition, params = builder.get_safe_condition_legacy(condition)
self.assertTrue(len(params) > 0 or safe_condition == "")
# Ensure no direct string concatenation in output
self.assertNotIn("'", safe_condition) # No literal quotes in SQL
print("✅ Device operations: PASSED")
def test_6_plugin_functionality(self):
"""Test 6: Plugin functionality"""
print("\n=== TEST 6: Plugin Functionality ===")
# Test plugin-related conditions that might be used
builder = create_safe_condition_builder()
plugin_conditions = [ plugin_conditions = [
"AND Plugin = 'TestPlugin'", "AND Plugin = 'TestPlugin'",
"AND Object_PrimaryId = 'primary123'", "AND Object_PrimaryId = 'primary123'",
"AND Status = 'Active'" "AND Status = 'Active'"
] ]
for cond in plugin_conditions:
safe_condition, params = builder.get_safe_condition_legacy(cond)
if safe_condition:
assert ":" in safe_condition
assert len(params) > 0
for condition in plugin_conditions: def test_sql_injection_prevention(builder):
safe_condition, params = builder.get_safe_condition_legacy(condition)
if safe_condition: # If condition was accepted
self.assertIn(":", safe_condition) # Should have parameter placeholder
self.assertTrue(len(params) > 0) # Should have parameters
# Test that plugin data structure is preserved
mock_db = Mock()
mock_db.sql = Mock()
mock_result = Mock()
mock_result.columnNames = ['Plugin', 'Object_PrimaryId', 'Status']
mock_result.json = {'data': []}
mock_db.get_table_as_json.return_value = mock_result
with patch('messaging.reporting.get_setting_value') as mock_settings:
mock_settings.side_effect = lambda key: {
'NTFPRCS_INCLUDED_SECTIONS': ['plugins']
}.get(key, '')
result = get_notifications(mock_db)
self.assertIn('plugins', result)
self.assertIn('plugins_meta', result)
print("✅ Plugin functionality: PASSED")
def test_7_sql_injection_prevention(self):
"""Test 7: SQL injection prevention (critical security test)"""
print("\n=== TEST 7: SQL Injection Prevention ===")
# Test malicious inputs are properly blocked
malicious_inputs = [ malicious_inputs = [
"'; DROP TABLE Events_Devices; --", "'; DROP TABLE Events_Devices; --",
"' OR '1'='1", "' OR '1'='1",
@@ -309,140 +195,43 @@ class NetAlertXIntegrationTest(unittest.TestCase):
"'; INSERT INTO Events VALUES ('hacked'); --", "'; INSERT INTO Events VALUES ('hacked'); --",
"' AND (SELECT COUNT(*) FROM sqlite_master) > 0 --" "' AND (SELECT COUNT(*) FROM sqlite_master) > 0 --"
] ]
for payload in malicious_inputs:
condition, params = builder.get_safe_condition_legacy(payload)
assert condition == ""
assert params == {}
builder = create_safe_condition_builder() def test_error_handling(builder):
for malicious_input in malicious_inputs:
condition, params = builder.get_safe_condition_legacy(malicious_input)
# All malicious inputs should result in empty/safe condition
self.assertEqual(condition, "", f"Malicious input not blocked: {malicious_input}")
self.assertEqual(params, {}, f"Parameters returned for malicious input: {malicious_input}")
print("✅ SQL injection prevention: PASSED")
def test_8_error_log_inspection(self):
"""Test 8: Error handling and logging"""
print("\n=== TEST 8: Error Handling and Logging ===")
# Test that invalid inputs are logged properly
builder = create_safe_condition_builder()
# This should log an error but not crash
invalid_condition = "INVALID SQL SYNTAX HERE" invalid_condition = "INVALID SQL SYNTAX HERE"
condition, params = builder.get_safe_condition_legacy(invalid_condition) condition, params = builder.get_safe_condition_legacy(invalid_condition)
assert condition == ""
assert params == {}
# Should return empty/safe values edge_cases = [None, "", " ", "\n\t", "AND column_not_in_whitelist = 'value'"]
self.assertEqual(condition, "")
self.assertEqual(params, {})
# Test edge cases
edge_cases = [
None, # This would cause TypeError in unpatched version
"",
" ",
"\n\t",
"AND column_not_in_whitelist = 'value'"
]
for case in edge_cases: for case in edge_cases:
try:
if case is not None: if case is not None:
condition, params = builder.get_safe_condition_legacy(case) condition, params = builder.get_safe_condition_legacy(case)
self.assertIsInstance(condition, str) assert isinstance(condition, str)
self.assertIsInstance(params, dict) assert isinstance(params, dict)
except Exception as e:
# Should not crash on any input
self.fail(f"Unexpected exception for input {case}: {e}")
print("✅ Error handling and logging: PASSED")
def test_9_backward_compatibility(self):
"""Test 9: Backward compatibility with legacy settings"""
print("\n=== TEST 9: Backward Compatibility ===")
# Test legacy {s-quote} placeholder support
builder = create_safe_condition_builder()
def test_backward_compatibility(builder):
legacy_conditions = [ legacy_conditions = [
"AND devName = {s-quote}Legacy Device{s-quote}", "AND devName = {s-quote}Legacy Device{s-quote}",
"AND devComments = {s-quote}Old Style Quote{s-quote}", "AND devComments = {s-quote}Old Style Quote{s-quote}",
"AND devName = 'Normal Quote'" # Modern style should still work "AND devName = 'Normal Quote'"
] ]
for cond in legacy_conditions:
condition, params = builder.get_safe_condition_legacy(cond)
if condition:
assert "{s-quote}" not in condition
assert ":" in condition
assert len(params) > 0
for legacy_condition in legacy_conditions: def test_performance_impact(builder):
condition, params = builder.get_safe_condition_legacy(legacy_condition)
if condition: # If accepted as valid
# Should not contain the {s-quote} placeholder in output
self.assertNotIn("{s-quote}", condition)
# Should have proper parameter binding
self.assertIn(":", condition)
self.assertTrue(len(params) > 0)
print("✅ Backward compatibility: PASSED")
def test_10_performance_impact(self):
"""Test 10: Performance impact measurement"""
print("\n=== TEST 10: Performance Impact ===")
import time import time
builder = create_safe_condition_builder()
# Test performance of condition building
test_condition = "AND devName = 'Performance Test Device'" test_condition = "AND devName = 'Performance Test Device'"
start = time.time()
start_time = time.time() for _ in range(1000):
for _ in range(1000): # Run 1000 times
condition, params = builder.get_safe_condition_legacy(test_condition) condition, params = builder.get_safe_condition_legacy(test_condition)
end_time = time.time() end = time.time()
avg_ms = (end - start) / 1000 * 1000
total_time = end_time - start_time assert avg_ms < 1.0
avg_time_ms = (total_time / 1000) * 1000
print(f"Average condition building time: {avg_time_ms:.3f}ms")
# Should be under 1ms per condition
self.assertLess(avg_time_ms, 1.0, "Performance regression detected")
print("✅ Performance impact: PASSED")
def run_integration_tests():
"""Run all integration tests and generate report"""
print("=" * 70)
print("NetAlertX SQL Injection Fix - Integration Test Suite")
print("Validating PR #1182 as requested by maintainer jokob-sk")
print("=" * 70)
# Run tests
suite = unittest.TestLoader().loadTestsFromTestCase(NetAlertXIntegrationTest)
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
# Generate summary
print("\n" + "=" * 70)
print("INTEGRATION TEST SUMMARY")
print("=" * 70)
total_tests = result.testsRun
failures = len(result.failures)
errors = len(result.errors)
passed = total_tests - failures - errors
print(f"Total Tests: {total_tests}")
print(f"Passed: {passed}")
print(f"Failed: {failures}")
print(f"Errors: {errors}")
print(f"Success Rate: {(passed/total_tests)*100:.1f}%")
if failures == 0 and errors == 0:
print("\n🎉 ALL INTEGRATION TESTS PASSED!")
print("✅ Ready for maintainer approval")
return True
else:
print("\n❌ INTEGRATION TESTS FAILED")
print("🚫 Requires fixes before approval")
return False
if __name__ == "__main__":
success = run_integration_tests()
sys.exit(0 if success else 1)
-139
View File
@@ -1,139 +0,0 @@
#!/usr/bin/env python3
"""
Test script to validate SQL injection fixes for issue #1179
"""
import re
import sys
def test_datetime_injection_fix():
"""Test that datetime injection vulnerability is fixed"""
# Read the reporting.py file
with open('server/messaging/reporting.py', 'r') as f:
content = f.read()
# Check for vulnerable f-string patterns with datetime and user input
vulnerable_patterns = [
r"datetime\('now',\s*f['\"].*{get_setting_value\('NTFPRCS_alert_down_time'\)}",
r"datetime\('now',\s*f['\"].*{get_timezone_offset\(\)}"
]
vulnerabilities_found = []
for pattern in vulnerable_patterns:
matches = re.findall(pattern, content)
if matches:
vulnerabilities_found.extend(matches)
if vulnerabilities_found:
print("❌ SECURITY TEST FAILED: Vulnerable datetime patterns found:")
for vuln in vulnerabilities_found:
print(f" - {vuln}")
return False
# Check for the secure patterns
secure_patterns = [
r"minutes = int\(get_setting_value\('NTFPRCS_alert_down_time'\) or 0\)",
r"tz_offset = get_timezone_offset\(\)"
]
secure_found = 0
for pattern in secure_patterns:
if re.search(pattern, content):
secure_found += 1
if secure_found >= 2:
print("✅ SECURITY TEST PASSED: Secure datetime handling implemented")
return True
else:
print("⚠️ SECURITY TEST WARNING: Expected secure patterns not fully found")
return False
def test_notification_instance_fix():
"""Test that the clearPendingEmailFlag function is secure"""
with open('server/models/notification_instance.py', 'r') as f:
content = f.read()
# Check for vulnerable f-string patterns in clearPendingEmailFlag
clearflag_section = ""
in_function = False
lines = content.split('\n')
for line in lines:
if 'def clearPendingEmailFlag' in line:
in_function = True
elif in_function and line.strip() and not line.startswith(' ') and not line.startswith('\t'):
break
if in_function:
clearflag_section += line + '\n'
# Check for vulnerable patterns
vulnerable_patterns = [
r"f['\"].*{get_setting_value\('NTFPRCS_alert_down_time'\)}",
r"f['\"].*{get_timezone_offset\(\)}"
]
vulnerabilities_found = []
for pattern in vulnerable_patterns:
matches = re.findall(pattern, clearflag_section)
if matches:
vulnerabilities_found.extend(matches)
if vulnerabilities_found:
print("❌ SECURITY TEST FAILED: clearPendingEmailFlag still vulnerable:")
for vuln in vulnerabilities_found:
print(f" - {vuln}")
return False
print("✅ SECURITY TEST PASSED: clearPendingEmailFlag appears secure")
return True
def test_code_quality():
"""Test basic code quality and imports"""
# Check if the modified files can be imported (basic syntax check)
try:
import subprocess
result = subprocess.run([
'python3', '-c',
'import sys; sys.path.append("server"); from messaging import reporting'
], capture_output=True, text=True, cwd='.')
if result.returncode == 0:
print("✅ CODE QUALITY TEST PASSED: reporting.py imports successfully")
return True
else:
print(f"❌ CODE QUALITY TEST FAILED: Import error: {result.stderr}")
return False
except Exception as e:
print(f"⚠️ CODE QUALITY TEST WARNING: Could not test imports: {e}")
return True # Don't fail for environment issues
if __name__ == "__main__":
print("🔒 Running SQL Injection Security Tests for Issue #1179\n")
tests = [
("Datetime Injection Fix", test_datetime_injection_fix),
("Notification Instance Security", test_notification_instance_fix),
("Code Quality", test_code_quality)
]
results = []
for test_name, test_func in tests:
print(f"Running: {test_name}")
result = test_func()
results.append(result)
print()
passed = sum(results)
total = len(results)
print(f"🔒 Security Test Summary: {passed}/{total} tests passed")
if passed == total:
print("✅ All security tests passed! The SQL injection fixes are working correctly.")
sys.exit(0)
else:
print("❌ Some security tests failed. Please review the fixes.")
sys.exit(1)