Testing Infrastructure

Testing Infrastructure

The following files were used as context for generating this wiki page:

Purpose and Scope

This document describes the testing infrastructure for WSHawk, including test frameworks, test organization, coverage measurement, and test execution workflows. It is intended for developers contributing to WSHawk or extending its functionality with new features.

For information about building WSHawk from source, see 11.3. For the development environment setup, see 11.1. For Docker-based development workflows, see 11.8.


Testing Stack Overview

WSHawk's testing infrastructure is built on industry-standard Python testing tools, supporting multiple Python versions (3.8-3.13) and enabling comprehensive verification of functionality across different environments.

graph TB
    subgraph "Test Execution Layer"
        pytest["pytest<br/>Test Runner<br/>Fixtures & Parametrization"]
        tox["tox<br/>Multi-Environment Testing<br/>Python 3.8-3.13"]
    end
    
    subgraph "Test Analysis Layer"
        coverage["coverage.py<br/>Code Coverage<br/>Branch Analysis"]
        hypothesis["hypothesis<br/>Property-Based Testing<br/>Fuzz Generation"]
    end
    
    subgraph "Test Organization"
        unit["Unit Tests<br/>tests/unit/<br/>Scanner, Payloads, Utils"]
        integration["Integration Tests<br/>tests/integration/<br/>End-to-End Scans"]
        fixtures["Test Fixtures<br/>tests/fixtures/<br/>Mock Servers, Payloads"]
    end
    
    subgraph "Test Artifacts"
        cache[".pytest_cache/<br/>Test Session Cache"]
        covdata[".coverage<br/>Coverage Data File"]
        htmlcov["htmlcov/<br/>HTML Coverage Report"]
        toxenv[".tox/<br/>Virtual Environments"]
        hypdata[".hypothesis/<br/>Example Database"]
    end
    
    subgraph "CI/CD Integration"
        ghactions["GitHub Actions<br/>.github/workflows/<br/>test.yml"]
    end
    
    pytest --> unit
    pytest --> integration
    pytest --> fixtures
    tox --> pytest
    
    pytest --> coverage
    pytest --> hypothesis
    
    pytest --> cache
    coverage --> covdata
    coverage --> htmlcov
    tox --> toxenv
    hypothesis --> hypdata
    
    ghactions --> tox
    ghactions --> coverage
    
    fixtures --> unit
    fixtures --> integration

Sources: setup.py:24, .gitignore:38-43


Test Organization Structure

WSHawk follows a hierarchical test organization pattern, with tests excluded from the distributed package.

Directory Layout

wshawk/
├── tests/                  # Root test directory (excluded from package)
│   ├── __init__.py
│   ├── unit/              # Unit tests for individual modules
│   │   ├── test_scanner_v2.py
│   │   ├── test_payloads.py
│   │   ├── test_resilience.py
│   │   ├── test_analysis.py
│   │   └── test_integrations.py
│   ├── integration/       # End-to-end integration tests
│   │   ├── test_full_scan.py
│   │   ├── test_web_dashboard.py
│   │   └── test_cli.py
│   ├── fixtures/          # Shared test fixtures and data
│   │   ├── mock_servers.py
│   │   ├── sample_payloads.json
│   │   └── vulnerable_endpoints.py
│   └── conftest.py        # pytest configuration and shared fixtures
├── examples/              # Example scripts (excluded from package)
└── docs/                  # Documentation (excluded from package)

The exclude directive in setup.py ensures that test code, examples, and documentation are not included in the PyPI distribution package, reducing package size and avoiding namespace pollution.

Sources: setup.py:24

Test Naming Conventions

| Test Type | File Pattern | Class Pattern | Function Pattern | |-----------|--------------|---------------|------------------| | Unit | test_<module>.py | Test<Class> | test_<function>_<scenario> | | Integration | test_<feature>.py | TestIntegration<Feature> | test_<workflow>_<outcome> | | Property | test_<module>_properties.py | TestProperties | test_property_<invariant> |


Test Execution Framework

pytest Configuration

The primary test runner is pytest, which provides powerful fixtures, parametrization, and plugin support. A typical conftest.py configuration includes:

# tests/conftest.py (typical structure)
import pytest
from wshawk.scanner_v2 import WSHawkV2
from wshawk.payloads import WSPayloads

@pytest.fixture
def scanner_instance():
    """Provides a configured WSHawkV2 instance for testing."""
    scanner = WSHawkV2("ws://test.example.com")
    scanner.use_headless_browser = False  # Disable for faster tests
    scanner.use_oast = False
    return scanner

@pytest.fixture
def payload_collection():
    """Provides WSPayloads instance with test payloads."""
    return WSPayloads()

@pytest.fixture(scope="session")
def mock_vulnerable_server():
    """Starts a mock vulnerable WebSocket server for integration tests."""
    # Start server, yield port, teardown
    pass

Running Tests Locally

Basic Test Execution:

# Run all tests
pytest

# Run with verbose output
pytest -v

# Run specific test file
pytest tests/unit/test_scanner_v2.py

# Run specific test function
pytest tests/unit/test_scanner_v2.py::TestScanner::test_injection_detection

# Run tests matching pattern
pytest -k "injection"

# Run with print statements visible
pytest -s

Parallel Execution:

# Install pytest-xdist
pip install pytest-xdist

# Run tests in parallel (4 workers)
pytest -n 4

Coverage Measurement

WSHawk uses coverage.py to measure code coverage and ensure comprehensive testing of critical security functionality.

graph LR
    tests["Test Suite<br/>pytest execution"]
    covrun["coverage run<br/>-m pytest"]
    covdata[".coverage<br/>SQLite Database"]
    covreport["coverage report<br/>Terminal Output"]
    covhtml["coverage html<br/>htmlcov/ Directory"]
    
    tests --> covrun
    covrun --> covdata
    covdata --> covreport
    covdata --> covhtml

Coverage Execution

Generate Coverage Data:

# Run tests with coverage
coverage run -m pytest

# Run with branch coverage (recommended)
coverage run --branch -m pytest

# Run with specific source (avoid including tests themselves)
coverage run --source=wshawk -m pytest

View Coverage Reports:

# Terminal report
coverage report

# Terminal report with missing lines
coverage report -m

# Generate HTML report (outputs to htmlcov/)
coverage html

# Open HTML report in browser
open htmlcov/index.html  # macOS
xdg-open htmlcov/index.html  # Linux

Coverage Artifacts

| Artifact | Location | Purpose | Gitignored | |----------|----------|---------|------------| | Coverage data | .coverage | Binary SQLite database with execution traces | Yes | | HTML report | htmlcov/ | Interactive browser-based coverage visualization | Yes | | XML report | coverage.xml | Machine-readable format for CI/CD integration | Yes |

Coverage Thresholds

Critical security modules should maintain high coverage:

  • Core Scanner (scanner_v2.py): ≥85% line coverage, ≥75% branch coverage
  • Payload System (payloads.py): ≥80% line coverage
  • Verification Modules (*_verifier.py): ≥85% line coverage
  • Resilience Layer (resilient_session.py): ≥90% line coverage

Sources: .gitignore:40-42


Multi-Environment Testing with tox

tox enables testing across multiple Python versions and dependency configurations, ensuring compatibility with the supported Python range (3.8-3.13).

tox Configuration

A typical tox.ini configuration:

[tox]
envlist = py38,py39,py310,py311,py312,py313,lint,coverage

[testenv]
deps =
    pytest
    pytest-asyncio
    pytest-cov
    hypothesis
commands =
    pytest {posargs}

[testenv:coverage]
deps =
    {[testenv]deps}
    coverage
commands =
    coverage run --branch -m pytest
    coverage report -m
    coverage html

[testenv:lint]
deps =
    flake8
    black
    mypy
commands =
    flake8 wshawk/
    black --check wshawk/
    mypy wshawk/

Running tox

# Run all environments
tox

# Run specific environment
tox -e py311

# Run coverage environment
tox -e coverage

# Run lint environment
tox -e lint

# Recreate virtual environments
tox -r

tox Artifacts

The .tox/ directory contains isolated virtual environments for each test configuration. This directory is gitignored to avoid committing large dependency installations.

Sources: setup.py:32-37, .gitignore:42


Property-Based Testing with Hypothesis

Hypothesis provides property-based testing, generating hundreds of test cases from high-level specifications to uncover edge cases in payload handling and message parsing.

graph TB
    strategy["@given Strategies<br/>st.text(), st.integers()<br/>Custom Generators"]
    hypothesis["Hypothesis Engine<br/>Example Generation<br/>Shrinking"]
    testfunc["Test Function<br/>test_property_*<br/>Assertions"]
    database[".hypothesis/<br/>Example Database<br/>Failing Cases"]
    
    strategy --> hypothesis
    hypothesis --> testfunc
    testfunc -->|"Failure"| database
    database -->|"Replay"| hypothesis

Hypothesis Test Example

from hypothesis import given, strategies as st
from wshawk.analysis import MessageAnalyzer

class TestMessageAnalyzerProperties:
    @given(st.text(min_size=1, max_size=1000))
    def test_property_analyzer_never_crashes(self, message):
        """MessageAnalyzer should handle any text input without crashing."""
        analyzer = MessageAnalyzer()
        # Should not raise exception
        result = analyzer.analyze_message(message)
        assert result is not None
    
    @given(st.dictionaries(
        keys=st.text(min_size=1, max_size=50),
        values=st.one_of(st.text(), st.integers(), st.floats())
    ))
    def test_property_json_field_detection(self, json_dict):
        """Field detection should work for any valid JSON structure."""
        import json
        analyzer = MessageAnalyzer()
        message = json.dumps(json_dict)
        fields = analyzer.detect_fields(message)
        # Should detect at least the top-level keys
        assert len(fields) >= len(json_dict)

Hypothesis Configuration

Hypothesis stores example databases in .hypothesis/ to replay failing cases:

# tests/conftest.py or specific test file
from hypothesis import settings, HealthCheck

# Custom profile for security testing
settings.register_profile("security", 
    max_examples=1000,  # More examples for thorough testing
    deadline=None,      # No time limit for complex operations
    suppress_health_check=[HealthCheck.too_slow]
)

settings.load_profile("security")

Hypothesis Artifacts

| Artifact | Location | Purpose | Gitignored | |----------|----------|---------|------------| | Example database | .hypothesis/ | Stores generated examples, especially failures | Yes |

Sources: .gitignore:43


Test Artifacts and Cleanup

WSHawk generates several test artifacts during execution. The .gitignore configuration ensures these are not committed to version control.

Artifact Locations

graph TB
    subgraph "Test Execution"
        pytest_run["pytest execution"]
        coverage_run["coverage run"]
        tox_run["tox execution"]
        hypothesis_run["hypothesis tests"]
    end
    
    subgraph "Cached Artifacts (Gitignored)"
        pytest_cache[".pytest_cache/<br/>Session cache<br/>Last failed tests"]
        coverage_file[".coverage<br/>Coverage data<br/>SQLite format"]
        htmlcov_dir["htmlcov/<br/>HTML reports<br/>Interactive viewer"]
        tox_dir[".tox/<br/>Virtual environments<br/>Per-Python version"]
        hypothesis_dir[".hypothesis/<br/>Example database<br/>Failing cases"]
    end
    
    subgraph "Test Results (Gitignored)"
        test_results["test_results/<br/>JUnit XML<br/>CI/CD reports"]
        scan_results["scan_results/<br/>Test scan outputs<br/>Sample reports"]
    end
    
    pytest_run --> pytest_cache
    coverage_run --> coverage_file
    coverage_run --> htmlcov_dir
    tox_run --> tox_dir
    hypothesis_run --> hypothesis_dir
    
    pytest_run --> test_results
    pytest_run --> scan_results

Cleanup Commands

# Remove all test artifacts
rm -rf .pytest_cache .coverage htmlcov .tox .hypothesis test_results scan_results

# Remove Python bytecode and build artifacts
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
find . -type f -name "*.pyo" -delete
rm -rf build/ dist/ *.egg-info/

# Complete clean (including virtual environments)
rm -rf venv/ env/ .venv/

Sources: .gitignore:38-43, .gitignore:78-82


CI/CD Integration

WSHawk's testing infrastructure integrates with GitHub Actions to run tests automatically on every push and pull request.

GitHub Actions Test Workflow

A typical test workflow (.github/workflows/test.yml):

name: Test Suite

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -e .
        pip install pytest pytest-cov hypothesis
    
    - name: Run tests with coverage
      run: |
        pytest --cov=wshawk --cov-report=xml --cov-report=term
    
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml

Test Report Integration

Test results can be exported in multiple formats for CI/CD integration:

# JUnit XML for Jenkins/GitLab CI
pytest --junitxml=test_results/junit.xml

# Coverage XML for Codecov/Coveralls
pytest --cov=wshawk --cov-report=xml

# HTML report for artifact storage
pytest --html=test_results/report.html --self-contained-html

Sources: setup.py:32-37


Writing New Tests

Test Structure Guidelines

Unit Test Template:

# tests/unit/test_new_feature.py
import pytest
from wshawk.new_module import NewClass

class TestNewClass:
    """Tests for NewClass functionality."""
    
    def test_initialization(self):
        """Test that NewClass initializes correctly."""
        instance = NewClass(param="value")
        assert instance.param == "value"
    
    def test_method_success_case(self):
        """Test method behavior in success scenario."""
        instance = NewClass()
        result = instance.method(input="test")
        assert result == "expected"
    
    def test_method_error_case(self):
        """Test method behavior when errors occur."""
        instance = NewClass()
        with pytest.raises(ValueError, match="invalid input"):
            instance.method(input="invalid")
    
    @pytest.mark.parametrize("input,expected", [
        ("test1", "result1"),
        ("test2", "result2"),
        ("test3", "result3"),
    ])
    def test_method_parametrized(self, input, expected):
        """Test method with multiple input cases."""
        instance = NewClass()
        assert instance.method(input=input) == expected

Integration Test Template:

# tests/integration/test_new_workflow.py
import pytest
from wshawk.scanner_v2 import WSHawkV2

@pytest.mark.integration
class TestNewWorkflow:
    """Integration tests for new scanning workflow."""
    
    @pytest.fixture
    def scanner(self):
        """Provide configured scanner instance."""
        scanner = WSHawkV2("ws://localhost:8080")
        scanner.use_headless_browser = False
        return scanner
    
    async def test_full_workflow(self, scanner, mock_vulnerable_server):
        """Test complete workflow from connection to report."""
        # Setup
        await scanner.connect()
        
        # Execute
        results = await scanner.run_heuristic_scan()
        
        # Verify
        assert len(results) > 0
        assert any(v.severity == "HIGH" for v in results)
        
        # Cleanup
        await scanner.disconnect()

Test Data Management

Fixtures for Test Payloads:

# tests/fixtures/sample_payloads.py
import json
import pytest

@pytest.fixture
def xss_payloads():
    """Provides sample XSS payloads for testing."""
    return [
        "<script>alert('xss')</script>",
        "<img src=x onerror=alert(1)>",
        "javascript:alert(document.cookie)",
    ]

@pytest.fixture
def json_message_samples():
    """Provides sample JSON WebSocket messages."""
    return [
        json.dumps({"action": "login", "user": "test"}),
        json.dumps({"type": "message", "content": "hello"}),
        json.dumps({"cmd": "execute", "args": ["ls", "-la"]}),
    ]

Async Test Support

For testing async functionality (WebSocket operations, scanner methods):

import pytest

# Mark test as async
@pytest.mark.asyncio
async def test_async_scan():
    """Test async scanning operations."""
    from wshawk.scanner_v2 import WSHawkV2
    
    scanner = WSHawkV2("ws://test.example.com")
    await scanner.connect()
    result = await scanner.send_payload("test")
    assert result is not None
    await scanner.disconnect()

Install pytest-asyncio dependency:

pip install pytest-asyncio

Sources: setup.py:9-14


Test Dependencies

The following dependencies are required for testing but not for production use:

| Package | Version | Purpose | |---------|---------|---------| | pytest | Latest | Test runner and framework | | pytest-cov | Latest | Coverage plugin for pytest | | pytest-asyncio | Latest | Async test support | | pytest-xdist | Latest | Parallel test execution | | hypothesis | Latest | Property-based testing | | coverage | Latest | Code coverage measurement | | tox | Latest | Multi-environment testing |

These are typically installed via a requirements-dev.txt or as extras in pyproject.toml:

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "pytest-cov>=4.0",
    "pytest-asyncio>=0.21",
    "pytest-xdist>=3.0",
    "hypothesis>=6.0",
    "coverage>=7.0",
    "tox>=4.0",
]

Install with:

pip install -e ".[dev]"

Sources: setup.py:9-14


Summary

WSHawk's testing infrastructure provides comprehensive verification through:

  • pytest for flexible test execution with fixtures and parametrization
  • tox for multi-Python-version compatibility testing (3.8-3.13)
  • coverage.py for measuring code coverage with branch analysis
  • hypothesis for property-based testing to uncover edge cases
  • CI/CD integration via GitHub Actions for automated testing

Test artifacts (.pytest_cache/, .coverage, htmlcov/, .tox/, .hypothesis/) are gitignored to maintain a clean repository. The test suite is excluded from package distribution via the exclude directive in setup.py.

For more information on development workflows, see 11.1 for environment setup and 11.8 for Docker-based testing.

Sources: setup.py:24, .gitignore:38-43