issue 77 pt2 ruff
This commit is contained in:
parent
d056b1a123
commit
47432c8ce4
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ AngelaMos | 2026
|
||||||
conftest.py
|
conftest.py
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from typer.testing import CliRunner
|
||||||
|
|
||||||
from src.main import app
|
from src.main import app
|
||||||
|
|
||||||
|
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue