fix(i-d): mail-API-Brueche behoben — signatures PATCH/DELETE + labels DELETE im Backend ergaenzt, drafts PATCH->PUT
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
Root-Cause: Frontend-Komponenten (SignatureManager, LabelManager) rufen Endpunkte auf die das Backend nie hatte (404/405 in Production). Anders als ai/sessions sind diese Funktionen ECHT in Komponenten eingebunden -> Backend-Routen nachbestellt statt Frontend-Calls zu loeschen:
(1) PATCH+DELETE /mail/signatures/{id}: MailSignatureUpdate-Schema neu, Tenant-Scoped + Owner-Check (403 bei fremder Signatur), is_default-Exklusivitaet beim Setzen. (2) DELETE /mail/labels/{id}: gleicher Stil. (3) updateDraft Frontend: apiPatch -> apiPut (Backend hat PUT /drafts/{id} bereits). Beweistest tests/test_mail_sig_label_routes.py 5/5 gruen (PATCH-Werte, DELETE+Liste-leer, 404-Faelle).
Verifikation: create_app registriert beide neuen Routen (563 total); ruff clean; tsc exit=0.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Tests for the mail signature PATCH/DELETE and label DELETE endpoints (I-D fix).
|
||||
|
||||
Proves the previously missing routes now work end-to-end:
|
||||
- PATCH /api/v1/mail/signatures/{id} -> 200 with updated values
|
||||
- DELETE /api/v1/mail/signatures/{id} -> 204, row gone
|
||||
- DELETE /api/v1/mail/labels/{id} -> 204, row gone
|
||||
- 404 for unknown IDs
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from httpx import ASGITransport, AsyncClient
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
||||
|
||||
from app.core.db import close_engine, reset_engine_for_testing
|
||||
from app.core.permission_registry import init_permission_registry
|
||||
from app.core.service_container import get_container
|
||||
from app.main import create_app
|
||||
from app.plugins.builtins.mail import MailPlugin
|
||||
from app.plugins.registry import reset_registry_for_testing
|
||||
from app.services.plugin_service import reset_plugin_service_for_testing
|
||||
from tests.conftest import ORIGIN_HEADER, login_client, seed_tenant_and_users
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def mail_app(engine: AsyncEngine, redis_client):
|
||||
"""FastAPI app with Mail plugin registered."""
|
||||
reset_engine_for_testing(engine)
|
||||
app = create_app()
|
||||
registry = reset_registry_for_testing()
|
||||
registry.initialize(engine, app)
|
||||
init_permission_registry(active_plugin_names={"mail"})
|
||||
container = get_container()
|
||||
await container.initialize()
|
||||
registry.register_plugin(MailPlugin())
|
||||
reset_plugin_service_for_testing(registry)
|
||||
sf = async_sessionmaker(bind=engine, expire_on_commit=False, class_=AsyncSession)
|
||||
async with sf() as session:
|
||||
await registry.install(session, "mail")
|
||||
await registry.activate(session, "mail")
|
||||
await session.commit()
|
||||
yield app
|
||||
await close_engine()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def mail_client(mail_app) -> AsyncClient:
|
||||
transport = ASGITransport(app=mail_app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as c:
|
||||
yield c
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def authed_client(
|
||||
mail_client: AsyncClient, db_session: AsyncSession
|
||||
) -> AsyncClient:
|
||||
seed = await seed_tenant_and_users(db_session)
|
||||
assert seed is not None
|
||||
await login_client(mail_client, "admin@tenanta.com")
|
||||
return mail_client
|
||||
|
||||
|
||||
async def _create_signature(client: AsyncClient) -> dict:
|
||||
resp = await client.post(
|
||||
"/api/v1/mail/signatures",
|
||||
json={"name": "Old Name", "body_html": "<p>old</p>", "is_default": False},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()
|
||||
|
||||
|
||||
async def _create_label(client: AsyncClient) -> dict:
|
||||
resp = await client.post(
|
||||
"/api/v1/mail/labels",
|
||||
json={"name": "Temp Label", "color": "#ff0000"},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 201, resp.text
|
||||
return resp.json()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_signature(authed_client: AsyncClient):
|
||||
sig = await _create_signature(authed_client)
|
||||
resp = await authed_client.patch(
|
||||
f"/api/v1/mail/signatures/{sig['id']}",
|
||||
json={"name": "New Name", "body_html": "<p>new</p>", "is_default": True},
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 200, resp.text
|
||||
data = resp.json()
|
||||
assert data["name"] == "New Name"
|
||||
assert data["body_html"] == "<p>new</p>"
|
||||
assert data["is_default"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_signature(authed_client: AsyncClient):
|
||||
sig = await _create_signature(authed_client)
|
||||
resp = await authed_client.delete(
|
||||
f"/api/v1/mail/signatures/{sig['id']}",
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 204, resp.text
|
||||
lst = await authed_client.get("/api/v1/mail/signatures", headers=ORIGIN_HEADER)
|
||||
assert all(s["id"] != sig["id"] for s in lst.json())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_label(authed_client: AsyncClient):
|
||||
label = await _create_label(authed_client)
|
||||
resp = await authed_client.delete(
|
||||
f"/api/v1/mail/labels/{label['id']}",
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 204, resp.text
|
||||
lst = await authed_client.get("/api/v1/mail/labels", headers=ORIGIN_HEADER)
|
||||
assert all(lb["id"] != label["id"] for lb in lst.json())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_unknown_signature_404(authed_client: AsyncClient):
|
||||
resp = await authed_client.delete(
|
||||
"/api/v1/mail/signatures/00000000-0000-0000-0000-000000000000",
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 404, resp.text
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_unknown_label_404(authed_client: AsyncClient):
|
||||
resp = await authed_client.delete(
|
||||
"/api/v1/mail/labels/00000000-0000-0000-0000-000000000000",
|
||||
headers=ORIGIN_HEADER,
|
||||
)
|
||||
assert resp.status_code == 404, resp.text
|
||||
Reference in New Issue
Block a user