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
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,36 @@ ENV/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# pytest
.pytest_cache/

# Claude
.claude/*

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

# OS files
.DS_Store
Thumbs.db

# Testing coverage reports
htmlcov/
coverage.xml
.coverage
.coverage.*

# Virtual environments
virtualenv/

# Poetry
# Note: Do not ignore poetry.lock - it should be committed
dist/
*.egg-info/
480 changes: 480 additions & 0 deletions poetry.lock

Large diffs are not rendered by default.

92 changes: 92 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
[tool.poetry]
name = "msdat"
version = "2.4"
description = "Microsoft SQL Database Attacking Tool"
authors = ["MSDAT Contributors"]
readme = "README.md"
packages = [{include = "*.py"}]

[tool.poetry.dependencies]
python = "^3.8"
cython = "^3.0.0"
colorlog = "^6.7.0"
termcolor = "^2.3.0"
pymssql = "^2.2.8"
python-libnmap = "^0.7.3"
argcomplete = "^3.1.0"

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

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

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py", "tests.py"]
python_classes = ["Test*", "*Tests"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--strict-config",
"--cov=.",
"--cov-branch",
"--cov-report=term-missing",
"--cov-report=html:htmlcov",
"--cov-report=xml:coverage.xml",
]
markers = [
"unit: Unit tests",
"integration: Integration tests",
"slow: Slow tests",
]
filterwarnings = [
"error",
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning",
]

[tool.coverage.run]
source = ["."]
omit = [
"*/tests/*",
"*/test_*",
"*/__pycache__/*",
"*/site-packages/*",
"setup.py",
"*/migrations/*",
"*/.venv/*",
"*/venv/*",
"*/virtualenv/*",
]

[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__.:",
"class .*\\bProtocol\\):",
"@(abc\\.)?abstractmethod",
]
precision = 2
show_missing = true
skip_covered = false

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

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

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

# Add the workspace directory to Python path for imports
sys.path.insert(0, str(Path(__file__).parent.parent))


@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 _create_temp_file(filename="test_file.txt", content=""):
file_path = temp_dir / filename
file_path.write_text(content)
return file_path
return _create_temp_file


@pytest.fixture
def mock_config():
"""Provide a mock configuration dictionary."""
return {
"database": {
"host": "localhost",
"port": 1433,
"username": "test_user",
"password": "test_password",
"database": "test_db"
},
"logging": {
"level": "INFO",
"file": "test.log"
},
"security": {
"timeout": 30,
"max_attempts": 3
}
}


@pytest.fixture
def mock_database_connection():
"""Mock database connection object."""
mock_conn = Mock()
mock_cursor = Mock()

# Configure the mock connection
mock_conn.cursor.return_value = mock_cursor
mock_conn.close.return_value = None
mock_conn.commit.return_value = None
mock_conn.rollback.return_value = None

# Configure the mock cursor
mock_cursor.execute.return_value = None
mock_cursor.fetchall.return_value = []
mock_cursor.fetchone.return_value = None
mock_cursor.close.return_value = None

return mock_conn, mock_cursor


@pytest.fixture
def mock_argparse_args():
"""Mock command line arguments."""
args = Mock()
args.host = "localhost"
args.port = 1433
args.user = "sa"
args.password = "password"
args.database = "master"
args.verbose = 1
args.timeout = 30
args.windows_auth = False
return args


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


@pytest.fixture
def sample_sql_results():
"""Sample SQL query results for testing."""
return [
{"id": 1, "name": "test_user1", "role": "admin"},
{"id": 2, "name": "test_user2", "role": "user"},
{"id": 3, "name": "test_user3", "role": "guest"}
]


@pytest.fixture
def mock_file_system(temp_dir):
"""Create a mock file system structure."""
# Create sample directory structure
(temp_dir / "config").mkdir()
(temp_dir / "logs").mkdir()
(temp_dir / "data").mkdir()

# Create sample files
(temp_dir / "config" / "settings.json").write_text(json.dumps({
"app": "test",
"version": "1.0"
}))

(temp_dir / "data" / "accounts.txt").write_text("user1:pass1\nuser2:pass2\n")

return temp_dir


@pytest.fixture
def mock_network_response():
"""Mock network response object."""
response = Mock()
response.status_code = 200
response.text = "Mock response body"
response.json.return_value = {"status": "success", "data": []}
response.headers = {"Content-Type": "application/json"}
return response


@pytest.fixture(autouse=True)
def reset_modules():
"""Reset module imports between tests to avoid state pollution."""
modules_to_reset = [
'Constants', 'Utils', 'Output', 'Mssql', 'MssqlInfo',
'PasswordGuesser', 'Passwordstealer', 'Xpcmdshell',
'OleAutomation', 'Jobs', 'BulkOpen', 'XpDirectory',
'SMBAuthenticationCapture', 'TrustworthyPE', 'Search',
'UsernameLikePassword', 'ScanPorts', 'Cleaner'
]

for module in modules_to_reset:
if module in sys.modules:
del sys.modules[module]


@pytest.fixture
def capture_output():
"""Capture stdout and stderr for testing print statements."""
from io import StringIO
import sys

old_stdout = sys.stdout
old_stderr = sys.stderr

stdout_capture = StringIO()
stderr_capture = StringIO()

sys.stdout = stdout_capture
sys.stderr = stderr_capture

yield stdout_capture, stderr_capture

sys.stdout = old_stdout
sys.stderr = old_stderr


@pytest.fixture
def mock_progressbar():
"""Mock progress bar for testing progress indicators."""
pbar = Mock()
pbar.update = Mock()
pbar.finish = Mock()
pbar.start = Mock()
return pbar


@pytest.fixture
def environment_variables():
"""Temporarily set environment variables for testing."""
original_env = os.environ.copy()

def _set_env(**kwargs):
for key, value in kwargs.items():
os.environ[key] = str(value)

yield _set_env

# Restore original environment
os.environ.clear()
os.environ.update(original_env)


@pytest.fixture
def mock_color_output():
"""Mock color output functions for testing colored terminal output."""
with patch('termcolor.colored', side_effect=lambda text, *args, **kwargs: text):
with patch('colorlog.ColoredFormatter'):
yield
Empty file added tests/integration/__init__.py
Empty file.
Loading