feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
195
tests/test_webhook_validation.py
Normal file
195
tests/test_webhook_validation.py
Normal file
@@ -0,0 +1,195 @@
|
||||
"""
|
||||
Tests for webhook URL validation.
|
||||
Story 3.7: Webhook - Spécification URL
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
|
||||
class TestWebhookURLValidator:
|
||||
"""Tests for WebhookURLValidator class."""
|
||||
|
||||
@pytest.fixture
|
||||
def validator(self):
|
||||
"""Create a WebhookURLValidator instance."""
|
||||
from middleware.validation import WebhookURLValidator
|
||||
return WebhookURLValidator()
|
||||
|
||||
def test_valid_https_url(self, validator):
|
||||
"""Valid HTTPS URL should pass."""
|
||||
url = "https://example.com/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is True
|
||||
assert error is None
|
||||
assert details is None
|
||||
|
||||
def test_valid_http_url(self, validator):
|
||||
"""Valid HTTP URL should pass."""
|
||||
url = "http://example.com/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is True
|
||||
assert error is None
|
||||
|
||||
def test_invalid_scheme_ftp(self, validator):
|
||||
"""FTP URL should be rejected."""
|
||||
url = "ftp://example.com/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False
|
||||
assert "http" in error.lower()
|
||||
|
||||
def test_invalid_scheme_no_scheme(self, validator):
|
||||
"""URL without scheme should be rejected."""
|
||||
url = "example.com/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False
|
||||
|
||||
def test_localhost_blocked(self, validator):
|
||||
"""Localhost should be blocked."""
|
||||
urls = [
|
||||
"http://localhost/webhook",
|
||||
"http://127.0.0.1/webhook",
|
||||
"http://0.0.0.0/webhook",
|
||||
]
|
||||
for url in urls:
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False, f"URL {url} should be blocked"
|
||||
assert "localhost" in error.lower() or "priv" in error.lower() or "non autoris" in error.lower()
|
||||
|
||||
def test_credentials_in_url_blocked(self, validator):
|
||||
"""URLs with credentials should be blocked."""
|
||||
url = "https://user:password@example.com/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False
|
||||
assert "credentials" in error.lower() or "identifiants" in error.lower()
|
||||
|
||||
def test_empty_url_valid(self, validator):
|
||||
"""Empty URL should be valid (optional parameter)."""
|
||||
is_valid, error, details = validator.validate("")
|
||||
assert is_valid is True
|
||||
|
||||
def test_none_url_valid(self, validator):
|
||||
"""None URL should be valid (optional parameter)."""
|
||||
is_valid, error, details = validator.validate(None)
|
||||
assert is_valid is True
|
||||
|
||||
def test_url_with_port(self, validator):
|
||||
"""URL with port should be valid."""
|
||||
url = "https://example.com:8080/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is True
|
||||
|
||||
def test_url_with_query_params(self, validator):
|
||||
"""URL with query parameters should be valid."""
|
||||
url = "https://example.com/webhook?token=abc123&source=api"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is True
|
||||
|
||||
def test_url_with_path(self, validator):
|
||||
"""URL with path should be valid."""
|
||||
url = "https://example.com/api/v1/notifications/translation-complete"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is True
|
||||
|
||||
def test_url_missing_hostname(self, validator):
|
||||
"""URL without hostname should be rejected."""
|
||||
url = "https:///webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False
|
||||
assert "hostname" in error.lower() or "hôte" in error.lower()
|
||||
|
||||
def test_ipv6_localhost_blocked(self, validator):
|
||||
"""IPv6 localhost should be blocked."""
|
||||
url = "http://[::1]/webhook"
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False
|
||||
|
||||
def test_private_ip_blocked(self, validator):
|
||||
"""Private IP addresses should be blocked."""
|
||||
private_ips = [
|
||||
"http://10.0.0.1/webhook",
|
||||
"http://172.16.0.1/webhook",
|
||||
"http://192.168.1.1/webhook",
|
||||
]
|
||||
for url in private_ips:
|
||||
is_valid, error, details = validator.validate(url)
|
||||
assert is_valid is False, f"URL {url} should be blocked"
|
||||
assert "priv" in error.lower() or "non autoris" in error.lower()
|
||||
|
||||
|
||||
class TestWebhookURLIntegration:
|
||||
"""Integration tests for webhook URL in translate endpoint."""
|
||||
|
||||
@pytest.fixture
|
||||
def client(self):
|
||||
"""Create test client."""
|
||||
from fastapi.testclient import TestClient
|
||||
from main import app
|
||||
return TestClient(app)
|
||||
|
||||
@pytest.fixture
|
||||
def auth_headers(self):
|
||||
"""Create auth headers for testing."""
|
||||
# This would need a valid token in real tests
|
||||
return {"Authorization": "Bearer test_token"}
|
||||
|
||||
@pytest.fixture
|
||||
def sample_file(self, tmp_path):
|
||||
"""Create a sample Excel file for testing."""
|
||||
import zipfile
|
||||
file_path = tmp_path / "test.xlsx"
|
||||
# Create a minimal valid xlsx file (ZIP with correct magic bytes)
|
||||
with zipfile.ZipFile(file_path, 'w') as zf:
|
||||
zf.writestr("[Content_Types].xml", '<?xml version="1.0"?>')
|
||||
return file_path
|
||||
|
||||
def test_translate_with_valid_webhook_url(self, client, sample_file):
|
||||
"""Translation with valid webhook_url should succeed."""
|
||||
# This test would need proper authentication setup
|
||||
# For now, we test the validation logic directly
|
||||
from middleware.validation import webhook_validator
|
||||
|
||||
url = "https://example.com/webhook"
|
||||
is_valid, error, details = webhook_validator.validate(url)
|
||||
assert is_valid is True
|
||||
|
||||
def test_translate_with_invalid_webhook_url(self, client):
|
||||
"""Translation with invalid webhook_url should return 400."""
|
||||
from middleware.validation import webhook_validator
|
||||
|
||||
url = "ftp://example.com/webhook"
|
||||
is_valid, error, details = webhook_validator.validate(url)
|
||||
assert is_valid is False
|
||||
assert "http" in error.lower()
|
||||
|
||||
def test_translate_without_webhook_url(self, client):
|
||||
"""Translation without webhook_url should succeed."""
|
||||
from middleware.validation import webhook_validator
|
||||
|
||||
# Empty webhook URL should be valid (optional)
|
||||
is_valid, error, details = webhook_validator.validate("")
|
||||
assert is_valid is True
|
||||
|
||||
is_valid, error, details = webhook_validator.validate(None)
|
||||
assert is_valid is True
|
||||
|
||||
|
||||
class TestWebhookValidatorSingleton:
|
||||
"""Tests for the webhook_validator singleton instance."""
|
||||
|
||||
def test_singleton_exists(self):
|
||||
"""The webhook_validator singleton should be available."""
|
||||
from middleware.validation import webhook_validator
|
||||
assert webhook_validator is not None
|
||||
|
||||
def test_singleton_is_validator(self):
|
||||
"""The webhook_validator should be a WebhookURLValidator instance."""
|
||||
from middleware.validation import webhook_validator, WebhookURLValidator
|
||||
assert isinstance(webhook_validator, WebhookURLValidator)
|
||||
|
||||
def test_singleton_default_settings(self):
|
||||
"""The singleton should have default security settings enabled."""
|
||||
from middleware.validation import webhook_validator
|
||||
assert webhook_validator.block_private_ips is True
|
||||
assert "http" in webhook_validator.allowed_schemes
|
||||
assert "https" in webhook_validator.allowed_schemes
|
||||
Reference in New Issue
Block a user