Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,45 @@
*.log

# Testing
.pytest_cache/
.coverage
htmlcov/
coverage.xml
*.py[cod]
__pycache__/
.tox/
.nox/

# Claude
.claude/*

# Virtual environments
venv/
.venv/
env/
ENV/
.env

# Poetry
poetry.lock

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Build artifacts
build/
dist/
*.egg-info/
.eggs/
*.egg

# Pipenv (keeping lock file)
# Pipfile.lock is NOT ignored
84 changes: 84 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
[tool.poetry]
name = "dnschef"
version = "0.4"
description = "A highly configurable DNS Proxy for Penetration Testers and Malware Analysts"
authors = ["Peter Kacherginsky", "Marcello Salvati"]
readme = "README"
homepage = "http://thesprawl.org/projects/dnschef/"
license = "BSD-3-Clause"

[tool.poetry.dependencies]
python = "^3.7"
dnslib = "0.9.10"

[tool.poetry.group.dev.dependencies]
pytest = "^7.4.0"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.0"

[tool.poetry.scripts]
test = "pytest:main"
tests = "pytest:main"

[tool.pytest.ini_options]
minversion = "6.0"
addopts = [
"-ra",
"--strict-markers",
"--strict-config",
"--cov=dnschef",
"--cov-branch",
"--cov-report=term-missing:skip-covered",
"--cov-report=html",
"--cov-report=xml",
"--cov-fail-under=80"
]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Tests that take a long time to run"
]

[tool.coverage.run]
source = ["dnschef"]
branch = true
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/site-packages/*",
"*/venv/*",
"*/.venv/*"
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if self.debug:",
"if settings.DEBUG",
"raise AssertionError",
"raise NotImplementedError",
"if 0:",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod"
]
show_missing = true
precision = 2
fail_under = 80

[tool.coverage.html]
directory = "htmlcov"

[tool.coverage.xml]
output = "coverage.xml"

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
Empty file added tests/__init__.py
Empty file.
163 changes: 163 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import pytest
import tempfile
import shutil
import os
from pathlib import Path
from unittest.mock import Mock, MagicMock


@pytest.fixture
def temp_dir():
"""Create a temporary directory for test files."""
temp_path = tempfile.mkdtemp()
yield Path(temp_path)
shutil.rmtree(temp_path)


@pytest.fixture
def temp_file(temp_dir):
"""Create a temporary file within the temp directory."""
def _temp_file(filename="test_file.txt", content=""):
file_path = temp_dir / filename
file_path.write_text(content)
return file_path
return _temp_file


@pytest.fixture
def mock_config():
"""Create a mock configuration object."""
config = Mock()
config.interface = "127.0.0.1"
config.port = 53
config.tcp = False
config.ipv6 = False
config.file = None
config.fakedomains = {}
config.truedomains = {}
config.nameservers = ["8.8.8.8", "8.8.4.4"]
return config


@pytest.fixture
def mock_dns_query():
"""Create a mock DNS query object."""
query = Mock()
query.questions = [Mock()]
query.questions[0].qname = "example.com"
query.questions[0].qtype = 1 # A record
query.questions[0].qclass = 1 # IN class
return query


@pytest.fixture
def mock_socket():
"""Create a mock socket object."""
socket_mock = MagicMock()
socket_mock.sendto = Mock(return_value=None)
socket_mock.recvfrom = Mock(return_value=(b"mock_response", ("127.0.0.1", 53)))
socket_mock.close = Mock(return_value=None)
return socket_mock


@pytest.fixture
def mock_logger():
"""Create a mock logger object."""
logger = Mock()
logger.info = Mock()
logger.debug = Mock()
logger.warning = Mock()
logger.error = Mock()
logger.critical = Mock()
return logger


@pytest.fixture
def dns_server_config():
"""Create a basic DNS server configuration dictionary."""
return {
"interface": "0.0.0.0",
"port": 53,
"tcp": False,
"ipv6": False,
"nameservers": ["8.8.8.8"],
"fakedomains": {
"example.com": "192.168.1.100"
},
"truedomains": {}
}


@pytest.fixture
def sample_dns_records():
"""Provide sample DNS records for testing."""
return {
"A": {
"example.com": "192.168.1.1",
"test.com": "10.0.0.1"
},
"AAAA": {
"example.com": "2001:db8::1",
"test.com": "2001:db8::2"
},
"MX": {
"example.com": "10 mail.example.com",
"test.com": "20 mail.test.com"
},
"CNAME": {
"www.example.com": "example.com",
"alias.test.com": "test.com"
},
"NS": {
"example.com": "ns1.example.com",
"test.com": "ns1.test.com"
},
"TXT": {
"example.com": "v=spf1 include:_spf.example.com ~all",
"test.com": "test-txt-record"
}
}


@pytest.fixture(autouse=True)
def reset_environment():
"""Reset environment variables and state before each test."""
original_env = os.environ.copy()
yield
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def capture_stdout(monkeypatch):
"""Capture stdout for testing print statements."""
import io
import sys

captured_output = io.StringIO()
monkeypatch.setattr(sys, 'stdout', captured_output)
yield captured_output
monkeypatch.undo()


@pytest.fixture
def mock_args():
"""Create mock command line arguments."""
args = Mock()
args.interface = "127.0.0.1"
args.port = 53
args.tcp = False
args.ipv6 = False
args.file = None
args.fakeip = None
args.fakeipv6 = None
args.fakedomains = None
args.fakealias = None
args.fakens = None
args.fakemx = None
args.faketxt = None
args.fakecname = None
args.truedomains = None
args.nameservers = "8.8.8.8"
args.q = False
return args
Empty file added tests/integration/__init__.py
Empty file.
97 changes: 97 additions & 0 deletions tests/test_setup_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import pytest
import sys
from pathlib import Path


class TestSetupValidation:
"""Validation tests to ensure the testing infrastructure is properly configured."""

def test_pytest_is_available(self):
"""Test that pytest is importable and available."""
import pytest
assert pytest.__version__

def test_pytest_cov_is_available(self):
"""Test that pytest-cov plugin is available."""
import pytest_cov
assert pytest_cov

def test_pytest_mock_is_available(self):
"""Test that pytest-mock plugin is available."""
import pytest_mock
assert pytest_mock

def test_project_root_in_path(self):
"""Test that the project root is in Python path."""
project_root = Path(__file__).parent.parent
assert str(project_root) in sys.path or str(project_root.absolute()) in sys.path

def test_can_import_dnschef(self):
"""Test that the main module can be imported."""
try:
import dnschef
assert dnschef.DNSCHEF_VERSION
except ImportError:
# Add parent directory to path and try again
sys.path.insert(0, str(Path(__file__).parent.parent))
import dnschef
assert dnschef.DNSCHEF_VERSION

def test_fixtures_are_available(self, temp_dir, mock_config, mock_logger):
"""Test that conftest fixtures are available and working."""
assert temp_dir.exists()
assert temp_dir.is_dir()

assert mock_config.interface == "127.0.0.1"
assert mock_config.port == 53

assert hasattr(mock_logger, 'info')
assert hasattr(mock_logger, 'error')

def test_markers_are_defined(self, pytestconfig):
"""Test that custom pytest markers are properly defined."""
markers = pytestconfig.getini('markers')
marker_names = [m.split(':')[0].strip() for m in markers]
assert 'unit' in marker_names
assert 'integration' in marker_names
assert 'slow' in marker_names

@pytest.mark.unit
def test_unit_marker_works(self):
"""Test that the unit test marker can be used."""
assert True

@pytest.mark.integration
def test_integration_marker_works(self):
"""Test that the integration test marker can be used."""
assert True

@pytest.mark.slow
def test_slow_marker_works(self):
"""Test that the slow test marker can be used."""
assert True

def test_temp_file_fixture(self, temp_file):
"""Test that the temp_file fixture works correctly."""
test_file = temp_file("test.txt", "Hello, World!")
assert test_file.exists()
assert test_file.read_text() == "Hello, World!"

def test_mock_dns_query_fixture(self, mock_dns_query):
"""Test that the mock DNS query fixture is properly configured."""
assert mock_dns_query.questions[0].qname == "example.com"
assert mock_dns_query.questions[0].qtype == 1
assert mock_dns_query.questions[0].qclass == 1

def test_sample_dns_records_fixture(self, sample_dns_records):
"""Test that the sample DNS records fixture provides expected data."""
assert "A" in sample_dns_records
assert "AAAA" in sample_dns_records
assert "MX" in sample_dns_records
assert sample_dns_records["A"]["example.com"] == "192.168.1.1"

def test_coverage_is_enabled(self):
"""Test that coverage tracking is properly configured."""
# This test will pass if pytest-cov is properly configured
# The actual coverage percentage will be checked by pytest-cov
assert True
Empty file added tests/unit/__init__.py
Empty file.