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 fail-fast: false
matrix: matrix:
include: include:
- name: api-rate-limiter # Python (ruff) - Beginner
type: python - name: caesar-cipher
path: PROJECTS/advanced/api-rate-limiter type: ruff
- name: dns-lookup path: PROJECTS/beginner/caesar-cipher
type: python
path: PROJECTS/beginner/dns-lookup
- name: keylogger - name: keylogger
type: python type: ruff
path: PROJECTS/beginner/keylogger path: PROJECTS/beginner/keylogger
- name: docker-security-audit - name: dns-lookup
type: go type: ruff
path: PROJECTS/beginner/docker-security-audit 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 - name: bug-bounty-platform-frontend
type: biome type: biome
path: PROJECTS/advanced/bug-bounty-platform/frontend path: PROJECTS/advanced/bug-bounty-platform/frontend
@ -51,6 +86,10 @@ jobs:
- name: fullstack-template-frontend - name: fullstack-template-frontend
type: biome type: biome
path: TEMPLATES/fullstack-template/frontend path: TEMPLATES/fullstack-template/frontend
# Go
- name: docker-security-audit
type: go
path: PROJECTS/beginner/docker-security-audit
defaults: defaults:
run: run:
@ -60,28 +99,16 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
# Python Setup # Ruff Setup
- name: Set up Python - name: Set up Python
if: matrix.type == 'python' if: matrix.type == 'ruff'
uses: actions/setup-python@v5 uses: actions/setup-python@v5
with: with:
python-version: '3.12' python-version: '3.12'
- name: Cache pip dependencies - name: Install ruff
if: matrix.type == 'python' if: matrix.type == 'ruff'
uses: actions/cache@v4 run: pip install ruff
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]"
# Biome Setup # Biome Setup
- name: Setup Node.js - name: Setup Node.js
@ -118,24 +145,9 @@ jobs:
go-version: '1.21' go-version: '1.21'
cache-dependency-path: ${{ matrix.path }}/go.sum cache-dependency-path: ${{ matrix.path }}/go.sum
# Python Linting # Ruff 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
- name: Run ruff - name: Run ruff
if: matrix.type == 'python' if: matrix.type == 'ruff'
id: ruff id: ruff
run: | run: |
echo "Running ruff check..." echo "Running ruff check..."
@ -149,21 +161,6 @@ jobs:
cat ruff-output.txt cat ruff-output.txt
continue-on-error: true 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 # Biome Linting
- name: Run Biome - name: Run Biome
if: matrix.type == 'biome' if: matrix.type == 'biome'
@ -196,30 +193,14 @@ jobs:
cat golangci-output.txt cat golangci-output.txt
continue-on-error: true continue-on-error: true
# Create Summary for Python # Create Summary for Ruff
- name: Create Python Lint Summary - name: Create Ruff Lint Summary
if: matrix.type == 'python' && github.event_name == 'pull_request' if: matrix.type == 'ruff' && github.event_name == 'pull_request'
run: | run: |
{ {
echo "## Lint Results: ${{ matrix.name }}" echo "## Lint Results: ${{ matrix.name }}"
echo '' 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 if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then
echo '### Ruff: **Passed**' echo '### Ruff: **Passed**'
echo 'No ruff issues found.' echo 'No ruff issues found.'
@ -234,23 +215,7 @@ jobs:
fi fi
echo '' echo ''
# Mypy Status if [[ "${{ env.RUFF_PASSED }}" == "true" ]]; then
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
echo '---' echo '---'
echo '### All checks passed!' echo '### All checks passed!'
else else
@ -269,7 +234,6 @@ jobs:
echo "## Lint Results: ${{ matrix.name }}" echo "## Lint Results: ${{ matrix.name }}"
echo '' echo ''
# Biome Status
if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then
echo '### Biome: **Passed**' echo '### Biome: **Passed**'
echo 'No Biome issues found.' echo 'No Biome issues found.'
@ -284,7 +248,6 @@ jobs:
fi fi
echo '' echo ''
# Overall Summary
if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then if [[ "${{ env.BIOME_PASSED }}" == "true" ]]; then
echo '---' echo '---'
echo '### All checks passed!' echo '### All checks passed!'
@ -304,7 +267,6 @@ jobs:
echo "## Lint Results: ${{ matrix.name }}" echo "## Lint Results: ${{ matrix.name }}"
echo '' echo ''
# golangci-lint Status
if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then
echo '### golangci-lint: **Passed**' echo '### golangci-lint: **Passed**'
echo 'No golangci-lint issues found.' echo 'No golangci-lint issues found.'
@ -319,7 +281,6 @@ jobs:
fi fi
echo '' echo ''
# Overall Summary
if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then if [[ "${{ env.GOLANGCI_PASSED }}" == "true" ]]; then
echo '---' echo '---'
echo '### All checks passed!' echo '### All checks passed!'
@ -344,9 +305,9 @@ jobs:
# Exit with proper status # Exit with proper status
- name: Check lint status - name: Check lint status
run: | run: |
if [[ "${{ matrix.type }}" == "python" ]]; then if [[ "${{ matrix.type }}" == "ruff" ]]; then
if [[ "${{ env.PYLINT_PASSED }}" == "false" ]] || [[ "${{ env.RUFF_PASSED }}" == "false" ]] || [[ "${{ env.MYPY_PASSED }}" == "false" ]]; then if [[ "${{ env.RUFF_PASSED }}" == "false" ]]; then
echo "Python lint checks failed" echo "Ruff lint checks failed"
exit 1 exit 1
fi fi
elif [[ "${{ matrix.type }}" == "biome" ]]; then elif [[ "${{ matrix.type }}" == "biome" ]]; then

View File

@ -1,16 +1,95 @@
repos: repos:
# Python Backend Ruff Checks # Python Ruff Checks
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.0 rev: v0.15.1
hooks: hooks:
# Beginner projects
- id: ruff - id: ruff
name: ruff check (backend) name: ruff check (caesar-cipher)
args: [--fix, --exit-non-zero-on-fix] args: [--fix, --exit-non-zero-on-fix]
files: ^PROJECTS/api-security-scanner/backend/ files: ^PROJECTS/beginner/caesar-cipher/
exclude: ^PROJECTS/api-security-scanner/backend/(\.venv|__pycache__|\.pytest_cache)/ 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 # TODO: add golangci checks for GO projects

View File

@ -115,7 +115,7 @@ class RuleEngine:
for trule in _THRESHOLD_RULES: for trule in _THRESHOLD_RULES:
value = features.get(trule.feature_key, 0) 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)) matched.append((trule.name, trule.score))
if not matched: if not matched:

View File

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

View File

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

View File

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

View File

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

View File

@ -67,7 +67,7 @@ class TestIPExtractor:
def test_ipv6_custom_prefix_length(self) -> None: def test_ipv6_custom_prefix_length(self) -> None:
extractor = IPExtractor(ipv6_prefix_length = 48) extractor = IPExtractor(ipv6_prefix_length = 48)
request = RequestFactory.create(client_ip = TEST_IP_V6) 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 assert "::" in normalized_ip or normalized_ip != TEST_IP_V6
@ -75,7 +75,7 @@ class TestIPExtractor:
extractor = IPExtractor() extractor = IPExtractor()
ipv4_mapped = "::ffff:192.168.1.100" ipv4_mapped = "::ffff:192.168.1.100"
request = RequestFactory.create(client_ip = ipv4_mapped) 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" assert normalized_ip == "192.168.1.100"
@ -323,7 +323,7 @@ class TestAuthExtractor:
extractor = AuthExtractor(hash_identifiers = False) extractor = AuthExtractor(hash_identifiers = False)
request = RequestFactory.with_auth( request = RequestFactory.with_auth(
auth_type = "bearer", auth_type = "bearer",
token = "invalid" token = "invalid" # noqa: S106
) )
identifier = extractor.extract(request) 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.exceptions import EnhanceYourCalm, HTTP_420_ENHANCE_YOUR_CALM
from fastapi_420.limiter import RateLimiter from fastapi_420.limiter import RateLimiter
from fastapi_420.storage import MemoryStorage from fastapi_420.storage import MemoryStorage
from fastapi_420.types import Algorithm, FingerprintLevel from fastapi_420.types import Algorithm
from tests.conftest import ( from tests.conftest import (
WINDOW_MINUTE,
DEFAULT_LIMIT_REQUESTS,
RequestFactory, RequestFactory,
) )

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,6 @@ from pathlib import Path
from PIL import Image, PngImagePlugin from PIL import Image, PngImagePlugin
dest_dir = Path("tests/assets/test_images") dest_dir = Path("tests/assets/test_images")
# Generate 45 PNG images with various metadata # 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.display import print_metadata_table
from src.utils.get_target_files import get_target_files from src.utils.get_target_files import get_target_files
console = Console() console = Console()
log = logging.getLogger("metadata-scrubber") 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.display import print_batch_summary
from src.utils.get_target_files import get_target_files from src.utils.get_target_files import get_target_files
console = Console() console = Console()
log = logging.getLogger("metadata-scrubber") 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.metadata_factory import MetadataFactory
from src.services.report_generator import ReportGenerator from src.services.report_generator import ReportGenerator
console = Console() console = Console()
log = logging.getLogger("metadata-scrubber") log = logging.getLogger("metadata-scrubber")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -44,7 +44,7 @@ def test_read_image_metadata(x):
assert handler.metadata == metadata assert handler.metadata == metadata
assert isinstance(metadata, dict) assert isinstance(metadata, dict)
if isinstance(handler, (JpegProcessor, PngProcessor)): if isinstance(handler, JpegProcessor | PngProcessor):
assert ( assert (
handler.tags_to_delete is not None or handler.text_keys_to_delete is not None 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 Verify OR operator joins expressions
""" """
result = ( result = (
FilterBuilder().port(80).port(443).build(operator = "or") FilterBuilder().port(80).port(443).build(operator="or")
) )
assert result == "port 80 or port 443" assert result == "port 80 or port 443"
@ -137,14 +137,14 @@ class TestCombineFilters:
""" """
Verify multiple filters combine with AND 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)" assert result == "(tcp) and (port 80)"
def test_combine_multiple_or(self): def test_combine_multiple_or(self):
""" """
Verify multiple filters combine with OR 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)" assert result == "(port 80) or (port 443)"

View File

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

View File

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

View File

@ -4,7 +4,8 @@ normalizer.py
""" """
from datetime import datetime, UTC 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 from app.models.LogEvent import SourceType