mirror of
https://github.com/suitenumerique/messages.git
synced 2026-08-17 21:25:41 +02:00
🚨(lint) lint subprojects
This commit is contained in:
@@ -48,14 +48,20 @@ def main() -> None:
|
||||
|
||||
parsed = parse_email(raw)
|
||||
inline = next(
|
||||
(a for a in parsed.get("attachments") or [] if a.get("disposition") == "inline"),
|
||||
(
|
||||
a
|
||||
for a in parsed.get("attachments") or []
|
||||
if a.get("disposition") == "inline"
|
||||
),
|
||||
None,
|
||||
)
|
||||
assert inline is not None, "expected an inline attachment on round-trip"
|
||||
assert inline.get("cid") == CID, (
|
||||
f"cid did not round-trip: composed={CID!r}, parsed={inline.get('cid')!r}"
|
||||
)
|
||||
print(f"OK: inline image '{inline['name']}' round-tripped with cid <{inline['cid']}>")
|
||||
print(
|
||||
f"OK: inline image '{inline['name']}' round-tripped with cid <{inline['cid']}>"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -109,6 +109,9 @@ select = [
|
||||
# and embed control characters in defense-matrix fixtures (PLE2502).
|
||||
"tests/**" = ["S", "SLF", "PLC0415", "PLE2502"]
|
||||
|
||||
# CLI examples use print() for output and assert for self-checking.
|
||||
"examples/**" = ["T20", "S101"]
|
||||
|
||||
# ──────────────────────────── ty (PEP 561 typecheck) ────────────────────────────
|
||||
# Static typecheck via Astral's ``ty`` — single Rust binary, no Node,
|
||||
# no Python deps. The shape contracts (``types.py``) and the public
|
||||
|
||||
@@ -296,9 +296,7 @@ def main() -> int:
|
||||
_ok("cross-realm role_id (master 'admin' role) → 404")
|
||||
|
||||
# ────────────────────── authentication ──────────────────────
|
||||
r = _post_with_token(
|
||||
None, {"role_id": created_role_id, "usernames": usernames}
|
||||
)
|
||||
r = _post_with_token(None, {"role_id": created_role_id, "usernames": usernames})
|
||||
assert r.status_code == 401
|
||||
_ok("no Authorization header → 401")
|
||||
|
||||
|
||||
@@ -34,7 +34,9 @@ def wait_for_rspamd():
|
||||
)
|
||||
# If we get a response (even if it's an error about empty message), rspamd is up
|
||||
if response.status_code in (200, 400, 401, 403):
|
||||
logger.info(f"Rspamd is ready (checkv2 check returned {response.status_code})")
|
||||
logger.info(
|
||||
f"Rspamd is ready (checkv2 check returned {response.status_code})"
|
||||
)
|
||||
return
|
||||
last_error = f"Unexpected status code: {response.status_code}"
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
@@ -72,4 +74,3 @@ def wait_for_rspamd():
|
||||
f"last error: {last_error}, retrying..."
|
||||
)
|
||||
time.sleep(0.2)
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"""Simple tests for rspamd API."""
|
||||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
from conftest import RSPAMD_URL, RSPAMD_AUTH
|
||||
|
||||
|
||||
def test_rspamd_health(wait_for_rspamd):
|
||||
"""Test that rspamd is running and accessible."""
|
||||
# Use the controller endpoint for health check
|
||||
@@ -46,7 +44,9 @@ def test_rspamd_check_empty_message(wait_for_rspamd):
|
||||
assert result["action"] in ("reject", "add header", "greylist", "no action")
|
||||
assert isinstance(result["score"], (int, float))
|
||||
# required_score can be None in some rspamd configurations
|
||||
assert result["required_score"] is None or isinstance(result["required_score"], (int, float))
|
||||
assert result["required_score"] is None or isinstance(
|
||||
result["required_score"], (int, float)
|
||||
)
|
||||
|
||||
|
||||
def test_rspamd_check_simple_message(wait_for_rspamd):
|
||||
@@ -87,4 +87,3 @@ This is a test email body.
|
||||
else:
|
||||
# If no required_score, just verify action is valid
|
||||
assert result["action"] in ("reject", "add header", "greylist", "no action")
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ class DeliveryMilter(Milter.Base):
|
||||
self.rcpttos.append(clean_to)
|
||||
return Milter.CONTINUE
|
||||
|
||||
except Exception:
|
||||
except Exception: # noqa: BLE001
|
||||
# Exception during validation - temporary failure
|
||||
return Milter.TEMPFAIL
|
||||
|
||||
@@ -150,7 +150,7 @@ class DeliveryMilter(Milter.Base):
|
||||
else:
|
||||
return Milter.TEMPFAIL
|
||||
|
||||
except Exception:
|
||||
except Exception: # noqa: BLE001
|
||||
return Milter.TEMPFAIL
|
||||
|
||||
def close(self):
|
||||
@@ -165,7 +165,7 @@ class DeliveryMilter(Milter.Base):
|
||||
|
||||
def main():
|
||||
"""Run the milter server"""
|
||||
print("Starting delivery milter...")
|
||||
print("Starting delivery milter...") # noqa: T201
|
||||
|
||||
# Set the socket for milter communication
|
||||
# Use Unix socket for better performance and security
|
||||
@@ -179,7 +179,7 @@ def main():
|
||||
try:
|
||||
os.setgid(grp.getgrnam("postfix").gr_gid)
|
||||
except (KeyError, OSError) as e:
|
||||
print(f"Warning: could not set gid to postfix: {e}", file=sys.stderr)
|
||||
print(f"Warning: could not set gid to postfix: {e}", file=sys.stderr) # noqa: T201
|
||||
os.umask(0o117)
|
||||
|
||||
# Register our milter class
|
||||
@@ -189,15 +189,15 @@ def main():
|
||||
flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS
|
||||
Milter.set_flags(flags)
|
||||
|
||||
print(f"Milter listening on {socket_path}")
|
||||
print(f"Milter listening on {socket_path}") # noqa: T201
|
||||
|
||||
try:
|
||||
# Start the milter
|
||||
Milter.runmilter("delivery_milter", socket_path, timeout=240)
|
||||
except KeyboardInterrupt:
|
||||
print("Milter shutting down...")
|
||||
except Exception as e:
|
||||
print(f"Milter error: {e}")
|
||||
print("Milter shutting down...") # noqa: T201
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"Milter error: {e}") # noqa: T201
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ def test_simple_email_delivery_with_multiple_recipients(mock_api_server, smtp_cl
|
||||
assert len(mock_api_server.received_emails) == 1
|
||||
|
||||
email = mock_api_server.received_emails[0]
|
||||
assert set(email["metadata"]["original_recipients"]) == set(["test@example.com"])
|
||||
assert set(email["metadata"]["original_recipients"]) == {"test@example.com"}
|
||||
assert email["metadata"]["sender"] == "sender@example.com"
|
||||
assert email["email"]["subject"] == "Simple Test Email"
|
||||
assert email["email"]["from"] == "sender@example.com"
|
||||
@@ -112,9 +112,10 @@ def test_simple_email_delivery_with_multiple_recipients(mock_api_server, smtp_cl
|
||||
assert len(mock_api_server.received_emails) == 1
|
||||
|
||||
email = mock_api_server.received_emails[0]
|
||||
assert set(email["metadata"]["original_recipients"]) == set(
|
||||
["test@example.com", "test2@example.com"]
|
||||
)
|
||||
assert set(email["metadata"]["original_recipients"]) == {
|
||||
"test@example.com",
|
||||
"test2@example.com",
|
||||
}
|
||||
assert email["metadata"]["sender"] == "sender@example.com"
|
||||
assert email["email"]["subject"] == "Simple Test Email"
|
||||
assert email["email"]["from"] == "sender@example.com"
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import pytest
|
||||
import smtplib
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import smtplib
|
||||
import time
|
||||
from email.parser import BytesParser
|
||||
|
||||
import pytest
|
||||
from aiosmtpd.controller import Controller
|
||||
from aiosmtpd.handlers import Message
|
||||
from aiosmtpd.smtp import AuthResult, LoginPassword
|
||||
from email.parser import BytesParser
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import logging
|
||||
import base64
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import logging
|
||||
from email.mime.application import MIMEApplication
|
||||
from email.mime.image import MIMEImage
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,7 +30,7 @@ def test_send_simple_text_email(smtp_client, mock_smtp_server):
|
||||
|
||||
# Give some time for the message to be relayed and received by the mock server
|
||||
max_retries = 10
|
||||
for attempt in range(max_retries):
|
||||
for _attempt in range(max_retries):
|
||||
if len(mock_smtp_server.get_messages()) > 0:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
@@ -65,7 +65,7 @@ def test_send_simple_text_email_localhost(smtp_client, mock_smtp_server):
|
||||
|
||||
# Give some time for the message to be relayed and received by the mock server
|
||||
max_retries = 10
|
||||
for attempt in range(max_retries):
|
||||
for _attempt in range(max_retries):
|
||||
if len(mock_smtp_server.get_messages()) > 0:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import pytest
|
||||
import time
|
||||
import logging
|
||||
import time
|
||||
from email.message import EmailMessage
|
||||
from email.parser import BytesParser
|
||||
from email.policy import default as default_policy
|
||||
|
||||
import pytest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Define a sample raw MIME message
|
||||
@@ -49,7 +50,7 @@ def test_mime_message_unmodified(smtp_client, mock_smtp_server):
|
||||
# sendmail expects bytes for the message
|
||||
smtp_client.sendmail(sender, recipient, original_bytes)
|
||||
logger.info("Raw message sent successfully via smtp_client.")
|
||||
except Exception as e:
|
||||
except Exception as e: # noqa: BLE001
|
||||
logger.error(f"Failed to send raw message: {e}")
|
||||
pytest.fail(f"SMTP sendmail failed: {e}")
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import pytest
|
||||
import smtplib
|
||||
import logging
|
||||
import os
|
||||
import smtplib
|
||||
|
||||
import pytest
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
import pytest
|
||||
import smtplib
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import ssl
|
||||
import struct
|
||||
import socks
|
||||
from aiosmtpd.controller import Controller
|
||||
from aiosmtpd.handlers import Message
|
||||
from urllib.parse import urlparse
|
||||
from dataclasses import dataclass
|
||||
from email.parser import BytesParser
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Parse SOCKS proxy environment variables
|
||||
from urllib.parse import urlparse
|
||||
from dataclasses import dataclass
|
||||
|
||||
@dataclass
|
||||
class ProxyConfig:
|
||||
@@ -27,6 +22,7 @@ class ProxyConfig:
|
||||
host: str = "localhost"
|
||||
port: int = 1080
|
||||
|
||||
|
||||
def parse_proxy_env(proxy_env):
|
||||
"""Parse SOCKS_PROXY1 or SOCKS_PROXY2 environment variable
|
||||
Format: username:password@host:port
|
||||
@@ -36,7 +32,7 @@ def parse_proxy_env(proxy_env):
|
||||
|
||||
try:
|
||||
# Add scheme to make it a valid URL for urlparse
|
||||
if not proxy_env.startswith(('http://', 'https://', 'socks://')):
|
||||
if not proxy_env.startswith(("http://", "https://", "socks://")):
|
||||
proxy_env = f"socks://{proxy_env}"
|
||||
|
||||
parsed = urlparse(proxy_env)
|
||||
@@ -45,11 +41,12 @@ def parse_proxy_env(proxy_env):
|
||||
username=parsed.username,
|
||||
password=parsed.password,
|
||||
host=parsed.hostname or "localhost",
|
||||
port=parsed.port or 1080
|
||||
port=parsed.port or 1080,
|
||||
)
|
||||
except Exception:
|
||||
return ProxyConfig()
|
||||
|
||||
|
||||
# Parse both proxy configurations
|
||||
PROXY1_CONFIG = parse_proxy_env(os.getenv("SOCKS_PROXY1"))
|
||||
PROXY2_CONFIG = parse_proxy_env(os.getenv("SOCKS_PROXY2"))
|
||||
@@ -58,15 +55,16 @@ PROXY2_CONFIG = parse_proxy_env(os.getenv("SOCKS_PROXY2"))
|
||||
def get_container_ip():
|
||||
"""Get the container's IP address automatically"""
|
||||
try:
|
||||
result = subprocess.run(['hostname', '-I'], capture_output=True, text=True)
|
||||
result = subprocess.run(["hostname", "-I"], capture_output=True, text=True)
|
||||
# hostname -I returns space-separated IPs, first one is usually the main one
|
||||
return result.stdout.strip().split()[0]
|
||||
except:
|
||||
except Exception:
|
||||
return "127.0.0.1" # fallback
|
||||
|
||||
|
||||
class MessageStore:
|
||||
"""Simple storage for received email messages"""
|
||||
|
||||
def __init__(self):
|
||||
self.messages = []
|
||||
|
||||
@@ -95,6 +93,7 @@ class MessageStore:
|
||||
|
||||
class MockSMTPHandler(Message):
|
||||
"""Handle SMTP messages and store them"""
|
||||
|
||||
def __init__(self, message_store):
|
||||
super().__init__()
|
||||
self.message_store = message_store
|
||||
@@ -123,6 +122,7 @@ class MockSMTPHandler(Message):
|
||||
|
||||
class MockSMTPServer:
|
||||
"""Mock SMTP server for testing"""
|
||||
|
||||
def __init__(self, host="0.0.0.0", port=2525):
|
||||
self.host = host
|
||||
self.port = port
|
||||
@@ -143,18 +143,35 @@ class MockSMTPServer:
|
||||
return self.message_store.get_messages()
|
||||
|
||||
|
||||
def create_proxied_socket(proxy_host, proxy_port, target_host, target_port, username=None, password=None, timeout=5):
|
||||
def create_proxied_socket(
|
||||
proxy_host,
|
||||
proxy_port,
|
||||
target_host,
|
||||
target_port,
|
||||
username=None,
|
||||
password=None,
|
||||
timeout=5,
|
||||
):
|
||||
"""Create a socket connected through a SOCKS proxy"""
|
||||
proxy = socks.socksocket()
|
||||
if type(timeout) in {int, float}:
|
||||
proxy.settimeout(timeout)
|
||||
proxy.set_proxy(socks.PROXY_TYPE_SOCKS5, proxy_host, proxy_port, rdns=False, username=username, password=password)
|
||||
proxy.set_proxy(
|
||||
socks.PROXY_TYPE_SOCKS5,
|
||||
proxy_host,
|
||||
proxy_port,
|
||||
rdns=False,
|
||||
username=username,
|
||||
password=password,
|
||||
)
|
||||
proxy.connect((target_host, target_port))
|
||||
|
||||
return proxy
|
||||
|
||||
|
||||
class SOCKSClient:
|
||||
"""SOCKS client for testing"""
|
||||
|
||||
def __init__(self, proxy_host, proxy_port, username=None, password=None):
|
||||
self.proxy_host = proxy_host
|
||||
self.proxy_port = proxy_port
|
||||
@@ -170,7 +187,7 @@ class SOCKSClient:
|
||||
target_port,
|
||||
self.username,
|
||||
self.password,
|
||||
timeout
|
||||
timeout,
|
||||
)
|
||||
sock.close()
|
||||
return True
|
||||
@@ -192,7 +209,7 @@ def socks_client():
|
||||
proxy_host=PROXY1_CONFIG.host,
|
||||
proxy_port=PROXY1_CONFIG.port,
|
||||
username=PROXY1_CONFIG.username,
|
||||
password=PROXY1_CONFIG.password
|
||||
password=PROXY1_CONFIG.password,
|
||||
)
|
||||
|
||||
|
||||
@@ -203,7 +220,7 @@ def socks_client_proxy2():
|
||||
proxy_host=PROXY2_CONFIG.host,
|
||||
proxy_port=PROXY2_CONFIG.port,
|
||||
username=PROXY2_CONFIG.username,
|
||||
password=PROXY2_CONFIG.password
|
||||
password=PROXY2_CONFIG.password,
|
||||
)
|
||||
|
||||
|
||||
@@ -216,7 +233,7 @@ def smtp_client_direct():
|
||||
|
||||
try:
|
||||
client.quit()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -230,9 +247,9 @@ class ProxySMTP(smtplib.SMTP):
|
||||
# This makes it simpler for SMTP_SSL to use the SMTP connect code
|
||||
# and just alter the socket connection bit.
|
||||
if timeout is not None and not timeout:
|
||||
raise ValueError('Non-blocking socket (timeout=0) is not supported')
|
||||
raise ValueError("Non-blocking socket (timeout=0) is not supported")
|
||||
if self.debuglevel > 0:
|
||||
self._print_debug('connect: to', (host, port), self.source_address)
|
||||
self._print_debug("connect: to", (host, port), self.source_address)
|
||||
|
||||
return create_proxied_socket(
|
||||
self.socks_client.proxy_host,
|
||||
@@ -241,7 +258,7 @@ class ProxySMTP(smtplib.SMTP):
|
||||
port,
|
||||
self.socks_client.username,
|
||||
self.socks_client.password,
|
||||
timeout
|
||||
timeout,
|
||||
)
|
||||
|
||||
|
||||
@@ -259,6 +276,5 @@ def smtp_client_via_proxy(socks_client):
|
||||
|
||||
try:
|
||||
client.quit()
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -61,6 +61,9 @@ def test_smtp_connection_via_proxy(smtp_client_via_proxy, mock_smtp_server):
|
||||
connection_info = messages[0].get("connection_info", {})
|
||||
logger.info(f"Proxy SMTP connection info: {connection_info}")
|
||||
|
||||
assert connection_info["peer_host"] != "127.0.0.1", "Proxy SMTP connection should not be direct"
|
||||
assert connection_info["peer_host"] != "127.0.0.1", (
|
||||
"Proxy SMTP connection should not be direct"
|
||||
)
|
||||
|
||||
|
||||
# TODO: stress test with https://pypi.org/project/pytest-run-parallel/ ?
|
||||
@@ -19,7 +19,7 @@ def test_socks_authentication_invalid_password(socks_client):
|
||||
proxy_host=socks_client.proxy_host,
|
||||
proxy_port=socks_client.proxy_port,
|
||||
username=socks_client.username,
|
||||
password="wrong_password"
|
||||
password="wrong_password",
|
||||
)
|
||||
|
||||
result = client.test_connection("8.8.8.8", 53)
|
||||
@@ -33,7 +33,7 @@ def test_socks_authentication_invalid_username(socks_client):
|
||||
proxy_host=socks_client.proxy_host,
|
||||
proxy_port=socks_client.proxy_port,
|
||||
username="wrong_username",
|
||||
password=socks_client.password
|
||||
password=socks_client.password,
|
||||
)
|
||||
|
||||
result = client.test_connection("8.8.8.8", 53)
|
||||
@@ -44,8 +44,7 @@ def test_socks_authentication_no_credentials(socks_client):
|
||||
"""Test SOCKS connection without authentication (should fail)"""
|
||||
# Create client without credentials using existing fixture
|
||||
client = SOCKSClient(
|
||||
proxy_host=socks_client.proxy_host,
|
||||
proxy_port=socks_client.proxy_port
|
||||
proxy_host=socks_client.proxy_host, proxy_port=socks_client.proxy_port
|
||||
)
|
||||
|
||||
result = client.test_connection("8.8.8.8", 53)
|
||||
@@ -59,7 +58,7 @@ def test_socks_authentication_failure_handling(socks_client):
|
||||
proxy_host=socks_client.proxy_host,
|
||||
proxy_port=socks_client.proxy_port,
|
||||
username="invalid_user",
|
||||
password="invalid_pass"
|
||||
password="invalid_pass",
|
||||
)
|
||||
|
||||
result = client.test_connection("8.8.8.8", 53)
|
||||
@@ -69,7 +68,9 @@ def test_socks_authentication_failure_handling(socks_client):
|
||||
# Connection Tests
|
||||
def test_socks_proxy_connection_establishment(socks_client):
|
||||
"""Test that SOCKS proxy connection can be established"""
|
||||
assert socks_client.test_connection("8.8.8.8", 53), "SOCKS connection should be established successfully"
|
||||
assert socks_client.test_connection("8.8.8.8", 53), (
|
||||
"SOCKS connection should be established successfully"
|
||||
)
|
||||
|
||||
|
||||
def test_socks_proxy_connection_refused(socks_client):
|
||||
|
||||
Reference in New Issue
Block a user