issue 77 pt2 ruff
This commit is contained in:
parent
d056b1a123
commit
47432c8ce4
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ AngelaMos | 2026
|
|||
conftest.py
|
||||
"""
|
||||
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ AngelaMos | 2026
|
|||
test_encoding.py
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from core.encoding import decode, encode, xor_bytes
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import pytest
|
|||
from core.models import CommandType
|
||||
from core.protocol import Message, MessageType, pack, unpack
|
||||
|
||||
|
||||
TEST_KEY = "test-protocol-key"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ from rich.console import Console
|
|||
|
||||
from src.services.metadata_factory import MetadataFactory
|
||||
|
||||
|
||||
log = logging.getLogger("metadata-scrubber")
|
||||
console = Console()
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ from rich.text import Text
|
|||
|
||||
from src.utils.formatter import clean_value
|
||||
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ from tests.conftest import (
|
|||
get_xlsx_test_file,
|
||||
)
|
||||
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
# Test file paths (cross-platform)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ from tests.conftest import (
|
|||
get_xlsx_test_file,
|
||||
)
|
||||
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
# Test file paths (cross-platform)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ from typer.testing import CliRunner
|
|||
|
||||
from src.main import app
|
||||
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue