140 lines
4.7 KiB
Python
140 lines
4.7 KiB
Python
|
|
"""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
|