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