fix: resolve email settings architecture violations and add tests/docs
- Fix API-002 in admin/settings.py: use service layer for DB delete - Fix API-001/API-003 in vendor/email_settings.py: add Pydantic response models, remove HTTPException raises - Fix SVC-002/SVC-006 in vendor_email_settings_service.py: use domain exceptions, change db.commit() to db.flush() - Add unit tests for VendorEmailSettingsService - Add integration tests for vendor and admin email settings APIs - Add user guide (docs/guides/email-settings.md) - Add developer guide (docs/implementation/email-settings.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
290
tests/integration/api/v1/admin/test_email_settings.py
Normal file
290
tests/integration/api/v1/admin/test_email_settings.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# tests/integration/api/v1/admin/test_email_settings.py
|
||||
"""Integration tests for admin email settings API."""
|
||||
|
||||
import pytest
|
||||
|
||||
from models.database.admin import AdminSetting
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# GET EMAIL STATUS TESTS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.email
|
||||
class TestGetAdminEmailStatus:
|
||||
"""Test suite for GET /admin/settings/email/status endpoint."""
|
||||
|
||||
def test_get_status_unauthenticated(self, client):
|
||||
"""Test getting status without auth fails."""
|
||||
response = client.get("/api/v1/admin/settings/email/status")
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_get_status_non_admin(self, client, auth_headers):
|
||||
"""Test getting status as non-admin fails."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/settings/email/status",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_get_status_admin(self, client, admin_headers):
|
||||
"""Test getting status as admin succeeds."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/settings/email/status",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "provider" in data
|
||||
assert "from_email" in data
|
||||
assert "enabled" in data
|
||||
assert "is_configured" in data
|
||||
|
||||
def test_get_status_has_db_overrides_flag(self, client, admin_headers):
|
||||
"""Test status includes has_db_overrides flag."""
|
||||
response = client.get(
|
||||
"/api/v1/admin/settings/email/status",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "has_db_overrides" in data
|
||||
# Initially should be False (no DB settings)
|
||||
assert data["has_db_overrides"] is False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# UPDATE EMAIL SETTINGS TESTS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.email
|
||||
class TestUpdateAdminEmailSettings:
|
||||
"""Test suite for PUT /admin/settings/email/settings endpoint."""
|
||||
|
||||
def test_update_unauthenticated(self, client):
|
||||
"""Test updating settings without auth fails."""
|
||||
response = client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
json={"from_email": "test@example.com"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_update_non_admin(self, client, auth_headers):
|
||||
"""Test updating settings as non-admin fails."""
|
||||
response = client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=auth_headers,
|
||||
json={"from_email": "test@example.com"},
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_update_settings_admin(self, client, admin_headers, db):
|
||||
"""Test updating settings as admin succeeds."""
|
||||
response = client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"from_email": "platform@example.com",
|
||||
"from_name": "Test Platform",
|
||||
"provider": "smtp",
|
||||
"smtp_host": "smtp.test.com",
|
||||
"smtp_port": 587,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert "updated_keys" in data
|
||||
assert "from_email" in data["updated_keys"]
|
||||
|
||||
# Verify settings were stored in DB
|
||||
setting = (
|
||||
db.query(AdminSetting)
|
||||
.filter(AdminSetting.key == "email_from_address")
|
||||
.first()
|
||||
)
|
||||
assert setting is not None
|
||||
assert setting.value == "platform@example.com"
|
||||
|
||||
def test_update_partial_settings(self, client, admin_headers):
|
||||
"""Test updating only some settings."""
|
||||
response = client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"enabled": False,
|
||||
"debug": True,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
assert "enabled" in data["updated_keys"]
|
||||
assert "debug" in data["updated_keys"]
|
||||
|
||||
def test_status_shows_db_overrides(self, client, admin_headers):
|
||||
"""Test that status shows DB overrides after update."""
|
||||
# First, set a DB override
|
||||
client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
json={"from_email": "override@example.com"},
|
||||
)
|
||||
|
||||
# Check status
|
||||
response = client.get(
|
||||
"/api/v1/admin/settings/email/status",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["has_db_overrides"] is True
|
||||
assert data["from_email"] == "override@example.com"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# RESET EMAIL SETTINGS TESTS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.email
|
||||
class TestResetAdminEmailSettings:
|
||||
"""Test suite for DELETE /admin/settings/email/settings endpoint."""
|
||||
|
||||
def test_reset_unauthenticated(self, client):
|
||||
"""Test resetting settings without auth fails."""
|
||||
response = client.delete("/api/v1/admin/settings/email/settings")
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_reset_non_admin(self, client, auth_headers):
|
||||
"""Test resetting settings as non-admin fails."""
|
||||
response = client.delete(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=auth_headers,
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_reset_settings_admin(self, client, admin_headers, db):
|
||||
"""Test resetting settings as admin."""
|
||||
# First, create some DB overrides
|
||||
client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
json={
|
||||
"from_email": "tobereset@example.com",
|
||||
"provider": "sendgrid",
|
||||
},
|
||||
)
|
||||
|
||||
# Verify they exist
|
||||
setting = (
|
||||
db.query(AdminSetting)
|
||||
.filter(AdminSetting.key == "email_from_address")
|
||||
.first()
|
||||
)
|
||||
assert setting is not None
|
||||
|
||||
# Reset
|
||||
response = client.delete(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["success"] is True
|
||||
|
||||
# Verify they're gone
|
||||
db.expire_all()
|
||||
setting = (
|
||||
db.query(AdminSetting)
|
||||
.filter(AdminSetting.key == "email_from_address")
|
||||
.first()
|
||||
)
|
||||
assert setting is None
|
||||
|
||||
def test_status_after_reset(self, client, admin_headers):
|
||||
"""Test status after reset shows no DB overrides."""
|
||||
# Set an override
|
||||
client.put(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
json={"from_email": "override@example.com"},
|
||||
)
|
||||
|
||||
# Reset
|
||||
client.delete(
|
||||
"/api/v1/admin/settings/email/settings",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
# Check status
|
||||
response = client.get(
|
||||
"/api/v1/admin/settings/email/status",
|
||||
headers=admin_headers,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["has_db_overrides"] is False
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# TEST EMAIL TESTS
|
||||
# =============================================================================
|
||||
|
||||
|
||||
@pytest.mark.integration
|
||||
@pytest.mark.email
|
||||
class TestSendAdminTestEmail:
|
||||
"""Test suite for POST /admin/settings/email/test endpoint."""
|
||||
|
||||
def test_send_test_unauthenticated(self, client):
|
||||
"""Test sending test email without auth fails."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/settings/email/test",
|
||||
json={"to_email": "test@example.com"},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
def test_send_test_non_admin(self, client, auth_headers):
|
||||
"""Test sending test email as non-admin fails."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/settings/email/test",
|
||||
headers=auth_headers,
|
||||
json={"to_email": "test@example.com"},
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
def test_send_test_invalid_email(self, client, admin_headers):
|
||||
"""Test sending to invalid email format fails."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/settings/email/test",
|
||||
headers=admin_headers,
|
||||
json={"to_email": "not-an-email"},
|
||||
)
|
||||
assert response.status_code == 422 # Validation error
|
||||
|
||||
def test_send_test_admin(self, client, admin_headers):
|
||||
"""Test sending test email as admin."""
|
||||
response = client.post(
|
||||
"/api/v1/admin/settings/email/test",
|
||||
headers=admin_headers,
|
||||
json={"to_email": "test@example.com"},
|
||||
)
|
||||
|
||||
# May fail if email not configured, but should not be 401/403
|
||||
assert response.status_code in (200, 500)
|
||||
data = response.json()
|
||||
assert "success" in data
|
||||
assert "message" in data
|
||||
Reference in New Issue
Block a user