issue 77 pt2 ruff

This commit is contained in:
CarterPerez-dev 2026-02-18 19:47:47 -05:00
parent d056b1a123
commit 47432c8ce4
33 changed files with 209 additions and 202 deletions

View File

@ -21,18 +21,53 @@ jobs:
fail-fast: false
matrix:
include:
- name: api-rate-limiter
type: python
path: PROJECTS/advanced/api-rate-limiter
- name: dns-lookup
type: python
path: PROJECTS/beginner/dns-lookup
# Python (ruff) - Beginner
- name: caesar-cipher
type: ruff
path: PROJECTS/beginner/caesar-cipher
- name: keylogger
type: python
type: ruff
path: PROJECTS/beginner/keylogger
- name: docker-security-audit
type: go
path: PROJECTS/beginner/docker-security-audit
- name: dns-lookup
type: ruff
path: PROJECTS/beginner/dns-lookup
- name: metadata-scrubber-tool
type: ruff
path: PROJECTS/beginner/metadata-scrubber-tool
- name: network-traffic-analyzer
type: ruff
path: PROJECTS/beginner/network-traffic-analyzer
- name: base64-tool
type: ruff
path: PROJECTS/beginner/base64-tool
- name: c2-beacon-backend
type: ruff
path: PROJECTS/beginner/c2-beacon/backend
# Python (ruff) - Intermediate
- name: api-security-scanner-backend
type: ruff
path: PROJECTS/intermediate/api-security-scanner/backend
- name: siem-dashboard-backend
type: ruff
path: PROJECTS/intermediate/siem-dashboard/backend
# Python (ruff) - Advanced
- name: bug-bounty-platform-backend
type: ruff
path: PROJECTS/advanced/bug-bounty-platform/backend
- name: encrypted-p2p-chat-backend
type: ruff
path: PROJECTS/advanced/encrypted-p2p-chat/backend
- name: api-rate-limiter
type: ruff
path: PROJECTS/advanced/api-rate-limiter
- name: ai-threat-detection-backend
type: ruff
path: PROJECTS/advanced/ai-threat-detection/backend
# Python (ruff) - Templates
- name: fullstack-template-backend
type: ruff
path: TEMPLATES/fullstack-template/backend
# Biome (frontend)
- name: bug-bounty-platform-frontend
type: biome
path: PROJECTS/advanced/bug-bounty-platform/frontend
@ -51,6 +86,10 @@ jobs:
- name: fullstack-template-frontend
type: biome
path: TEMPLATES/fullstack-template/frontend
# Go
- name: docker-security-audit
type: go
path: PROJECTS/beginner/docker-security-audit
defaults:
run:
@ -60,28 +99,16 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
# Python Setup
# Ruff Setup
- name: Set up Python
if: matrix.type == 'python'
if: matrix.type == 'ruff'
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Cache pip dependencies
if: matrix.type == 'python'
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ matrix.name }}-${{ hashFiles(format('{0}/pyproject.toml', matrix.path)) }}
restore-keys: |
${{ runner.os }}-pip-${{ matrix.name }}-
${{ runner.os }}-pip-
- name: Install Python dependencies
if: matrix.type == 'python'
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Install ruff
if: matrix.type == 'ruff'
run: pip install ruff
# Biome Setup
- name: Setup Node.js
@ -118,24 +145,9 @@ jobs:
go-version: '1.21'
cache-dependency-path: ${{ matrix.path }}/go.sum
# Python Linting
- name: Run pylint
if: matrix.type == 'python'
id: pylint
run: |
echo "Running pylint..."
if pylint . > pylint-output.txt 2>&1; then
echo "PYLINT_PASSED=true" >> $GITHUB_ENV
echo "No pylint errors found!"
else
echo "PYLINT_PASSED=false" >> $GITHUB_ENV
echo "Pylint found issues!"
fi
cat pylint-output.txt
continue-on-error: true
# Ruff Linting
- name: Run ruff
if: matrix.type == 'python'
if: matrix.type == 'ruff'
id: ruff
run: |
echo "Running ruff check..."
@ -149,21 +161,6 @@ jobs:
cat ruff-output.txt
continue-on-error: true
- name: Run mypy
if: matrix.type == 'python'
id: mypy
run: |
echo "Running mypy..."
if mypy . > mypy-output.txt 2>&1; then
echo "MYPY_PASSED=true" >> $GITHUB_ENV
echo "No mypy errors found"
else
echo "MYPY_PASSED=false" >> $GITHUB_ENV
echo "Mypy found issues"
fi
cat mypy-output.txt
continue-on-error: true
# Biome Linting
- name: Run Biome
if: matrix.type == 'biome'
@ -196,30 +193,14 @@ jobs:
cat golangci-output.txt
continue-on-error: true
# Create Summary for Python
- name: Create Python Lint Summary
if: matrix.type == 'python' && github.event_name == 'pull_request'
# Create Summary for Ruff
- name: Create Ruff Lint Summary
if: matrix.type == 'ruff' && github.event_name == 'pull_request'
run: |
{
echo "## Lint Results: ${{ matrix.name }}"
echo ''
# Pylint Status
if [[ "${{ env.PYLINT_PASSED }}" == "true" ]]; then
echo '### Pylint: **Passed**'
echo 'No pylint issues found.'
else
echo '### Pylint: **Issues Found**'
echo '<details><summary>View pylint output</summary>'
echo ''
echo '```'
head -100 pylint-output.txt
echo '```'
echo '</details>'
fi
echo ''
# Ruff Status
if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then
echo '### Ruff: **Passed**'
echo 'No ruff issues found.'
@ -234,23 +215,7 @@ jobs:
fi
echo ''
# Mypy Status
if [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then
echo '### Mypy: **Passed**'
echo 'No mypy issues found.'
else
echo '### Mypy: **Issues Found**'
echo '<details><summary>View mypy output</summary>'
echo ''
echo '```'
head -100 mypy-output.txt
echo '```'
echo '</details>'
fi
echo ''
# Overall Summary
if [[ "${{ env.PYLINT_PASSED }}" == "true" ]] && [[ "${{ env.RUFF_PASSED }}" == "true" ]] && [[ "${{ env.MYPY_PASSED }}" == "true" ]]; then
if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then
echo '---'
echo '### All checks passed!'
else
@ -269,7 +234,6 @@ jobs:
echo "## Lint Results: ${{ matrix.name }}"
echo ''
# Biome Status
if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then
echo '### Biome: **Passed**'
echo 'No Biome issues found.'
@ -284,7 +248,6 @@ jobs:
fi
echo ''
# Overall Summary
if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then
echo '---'
echo '### All checks passed!'
@ -304,7 +267,6 @@ jobs:
echo "## Lint Results: ${{ matrix.name }}"
echo ''
# golangci-lint Status
if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then
echo '### golangci-lint: **Passed**'
echo 'No golangci-lint issues found.'
@ -319,7 +281,6 @@ jobs:
fi
echo ''
# Overall Summary
if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then
echo '---'
echo '### All checks passed!'
@ -344,9 +305,9 @@ jobs:
# Exit with proper status
- name: Check lint status
run: |
if [[ "${{ matrix.type }}" == "python" ]]; then
if [[ "${{ env.PYLINT_PASSED }}" == "false" ]] || [[ "${{ env.RUFF_PASSED }}" == "false" ]] || [[ "${{ env.MYPY_PASSED }}" == "false" ]]; then
echo "Python lint checks failed"
if [[ "${{ matrix.type }}" == "ruff" ]]; then
if [[ "${{ env.RUFF_PASSED }}" == "false" ]]; then
echo "Ruff lint checks failed"
exit 1
fi
elif [[ "${{ matrix.type }}" == "biome" ]]; then

View File

@ -1,16 +1,95 @@
repos:
# Python Backend Ruff Checks
# Python Ruff Checks
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0
rev: v0.15.1
hooks:
# Beginner projects
- id: ruff
name: ruff check (backend)
name: ruff check (caesar-cipher)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/api-security-scanner/backend/
exclude: ^PROJECTS/api-security-scanner/backend/(\.venv|__pycache__|\.pytest_cache)/
files: ^PROJECTS/beginner/caesar-cipher/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (keylogger)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/keylogger/
exclude: (\.venv|__pycache__|\.pytest_cache)/
# TODO: add additional ruff checks for remaining projects
- id: ruff
name: ruff check (dns-lookup)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/dns-lookup/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (metadata-scrubber-tool)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/metadata-scrubber-tool/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (network-traffic-analyzer)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/network-traffic-analyzer/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (base64-tool)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/base64-tool/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (c2-beacon backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/beginner/c2-beacon/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
# Intermediate projects
- id: ruff
name: ruff check (api-security-scanner backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/intermediate/api-security-scanner/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (siem-dashboard backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/intermediate/siem-dashboard/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
# Advanced projects
- id: ruff
name: ruff check (bug-bounty-platform backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/advanced/bug-bounty-platform/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (encrypted-p2p-chat backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/advanced/encrypted-p2p-chat/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (api-rate-limiter)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/advanced/api-rate-limiter/
exclude: (\.venv|__pycache__|\.pytest_cache)/
- id: ruff
name: ruff check (ai-threat-detection backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/advanced/ai-threat-detection/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
# Templates
- id: ruff
name: ruff check (fullstack-template backend)
args: [--fix, --exit-non-zero-on-fix]
files: ^TEMPLATES/fullstack-template/backend/
exclude: (\.venv|__pycache__|\.pytest_cache)/
# TODO: add golangci checks for GO projects

View File

@ -115,7 +115,7 @@ class RuleEngine:
for trule in _THRESHOLD_RULES:
value = features.get(trule.feature_key, 0)
if isinstance(value, (int, float)) and value > trule.threshold:
if isinstance(value, int | float) and value > trule.threshold:
matched.append((trule.name, trule.score))
if not matched:

View File

@ -26,7 +26,7 @@ async def test_health_returns_200() -> None:
data = response.json()
assert data["status"] == "healthy"
assert "uptime_seconds" in data
assert isinstance(data["uptime_seconds"], (int, float))
assert isinstance(data["uptime_seconds"], int | float)
assert "pipeline_running" in data

View File

@ -6,7 +6,6 @@ app.py
from __future__ import annotations
import uvicorn
from typing import Annotated
from contextlib import asynccontextmanager
from fastapi import (
@ -17,7 +16,6 @@ from fastapi import (
from fastapi_420 import (
RateLimiter,
RateLimiterSettings,
RateLimitMiddleware,
ScopedRateLimiter,
FingerprintSettings,
StorageSettings,

View File

@ -10,15 +10,12 @@ import time
from collections.abc import AsyncGenerator, Generator
from dataclasses import dataclass, field
from typing import Any
from unittest.mock import MagicMock
import pytest
from fastapi import FastAPI, Request
from httpx import ASGITransport, AsyncClient
from starlette.testclient import TestClient
from fastapi_420.algorithms import create_algorithm
from fastapi_420.algorithms.base import BaseAlgorithm
from fastapi_420.algorithms.fixed_window import FixedWindowAlgorithm
from fastapi_420.algorithms.sliding_window import SlidingWindowAlgorithm
from fastapi_420.algorithms.token_bucket import TokenBucketAlgorithm
@ -50,7 +47,6 @@ from fastapi_420.types import (
RateLimitKey,
RateLimitResult,
RateLimitRule,
StorageType,
TokenBucketState,
WindowState,
)
@ -687,7 +683,7 @@ def rate_limiter_settings(
@pytest.fixture
async def memory_storage() -> AsyncGenerator[MemoryStorage, None]:
async def memory_storage() -> AsyncGenerator[MemoryStorage]:
"""
Create and manage MemoryStorage instance
"""
@ -791,8 +787,7 @@ async def circuit_breaker() -> CircuitBreaker:
async def rate_limiter(
rate_limiter_settings: RateLimiterSettings,
memory_storage: MemoryStorage,
) -> AsyncGenerator[RateLimiter,
None]:
) -> AsyncGenerator[RateLimiter]:
"""
Create and manage RateLimiter instance
"""
@ -922,8 +917,7 @@ async def test_app_with_limiter(
@pytest.fixture
async def async_client(test_app: FastAPI) -> AsyncGenerator[AsyncClient,
None]:
async def async_client(test_app: FastAPI) -> AsyncGenerator[AsyncClient]:
"""
Create async HTTP client for testing
"""
@ -937,8 +931,7 @@ async def async_client(test_app: FastAPI) -> AsyncGenerator[AsyncClient,
@pytest.fixture
async def rate_limited_client(
test_app_with_limiter: FastAPI,
) -> AsyncGenerator[AsyncClient,
None]:
) -> AsyncGenerator[AsyncClient]:
"""
Create async HTTP client with rate limiting
"""
@ -950,7 +943,7 @@ async def rate_limited_client(
@pytest.fixture
def sync_client(test_app: FastAPI) -> Generator[TestClient, None, None]:
def sync_client(test_app: FastAPI) -> Generator[TestClient]:
"""
Create sync test client
"""

View File

@ -4,7 +4,6 @@ test_algorithms.py
"""
from __future__ import annotations
import time
import pytest
@ -17,7 +16,6 @@ from fastapi_420.types import Algorithm
from tests.conftest import (
WINDOW_MINUTE,
WINDOW_SECOND,
RuleFactory,
)
@ -75,8 +73,8 @@ class TestSlidingWindowAlgorithm:
storage = MemoryStorage()
rule = RuleFactory.per_minute(100)
for i in range(50):
result = await algo.check(storage, f"multi_key", rule)
for _i in range(50):
result = await algo.check(storage, "multi_key", rule)
assert result.allowed is True
assert result.remaining == 50

View File

@ -67,7 +67,7 @@ class TestIPExtractor:
def test_ipv6_custom_prefix_length(self) -> None:
extractor = IPExtractor(ipv6_prefix_length = 48)
request = RequestFactory.create(client_ip = TEST_IP_V6)
raw_ip, normalized_ip = extractor.extract(request)
_, normalized_ip = extractor.extract(request)
assert "::" in normalized_ip or normalized_ip != TEST_IP_V6
@ -75,7 +75,7 @@ class TestIPExtractor:
extractor = IPExtractor()
ipv4_mapped = "::ffff:192.168.1.100"
request = RequestFactory.create(client_ip = ipv4_mapped)
raw_ip, normalized_ip = extractor.extract(request)
_, normalized_ip = extractor.extract(request)
assert normalized_ip == "192.168.1.100"
@ -323,7 +323,7 @@ class TestAuthExtractor:
extractor = AuthExtractor(hash_identifiers = False)
request = RequestFactory.with_auth(
auth_type = "bearer",
token = "invalid"
token = "invalid" # noqa: S106
)
identifier = extractor.extract(request)

View File

@ -10,11 +10,9 @@ from fastapi_420.config import RateLimiterSettings
from fastapi_420.exceptions import EnhanceYourCalm, HTTP_420_ENHANCE_YOUR_CALM
from fastapi_420.limiter import RateLimiter
from fastapi_420.storage import MemoryStorage
from fastapi_420.types import Algorithm, FingerprintLevel
from fastapi_420.types import Algorithm
from tests.conftest import (
WINDOW_MINUTE,
DEFAULT_LIMIT_REQUESTS,
RequestFactory,
)

View File

@ -15,8 +15,6 @@ from fastapi_420.types import StorageType
from tests.conftest import (
WINDOW_MINUTE,
WINDOW_SECOND,
DEFAULT_LIMIT_REQUESTS,
)
@ -163,7 +161,6 @@ class TestMemoryStorageSlidingWindow:
previous_window = current_window - 1
prev_key = f"{key}:{previous_window}"
curr_key = f"{key}:{current_window}"
storage._windows[prev_key] = storage._windows.__class__().__class__
from fastapi_420.storage.memory import WindowEntry
@ -360,7 +357,6 @@ class TestMemoryStorageCleanup:
storage = MemoryStorage(cleanup_interval = 1)
await storage.start_cleanup_task()
task = storage._cleanup_task
await storage.close()
assert storage._cleanup_task is None

View File

@ -188,9 +188,8 @@ class Settings(BaseSettings):
"""
Enforce security constraints in production environment.
"""
if self.ENVIRONMENT == Environment.PRODUCTION:
if self.DEBUG:
raise ValueError("DEBUG must be False in production")
if self.ENVIRONMENT == Environment.PRODUCTION and self.DEBUG:
raise ValueError("DEBUG must be False in production")
return self

View File

@ -12,7 +12,7 @@ from fastapi.responses import JSONResponse
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from config import settings, Environment, API_PREFIX
from config import settings, API_PREFIX
from core.database import sessionmanager
from core.exceptions import BaseAppException
from core.logging import configure_logging
@ -74,8 +74,6 @@ def create_app() -> FastAPI:
"""
Application factory
"""
is_production = settings.ENVIRONMENT == Environment.PRODUCTION
app = FastAPI(
title = settings.APP_NAME,
summary = settings.APP_SUMMARY,

View File

@ -4,7 +4,6 @@ Encryption endpoints for X3DH prekey bundles
"""
import logging
from typing import Optional
from uuid import UUID
from fastapi import APIRouter, Depends, status

View File

@ -3,7 +3,6 @@ AngelaMos | 2026
conftest.py
"""
import tempfile
from pathlib import Path
import pytest

View File

@ -3,7 +3,6 @@ AngelaMos | 2026
test_encoding.py
"""
import pytest
from core.encoding import decode, encode, xor_bytes

View File

@ -8,7 +8,6 @@ import pytest
from core.models import CommandType
from core.protocol import Message, MessageType, pack, unpack
TEST_KEY = "test-protocol-key"

View File

@ -172,11 +172,12 @@ class TestTaskManager:
await asyncio.sleep(0.1)
await task_manager.submit(_make_task("delayed"), db)
asyncio.create_task(delayed_submit())
background_task = asyncio.create_task(delayed_submit())
retrieved = await asyncio.wait_for(
task_manager.get_next("beacon-t"), timeout=2.0
)
assert retrieved.id == "delayed"
await background_task
def test_remove_queue(self, task_manager: TaskManager) -> None:
"""

View File

@ -9,10 +9,7 @@ from __future__ import annotations
import asyncio
import time
from dataclasses import (
dataclass,
field
)
from dataclasses import dataclass, field
from enum import StrEnum
from typing import Any

View File

@ -5,7 +5,6 @@ from pathlib import Path
from PIL import Image, PngImagePlugin
dest_dir = Path("tests/assets/test_images")
# Generate 45 PNG images with various metadata

View File

@ -15,7 +15,6 @@ from src.services.metadata_factory import MetadataFactory
from src.utils.display import print_metadata_table
from src.utils.get_target_files import get_target_files
console = Console()
log = logging.getLogger("metadata-scrubber")

View File

@ -27,7 +27,6 @@ from src.services.batch_processor import BatchProcessor
from src.utils.display import print_batch_summary
from src.utils.get_target_files import get_target_files
console = Console()
log = logging.getLogger("metadata-scrubber")

View File

@ -14,7 +14,6 @@ from rich.console import Console
from src.services.metadata_factory import MetadataFactory
from src.services.report_generator import ReportGenerator
console = Console()
log = logging.getLogger("metadata-scrubber")

View File

@ -91,7 +91,7 @@ class PngProcessor:
if key in ("icc_profile", "exif", "transparency", "gamma"):
continue
if isinstance(value, (str, bytes)):
if isinstance(value, str | bytes):
found_metadata = True
if isinstance(value, bytes):
try:

View File

@ -19,7 +19,6 @@ from rich.console import Console
from src.services.metadata_factory import MetadataFactory
log = logging.getLogger("metadata-scrubber")
console = Console()

View File

@ -15,7 +15,6 @@ from rich.text import Text
from src.utils.formatter import clean_value
console = Console()

View File

@ -22,7 +22,6 @@ from tests.conftest import (
get_xlsx_test_file,
)
runner = CliRunner()
# Test file paths (cross-platform)

View File

@ -22,7 +22,6 @@ from tests.conftest import (
get_xlsx_test_file,
)
runner = CliRunner()
# Test file paths (cross-platform)

View File

@ -13,7 +13,6 @@ from typer.testing import CliRunner
from src.main import app
runner = CliRunner()

View File

@ -44,7 +44,7 @@ def test_read_image_metadata(x):
assert handler.metadata == metadata
assert isinstance(metadata, dict)
if isinstance(handler, (JpegProcessor, PngProcessor)):
if isinstance(handler, JpegProcessor | PngProcessor):
assert (
handler.tags_to_delete is not None or handler.text_keys_to_delete is not None
)

View File

@ -91,7 +91,7 @@ class TestFilterBuilder:
Verify OR operator joins expressions
"""
result = (
FilterBuilder().port(80).port(443).build(operator = "or")
FilterBuilder().port(80).port(443).build(operator="or")
)
assert result == "port 80 or port 443"
@ -137,14 +137,14 @@ class TestCombineFilters:
"""
Verify multiple filters combine with AND
"""
result = combine_filters(["tcp", "port 80"], operator = "and")
result = combine_filters(["tcp", "port 80"], operator="and")
assert result == "(tcp) and (port 80)"
def test_combine_multiple_or(self):
"""
Verify multiple filters combine with OR
"""
result = combine_filters(["port 80", "port 443"], operator = "or")
result = combine_filters(["port 80", "port 443"], operator="or")
assert result == "(port 80) or (port 443)"

View File

@ -5,6 +5,7 @@ test_models.py
Basic happy path tests for data models
"""
import pytest
from netanal.models import (
@ -43,16 +44,16 @@ class TestPacketInfo:
Verify PacketInfo stores all fields correctly
"""
packet = PacketInfo(
timestamp = 1234567890.123,
src_ip = "192.168.1.1",
dst_ip = "192.168.1.2",
protocol = Protocol.TCP,
size = 1500,
src_port = 443,
dst_port = 54321,
timestamp=1234567890.123,
src_ip="192.168.1.1",
dst_ip="192.168.1.2",
protocol=Protocol.TCP,
size=1500,
src_port=443,
dst_port=54321,
)
assert packet.timestamp == 1234567890.123
assert packet.timestamp == pytest.approx(1234567890.123)
assert packet.src_ip == "192.168.1.1"
assert packet.dst_ip == "192.168.1.2"
assert packet.protocol == Protocol.TCP
@ -65,11 +66,11 @@ class TestPacketInfo:
Verify optional fields default to None
"""
packet = PacketInfo(
timestamp = 0.0,
src_ip = "10.0.0.1",
dst_ip = "10.0.0.2",
protocol = Protocol.ICMP,
size = 64,
timestamp=0.0,
src_ip="10.0.0.1",
dst_ip="10.0.0.2",
protocol=Protocol.ICMP,
size=64,
)
assert packet.src_port is None
@ -99,16 +100,16 @@ class TestCaptureConfig:
Verify custom configuration is stored correctly
"""
config = CaptureConfig(
interface = "eth0",
bpf_filter = "tcp port 80",
packet_count = 100,
timeout_seconds = 30.0,
interface="eth0",
bpf_filter="tcp port 80",
packet_count=100,
timeout_seconds=30.0,
)
assert config.interface == "eth0"
assert config.bpf_filter == "tcp port 80"
assert config.packet_count == 100
assert config.timeout_seconds == 30.0
assert config.timeout_seconds == pytest.approx(30.0)
class TestEndpointStats:
@ -119,7 +120,7 @@ class TestEndpointStats:
"""
Verify total_packets and total_bytes computed properties
"""
endpoint = EndpointStats(ip_address = "192.168.1.100")
endpoint = EndpointStats(ip_address="192.168.1.100")
endpoint.packets_sent = 50
endpoint.packets_received = 30
endpoint.bytes_sent = 5000
@ -149,36 +150,36 @@ class TestCaptureStatistics:
Verify duration_seconds computed property
"""
stats = CaptureStatistics(
start_time = 1000.0,
end_time = 1010.0,
start_time=1000.0,
end_time=1010.0,
)
assert stats.duration_seconds == 10.0
assert stats.duration_seconds == pytest.approx(10.0)
def test_average_bandwidth(self):
"""
Verify average_bandwidth calculation (bytes/second)
"""
stats = CaptureStatistics(
start_time = 1000.0,
end_time = 1010.0,
total_bytes = 10000,
start_time=1000.0,
end_time=1010.0,
total_bytes=10000,
)
assert stats.average_bandwidth == 1000.0
assert stats.average_bandwidth == pytest.approx(1000.0)
def test_protocol_percentages(self):
"""
Verify get_protocol_percentages returns correct distribution
"""
stats = CaptureStatistics(total_packets = 100)
stats = CaptureStatistics(total_packets=100)
stats.protocol_distribution[Protocol.TCP] = 70
stats.protocol_distribution[Protocol.UDP] = 30
percentages = stats.get_protocol_percentages()
assert percentages[Protocol.TCP] == 70.0
assert percentages[Protocol.UDP] == 30.0
assert percentages[Protocol.TCP] == pytest.approx(70.0)
assert percentages[Protocol.UDP] == pytest.approx(30.0)
def test_top_talkers(self):
"""
@ -186,15 +187,15 @@ class TestCaptureStatistics:
"""
stats = CaptureStatistics()
endpoint1 = EndpointStats(ip_address = "192.168.1.1")
endpoint1 = EndpointStats(ip_address="192.168.1.1")
endpoint1.bytes_sent = 1000
endpoint1.bytes_received = 500
endpoint2 = EndpointStats(ip_address = "192.168.1.2")
endpoint2 = EndpointStats(ip_address="192.168.1.2")
endpoint2.bytes_sent = 5000
endpoint2.bytes_received = 2000
endpoint3 = EndpointStats(ip_address = "192.168.1.3")
endpoint3 = EndpointStats(ip_address="192.168.1.3")
endpoint3.bytes_sent = 100
endpoint3.bytes_received = 50
@ -202,7 +203,7 @@ class TestCaptureStatistics:
stats.endpoints["192.168.1.2"] = endpoint2
stats.endpoints["192.168.1.3"] = endpoint3
top = stats.get_top_talkers(limit = 2)
top = stats.get_top_talkers(limit=2)
assert len(top) == 2
assert top[0].ip_address == "192.168.1.2"

View File

@ -28,7 +28,7 @@ SessionLocal = sessionmaker(
Base = declarative_base()
def get_db() -> Generator[Session, None, None]:
def get_db() -> Generator[Session]:
"""
FastAPI dependency for database sessions
"""

View File

@ -4,7 +4,8 @@ normalizer.py
"""
from datetime import datetime, UTC
from typing import Any, Callable
from typing import Any
from collections.abc import Callable
from app.models.LogEvent import SourceType