Add fixer and rule tests
This commit is contained in:
278
tests/test_rules.py
Normal file
278
tests/test_rules.py
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
"""Tests for rule analyzers."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
from src.analyzers.base import SeverityLevel, FindingCategory
|
||||||
|
from src.rules.security import SQLInjectionAnalyzer, EvalUsageAnalyzer, PathTraversalAnalyzer
|
||||||
|
from src.rules.antipatterns import (
|
||||||
|
ExceptionSwallowAnalyzer,
|
||||||
|
MagicNumberAnalyzer,
|
||||||
|
DeepNestingAnalyzer,
|
||||||
|
LongFunctionAnalyzer,
|
||||||
|
)
|
||||||
|
from src.rules.secrets import HardcodedSecretAnalyzer
|
||||||
|
from src.rules.performance import InefficientLoopAnalyzer, RedundantOperationAnalyzer, UnnecessaryCopyAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
class TestSQLInjectionAnalyzer:
|
||||||
|
"""Tests for SQL injection detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = SQLInjectionAnalyzer()
|
||||||
|
|
||||||
|
def test_no_false_positive_with_safe_query(self):
|
||||||
|
code = '''
|
||||||
|
user_input = "test"
|
||||||
|
result = safe_query(user_input)
|
||||||
|
'''
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) == 0
|
||||||
|
|
||||||
|
def test_no_false_positive_with_parameterized_query(self):
|
||||||
|
code = """
|
||||||
|
cursor.execute("SELECT * FROM users WHERE name = ?", (user_input,))
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) == 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEvalUsageAnalyzer:
|
||||||
|
"""Tests for eval/exec detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = EvalUsageAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_eval_usage(self):
|
||||||
|
code = """
|
||||||
|
user_input = "os.system('rm -rf /')"
|
||||||
|
result = eval(user_input)
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
assert findings[0].severity == SeverityLevel.CRITICAL
|
||||||
|
|
||||||
|
def test_detects_exec_usage(self):
|
||||||
|
code = """
|
||||||
|
code = "print('hello')"
|
||||||
|
exec(code)
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestExceptionSwallowAnalyzer:
|
||||||
|
"""Tests for exception swallowing detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = ExceptionSwallowAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_empty_except_clause(self):
|
||||||
|
code = """
|
||||||
|
try:
|
||||||
|
dangerous_operation()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMagicNumberAnalyzer:
|
||||||
|
"""Tests for magic number detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = MagicNumberAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_magic_number(self):
|
||||||
|
code = """
|
||||||
|
result = 42 * multiplier
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
|
||||||
|
def test_no_false_positive_for_small_numbers(self):
|
||||||
|
code = """
|
||||||
|
for i in range(3):
|
||||||
|
process(i)
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) == 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHardcodedSecretAnalyzer:
|
||||||
|
"""Tests for hardcoded secret detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = HardcodedSecretAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_aws_access_key(self):
|
||||||
|
code = """
|
||||||
|
AWS_KEY = "AKIAIOSFODNN7EXAMPLE"
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
assert findings[0].severity == SeverityLevel.CRITICAL
|
||||||
|
|
||||||
|
def test_detects_github_token(self):
|
||||||
|
code = """
|
||||||
|
GITHUB_TOKEN = "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestPathTraversalAnalyzer:
|
||||||
|
"""Tests for path traversal detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = PathTraversalAnalyzer()
|
||||||
|
|
||||||
|
def test_no_false_positive_with_safe_path(self):
|
||||||
|
code = '''
|
||||||
|
user_input = "safe/path"
|
||||||
|
result = safe_open(user_input)
|
||||||
|
'''
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) == 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInefficientLoopAnalyzer:
|
||||||
|
"""Tests for inefficient loop detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = InefficientLoopAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_inefficient_loop(self):
|
||||||
|
code = """
|
||||||
|
items = [1, 2, 3, 4, 5]
|
||||||
|
for i in range(len(items)):
|
||||||
|
print(items[i])
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) > 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRedundantOperationAnalyzer:
|
||||||
|
"""Tests for redundant operation detection."""
|
||||||
|
|
||||||
|
def setup_method(self):
|
||||||
|
self.analyzer = RedundantOperationAnalyzer()
|
||||||
|
|
||||||
|
def test_detects_redundant_list_call(self):
|
||||||
|
code = """
|
||||||
|
items = [1, 2, 3]
|
||||||
|
result = list(list(items))
|
||||||
|
"""
|
||||||
|
tree = self._parse_code(code)
|
||||||
|
findings = self.analyzer.analyze(code, Path("test.py"), tree)
|
||||||
|
assert len(findings) >= 0
|
||||||
|
|
||||||
|
def _parse_code(self, code):
|
||||||
|
from src.analyzers import PythonParser
|
||||||
|
parser = PythonParser()
|
||||||
|
return parser.parse(code)
|
||||||
|
|
||||||
|
|
||||||
|
class TestRuleMetadata:
|
||||||
|
"""Tests for rule metadata."""
|
||||||
|
|
||||||
|
def test_all_rules_have_unique_ids(self):
|
||||||
|
analyzers = [
|
||||||
|
SQLInjectionAnalyzer(),
|
||||||
|
EvalUsageAnalyzer(),
|
||||||
|
PathTraversalAnalyzer(),
|
||||||
|
ExceptionSwallowAnalyzer(),
|
||||||
|
MagicNumberAnalyzer(),
|
||||||
|
DeepNestingAnalyzer(),
|
||||||
|
LongFunctionAnalyzer(),
|
||||||
|
HardcodedSecretAnalyzer(),
|
||||||
|
InefficientLoopAnalyzer(),
|
||||||
|
RedundantOperationAnalyzer(),
|
||||||
|
UnnecessaryCopyAnalyzer(),
|
||||||
|
]
|
||||||
|
rule_ids = [a.rule_id() for a in analyzers]
|
||||||
|
assert len(rule_ids) == len(set(rule_ids)), "Rule IDs must be unique"
|
||||||
|
|
||||||
|
def test_all_rules_have_valid_severity(self):
|
||||||
|
analyzers = [
|
||||||
|
SQLInjectionAnalyzer(),
|
||||||
|
EvalUsageAnalyzer(),
|
||||||
|
PathTraversalAnalyzer(),
|
||||||
|
ExceptionSwallowAnalyzer(),
|
||||||
|
MagicNumberAnalyzer(),
|
||||||
|
DeepNestingAnalyzer(),
|
||||||
|
LongFunctionAnalyzer(),
|
||||||
|
HardcodedSecretAnalyzer(),
|
||||||
|
InefficientLoopAnalyzer(),
|
||||||
|
RedundantOperationAnalyzer(),
|
||||||
|
UnnecessaryCopyAnalyzer(),
|
||||||
|
]
|
||||||
|
for analyzer in analyzers:
|
||||||
|
assert analyzer.severity() in list(SeverityLevel)
|
||||||
|
|
||||||
|
def test_all_rules_have_valid_category(self):
|
||||||
|
from src.analyzers.base import FindingCategory
|
||||||
|
analyzers = [
|
||||||
|
SQLInjectionAnalyzer(),
|
||||||
|
EvalUsageAnalyzer(),
|
||||||
|
PathTraversalAnalyzer(),
|
||||||
|
ExceptionSwallowAnalyzer(),
|
||||||
|
MagicNumberAnalyzer(),
|
||||||
|
DeepNestingAnalyzer(),
|
||||||
|
LongFunctionAnalyzer(),
|
||||||
|
HardcodedSecretAnalyzer(),
|
||||||
|
InefficientLoopAnalyzer(),
|
||||||
|
RedundantOperationAnalyzer(),
|
||||||
|
UnnecessaryCopyAnalyzer(),
|
||||||
|
]
|
||||||
|
for analyzer in analyzers:
|
||||||
|
assert analyzer.category() in list(FindingCategory)
|
||||||
Reference in New Issue
Block a user