385 lines
11 KiB
Python
385 lines
11 KiB
Python
"""
|
|
Pytest configuration and fixtures for SneakyScanner tests.
|
|
"""
|
|
|
|
import os
|
|
import tempfile
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import sessionmaker
|
|
|
|
from web.app import create_app
|
|
from web.models import Base, Scan
|
|
from web.utils.settings import PasswordManager, SettingsManager
|
|
|
|
|
|
@pytest.fixture(scope='function')
|
|
def test_db():
|
|
"""
|
|
Create a temporary test database.
|
|
|
|
Yields a SQLAlchemy session for testing, then cleans up.
|
|
"""
|
|
# Create temporary database file
|
|
db_fd, db_path = tempfile.mkstemp(suffix='.db')
|
|
|
|
# Create engine and session
|
|
engine = create_engine(f'sqlite:///{db_path}', echo=False)
|
|
Base.metadata.create_all(engine)
|
|
|
|
Session = sessionmaker(bind=engine)
|
|
session = Session()
|
|
|
|
yield session
|
|
|
|
# Cleanup
|
|
session.close()
|
|
os.close(db_fd)
|
|
os.unlink(db_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_scan_report():
|
|
"""
|
|
Sample scan report matching the structure from scanner.py.
|
|
|
|
Returns a dictionary representing a typical scan output.
|
|
"""
|
|
return {
|
|
'title': 'Test Scan',
|
|
'scan_time': '2025-11-14T10:30:00Z',
|
|
'scan_duration': 125.5,
|
|
'config_file': '/app/configs/test.yaml',
|
|
'sites': [
|
|
{
|
|
'name': 'Test Site',
|
|
'ips': [
|
|
{
|
|
'address': '192.168.1.10',
|
|
'expected': {
|
|
'ping': True,
|
|
'tcp_ports': [22, 80, 443],
|
|
'udp_ports': [53],
|
|
'services': []
|
|
},
|
|
'actual': {
|
|
'ping': True,
|
|
'tcp_ports': [22, 80, 443, 8080],
|
|
'udp_ports': [53],
|
|
'services': [
|
|
{
|
|
'port': 22,
|
|
'service': 'ssh',
|
|
'product': 'OpenSSH',
|
|
'version': '8.9p1',
|
|
'extrainfo': 'Ubuntu',
|
|
'ostype': 'Linux'
|
|
},
|
|
{
|
|
'port': 443,
|
|
'service': 'https',
|
|
'product': 'nginx',
|
|
'version': '1.24.0',
|
|
'extrainfo': '',
|
|
'ostype': '',
|
|
'http_info': {
|
|
'protocol': 'https',
|
|
'screenshot': 'screenshots/192_168_1_10_443.png',
|
|
'certificate': {
|
|
'subject': 'CN=example.com',
|
|
'issuer': 'CN=Let\'s Encrypt Authority',
|
|
'serial_number': '123456789',
|
|
'not_valid_before': '2025-01-01T00:00:00Z',
|
|
'not_valid_after': '2025-12-31T23:59:59Z',
|
|
'days_until_expiry': 365,
|
|
'sans': ['example.com', 'www.example.com'],
|
|
'is_self_signed': False,
|
|
'tls_versions': {
|
|
'TLS 1.2': {
|
|
'supported': True,
|
|
'cipher_suites': [
|
|
'TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384',
|
|
'TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256'
|
|
]
|
|
},
|
|
'TLS 1.3': {
|
|
'supported': True,
|
|
'cipher_suites': [
|
|
'TLS_AES_256_GCM_SHA384',
|
|
'TLS_AES_128_GCM_SHA256'
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
'port': 80,
|
|
'service': 'http',
|
|
'product': 'nginx',
|
|
'version': '1.24.0',
|
|
'extrainfo': '',
|
|
'ostype': '',
|
|
'http_info': {
|
|
'protocol': 'http',
|
|
'screenshot': 'screenshots/192_168_1_10_80.png'
|
|
}
|
|
},
|
|
{
|
|
'port': 8080,
|
|
'service': 'http',
|
|
'product': 'Jetty',
|
|
'version': '9.4.48',
|
|
'extrainfo': '',
|
|
'ostype': ''
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_config_file(tmp_path):
|
|
"""
|
|
Create a sample YAML config file for testing.
|
|
|
|
Args:
|
|
tmp_path: pytest temporary directory fixture
|
|
|
|
Returns:
|
|
Path to created config file
|
|
"""
|
|
config_data = {
|
|
'title': 'Test Scan',
|
|
'sites': [
|
|
{
|
|
'name': 'Test Site',
|
|
'ips': [
|
|
{
|
|
'address': '192.168.1.10',
|
|
'expected': {
|
|
'ping': True,
|
|
'tcp_ports': [22, 80, 443],
|
|
'udp_ports': [53],
|
|
'services': ['ssh', 'http', 'https']
|
|
}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
|
|
config_file = tmp_path / 'test_config.yaml'
|
|
with open(config_file, 'w') as f:
|
|
yaml.dump(config_data, f)
|
|
|
|
return str(config_file)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_invalid_config_file(tmp_path):
|
|
"""
|
|
Create an invalid config file for testing validation.
|
|
|
|
Returns:
|
|
Path to invalid config file
|
|
"""
|
|
config_file = tmp_path / 'invalid_config.yaml'
|
|
with open(config_file, 'w') as f:
|
|
f.write("invalid: yaml: content: [missing closing bracket")
|
|
|
|
return str(config_file)
|
|
|
|
|
|
@pytest.fixture(scope='function')
|
|
def app():
|
|
"""
|
|
Create Flask application for testing.
|
|
|
|
Returns:
|
|
Configured Flask app instance with test database
|
|
"""
|
|
# Create temporary database
|
|
db_fd, db_path = tempfile.mkstemp(suffix='.db')
|
|
|
|
# Create app with test config
|
|
test_config = {
|
|
'TESTING': True,
|
|
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
|
|
'SECRET_KEY': 'test-secret-key'
|
|
}
|
|
|
|
app = create_app(test_config)
|
|
|
|
yield app
|
|
|
|
# Cleanup
|
|
os.close(db_fd)
|
|
os.unlink(db_path)
|
|
|
|
|
|
@pytest.fixture(scope='function')
|
|
def client(app):
|
|
"""
|
|
Create Flask test client.
|
|
|
|
Args:
|
|
app: Flask application fixture
|
|
|
|
Returns:
|
|
Flask test client for making API requests
|
|
"""
|
|
return app.test_client()
|
|
|
|
|
|
@pytest.fixture(scope='function')
|
|
def db(app):
|
|
"""
|
|
Alias for database session that works with Flask app context.
|
|
|
|
Args:
|
|
app: Flask application fixture
|
|
|
|
Returns:
|
|
SQLAlchemy session
|
|
"""
|
|
with app.app_context():
|
|
yield app.db_session
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_scan(db):
|
|
"""
|
|
Create a sample scan in the database for testing.
|
|
|
|
Args:
|
|
db: Database session fixture
|
|
|
|
Returns:
|
|
Scan model instance
|
|
"""
|
|
scan = Scan(
|
|
timestamp=datetime.utcnow(),
|
|
status='completed',
|
|
config_file='/app/configs/test.yaml',
|
|
title='Test Scan',
|
|
duration=125.5,
|
|
triggered_by='test',
|
|
json_path='/app/output/scan_report_20251114_103000.json',
|
|
html_path='/app/output/scan_report_20251114_103000.html',
|
|
zip_path='/app/output/scan_report_20251114_103000.zip',
|
|
screenshot_dir='/app/output/scan_report_20251114_103000_screenshots'
|
|
)
|
|
|
|
db.add(scan)
|
|
db.commit()
|
|
db.refresh(scan)
|
|
|
|
return scan
|
|
|
|
|
|
# Authentication Fixtures
|
|
|
|
@pytest.fixture
|
|
def app_password():
|
|
"""
|
|
Test password for authentication tests.
|
|
|
|
Returns:
|
|
Test password string
|
|
"""
|
|
return 'testpassword123'
|
|
|
|
|
|
@pytest.fixture
|
|
def db_with_password(db, app_password):
|
|
"""
|
|
Database session with application password set.
|
|
|
|
Args:
|
|
db: Database session fixture
|
|
app_password: Test password fixture
|
|
|
|
Returns:
|
|
Database session with password configured
|
|
"""
|
|
settings_manager = SettingsManager(db)
|
|
PasswordManager.set_app_password(settings_manager, app_password)
|
|
return db
|
|
|
|
|
|
@pytest.fixture
|
|
def db_no_password(app):
|
|
"""
|
|
Database session without application password set.
|
|
|
|
Args:
|
|
app: Flask application fixture
|
|
|
|
Returns:
|
|
Database session without password
|
|
"""
|
|
with app.app_context():
|
|
# Clear any password that might be set
|
|
settings_manager = SettingsManager(app.db_session)
|
|
settings_manager.delete('app_password')
|
|
yield app.db_session
|
|
|
|
|
|
@pytest.fixture
|
|
def authenticated_client(client, db_with_password, app_password):
|
|
"""
|
|
Flask test client with authenticated session.
|
|
|
|
Args:
|
|
client: Flask test client fixture
|
|
db_with_password: Database with password set
|
|
app_password: Test password fixture
|
|
|
|
Returns:
|
|
Test client with active session
|
|
"""
|
|
# Log in
|
|
client.post('/auth/login', data={
|
|
'password': app_password
|
|
})
|
|
return client
|
|
|
|
|
|
@pytest.fixture
|
|
def client_no_password(app):
|
|
"""
|
|
Flask test client with no password set (for setup testing).
|
|
|
|
Args:
|
|
app: Flask application fixture
|
|
|
|
Returns:
|
|
Test client for testing setup flow
|
|
"""
|
|
# Create temporary database without password
|
|
db_fd, db_path = tempfile.mkstemp(suffix='.db')
|
|
|
|
test_config = {
|
|
'TESTING': True,
|
|
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
|
|
'SECRET_KEY': 'test-secret-key'
|
|
}
|
|
|
|
test_app = create_app(test_config)
|
|
test_client = test_app.test_client()
|
|
|
|
yield test_client
|
|
|
|
# Cleanup
|
|
os.close(db_fd)
|
|
os.unlink(db_path)
|