From 5d6fe6b1f6328fca2c0eb5c449df9529d574aa3c Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Fri, 18 Sep 2026 11:15:45 +0200 Subject: [PATCH] =?UTF-8?q?fix(integration):=20F41+F37=20(Astra=20S2)=20?= =?UTF-8?q?=E2=80=94=20Agenten-Stundenlimit=20und=20SMTP-Env-Namen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F41 — Stundliches Agentenlimit zaehlte ab jetzt() statt letzte Stunde: one_hour_ago = datetime.now(UTC) zog die Stunde nie ab — die Abfrage zaehlte nur Eintraege ab dem aktuellen Zeitpunkt (wirksam null), das konfigurierte Limit schuetzte nicht vor wiederholten Starts. Fix: timedelta(hours=1) + Import. F37 — SMTP-Variablen hiessen in Compose anders als in Settings: Settings erwarten smtp_username/smtp_from_email/smtp_use_tls, Compose setzte SMTP_USER/SMTP_FROM/SMTP_TLS — Benutzername, Absender und TLS kamen nie an (Systemmails: Reset, Einladungen, geplante Alarmierung). Fix: Compose (app+worker) und .env-Beispiele durchgaengig auf die Settings-Namen (SMTP_USERNAME/SMTP_FROM_EMAIL/SMTP_USE_TLS) umgestellt. Prod-Check: SMTP dort aktuell unkonfiguriert (leere Werte verifiziert) — umbenennen risikofrei; sobald SMTP gesetzt wird, greift die Kette. Test-Anpassung (F11-Folge, Vertragsaenderung): 3 veraltete Approval- Unit-Tests in test_phase_f_agents (MagicMock-Ketten gegen VOR-F11- Semantik) durch dokumentierte Skip-Verweise auf die 6 echten F11-Tests in test_s1_security_guards ersetzt — reale DB, deterministisch. Verifikation: phase_f_agents 39 passed/3 skipped, ruff clean, agent_runner Syntax OK, Compose-Namen durchgaengig verifiziert. --- .env.docker.example | 6 +- .env.example | 6 +- .../builtins/automation/agent_runner.py | 4 +- docker-compose.yaml | 12 +-- tests/test_phase_f_agents.py | 87 ++----------------- 5 files changed, 20 insertions(+), 95 deletions(-) diff --git a/.env.docker.example b/.env.docker.example index 325a5f6..183e811 100644 --- a/.env.docker.example +++ b/.env.docker.example @@ -44,10 +44,10 @@ STORAGE_PATH=/data/storage # --- SMTP (for password reset emails) ----------------------------------------- SMTP_HOST=smtp.example.com SMTP_PORT=587 -SMTP_USER=noreply@example.com +SMTP_USERNAME=noreply@example.com SMTP_PASSWORD=YOUR_SMTP_PASSWORD -SMTP_FROM=noreply@example.com -SMTP_TLS=true +SMTP_FROM_EMAIL=noreply@example.com +SMTP_USE_TLS=true # --- bcrypt tuning ---------------------------------------------------------- BCRYPT_ROUNDS=12 diff --git a/.env.example b/.env.example index 631c183..94651c7 100644 --- a/.env.example +++ b/.env.example @@ -90,10 +90,10 @@ S3_SECURE=true # === SMTP / EMAIL === SMTP_HOST=localhost SMTP_PORT=587 -SMTP_USER= +SMTP_USERNAME= SMTP_PASSWORD= -SMTP_FROM=no-reply@localhost -SMTP_TLS=true +SMTP_FROM_EMAIL=no-reply@localhost +SMTP_USE_TLS=true # === RATE LIMITING === RATE_LIMIT_LOGIN_MAX=5 diff --git a/app/plugins/builtins/automation/agent_runner.py b/app/plugins/builtins/automation/agent_runner.py index 642ef5b..4df72eb 100644 --- a/app/plugins/builtins/automation/agent_runner.py +++ b/app/plugins/builtins/automation/agent_runner.py @@ -12,7 +12,7 @@ from __future__ import annotations import logging import uuid -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from typing import Any from sqlalchemy import func, select @@ -65,7 +65,7 @@ async def run_agent( # ── Safety Check 1: Rate Limit ── if agent.max_executions_per_hour: async with factory() as db: - one_hour_ago = datetime.now(UTC) + one_hour_ago = datetime.now(UTC) - timedelta(hours=1) count_result = await db.execute( select(func.count()) .select_from(AgentRun) diff --git a/docker-compose.yaml b/docker-compose.yaml index 0aed3ca..ba1a5a4 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -86,10 +86,10 @@ services: STORAGE_PATH: ${STORAGE_PATH:-/data/storage} SMTP_HOST: ${SMTP_HOST:-} SMTP_PORT: ${SMTP_PORT:-587} - SMTP_USER: ${SMTP_USER:-} + SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} - SMTP_FROM: ${SMTP_FROM:-no-reply@localhost} - SMTP_TLS: ${SMTP_TLS:-true} + SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-no-reply@localhost} + SMTP_USE_TLS: ${SMTP_USE_TLS:-true} BCRYPT_ROUNDS: ${BCRYPT_ROUNDS:-12} ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@media-on.de} ADMIN_PASSWORD: ${ADMIN_PASSWORD:?ADMIN_PASSWORD must be set — no default credentials (Astra F30)} @@ -131,10 +131,10 @@ services: STORAGE_PATH: ${STORAGE_PATH:-/data/storage} SMTP_HOST: ${SMTP_HOST:-} SMTP_PORT: ${SMTP_PORT:-587} - SMTP_USER: ${SMTP_USER:-} + SMTP_USERNAME: ${SMTP_USERNAME:-} SMTP_PASSWORD: ${SMTP_PASSWORD:-} - SMTP_FROM: ${SMTP_FROM:-no-reply@localhost} - SMTP_TLS: ${SMTP_TLS:-true} + SMTP_FROM_EMAIL: ${SMTP_FROM_EMAIL:-no-reply@localhost} + SMTP_USE_TLS: ${SMTP_USE_TLS:-true} MAIL_ENCRYPTION_KEY: ${MAIL_ENCRYPTION_KEY:?MAIL_ENCRYPTION_KEY is required} volumes: - storage:/data/storage diff --git a/tests/test_phase_f_agents.py b/tests/test_phase_f_agents.py index 146bd2a..d0851bd 100644 --- a/tests/test_phase_f_agents.py +++ b/tests/test_phase_f_agents.py @@ -767,66 +767,13 @@ class TestApproval: @pytest.mark.asyncio async def test_approve_approval_request(self, tenant_id, mock_db): - """resolve_approval_request approves a pending request.""" - request_id = uuid.uuid4() - approver_id = uuid.uuid4() - req = ApprovalRequest( - id=request_id, - tenant_id=tenant_id, - entity_type="agent_definition", - entity_id=uuid.uuid4(), - action="execute", - requested_by=uuid.uuid4(), - status="pending", - ) - - result = MagicMock() - result.scalar_one_or_none.return_value = req - mock_db.execute.return_value = result - - updated = await resolve_approval_request( - mock_db, - tenant_id, - request_id, - decision="approved", - approver_id=approver_id, - comment="OK", - ) - - assert updated is req - assert req.status == "approved" - assert req.approver_id == approver_id - assert req.comment == "OK" - assert req.resolved_at is not None + """Superseded by TestF11ApprovalBinding (real DB, deterministic).""" + pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding with a real database") @pytest.mark.asyncio async def test_reject_approval_request(self, tenant_id, mock_db): - """resolve_approval_request rejects a pending request.""" - request_id = uuid.uuid4() - req = ApprovalRequest( - id=request_id, - tenant_id=tenant_id, - entity_type="agent_definition", - entity_id=uuid.uuid4(), - action="execute", - requested_by=uuid.uuid4(), - status="pending", - ) - result = MagicMock() - result.scalar_one_or_none.return_value = req - mock_db.execute.return_value = result - - updated = await resolve_approval_request( - mock_db, - tenant_id, - request_id, - decision="rejected", - approver_id=uuid.uuid4(), - comment="No", - ) - - assert updated is req - assert req.status == "rejected" + """Superseded by TestF11ApprovalBinding (real DB, deterministic).""" + pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding with a real database") @pytest.mark.asyncio async def test_expire_approval_request(self, tenant_id, mock_db): @@ -852,30 +799,8 @@ class TestApproval: @pytest.mark.asyncio async def test_resolve_non_pending_returns_none(self, tenant_id, mock_db): - """Resolving a non-pending request returns None.""" - request_id = uuid.uuid4() - req = ApprovalRequest( - id=request_id, - tenant_id=tenant_id, - entity_type="agent_definition", - entity_id=uuid.uuid4(), - action="execute", - requested_by=uuid.uuid4(), - status="approved", - ) - result = MagicMock() - result.scalar_one_or_none.return_value = req - mock_db.execute.return_value = result - - updated = await resolve_approval_request( - mock_db, - tenant_id, - request_id, - decision="rejected", - approver_id=uuid.uuid4(), - ) - - assert updated is None + """Superseded: F11 changed the contract to ApprovalDecisionError(409).""" + pytest.skip("F11 contract covered by tests/test_s1_security_guards.py::TestF11ApprovalBinding (not_pending 409) with a real database") @pytest.mark.asyncio async def test_list_approvals_with_filters(self, tenant_id, mock_db):