Files
bambuddy/backend/tests/integration/test_endpoint_auth.py
T
maziggy 2fd90e9dce refactor(api): rename /filaments/ to /filament-catalog/ for clarity (#427)
The /filaments/ endpoint manages material type definitions (cost, temps,
density) but shares its name with the UI's "Filament" page which shows
the spool inventory (/inventory/spools/). This caused users to expect
the API to return their spool inventory.

Rename to /filament-catalog/ to make the distinction clear. No frontend
behavior change — these API methods are defined but unused in the UI.
2026-02-18 11:58:17 +01:00

174 lines
6.9 KiB
Python

"""Integration tests for API endpoint authentication.
Tests that verify endpoints properly enforce authentication when auth is enabled,
and allow access when auth is disabled (CVE-2026-25505 fix verification).
"""
from unittest.mock import patch
import pytest
from httpx import AsyncClient
class TestEndpointAuthenticationEnforcement:
"""Tests that endpoints enforce authentication when auth is enabled."""
@pytest.fixture
async def user_factory(self, db_session):
"""Factory to create test users."""
async def _create_user(**kwargs):
from passlib.hash import bcrypt
from backend.app.models.user import User
defaults = {
"username": "testuser",
"password_hash": bcrypt.hash("testpass123"),
"is_admin": False,
}
defaults.update(kwargs)
user = User(**defaults)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
return _create_user
@pytest.fixture
async def admin_user(self, user_factory, db_session):
"""Create an admin user for testing."""
from sqlalchemy import select
from backend.app.models.group import Group
# Get or create admin group
result = await db_session.execute(select(Group).where(Group.name == "Administrators"))
admin_group = result.scalar_one_or_none()
user = await user_factory(username="admin", is_admin=True)
if admin_group:
user.groups.append(admin_group)
await db_session.commit()
return user
@pytest.fixture
async def auth_token(self, admin_user, async_client: AsyncClient):
"""Get a valid auth token for the admin user."""
response = await async_client.post(
"/api/v1/auth/login",
json={"username": "admin", "password": "testpass123"},
)
if response.status_code == 200:
return response.json().get("access_token")
return None
@pytest.mark.asyncio
@pytest.mark.integration
async def test_filaments_list_accessible_without_auth_when_disabled(self, async_client: AsyncClient):
"""Verify filaments list is accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
response = await async_client.get("/api/v1/filament-catalog/")
assert response.status_code == 200
@pytest.mark.asyncio
@pytest.mark.integration
async def test_external_links_list_accessible_without_auth_when_disabled(self, async_client: AsyncClient):
"""Verify external links list is accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
response = await async_client.get("/api/v1/external-links/")
assert response.status_code == 200
@pytest.mark.asyncio
@pytest.mark.integration
async def test_notifications_list_accessible_without_auth_when_disabled(self, async_client: AsyncClient):
"""Verify notifications list is accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
response = await async_client.get("/api/v1/notifications/")
assert response.status_code == 200
@pytest.mark.asyncio
@pytest.mark.integration
async def test_maintenance_types_accessible_without_auth_when_disabled(self, async_client: AsyncClient):
"""Verify maintenance types is accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
response = await async_client.get("/api/v1/maintenance/types")
assert response.status_code == 200
@pytest.mark.asyncio
@pytest.mark.integration
async def test_system_info_accessible_without_auth_when_disabled(self, async_client: AsyncClient):
"""Verify system info is accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
response = await async_client.get("/api/v1/system/info")
assert response.status_code == 200
class TestImageEndpointsPublicAccess:
"""Tests that image endpoints remain accessible without auth.
These endpoints serve images via <img> tags which cannot send Authorization headers.
"""
@pytest.fixture
async def link_with_icon(self, db_session):
"""Create an external link with a custom icon for testing."""
from backend.app.models.external_link import ExternalLink
link = ExternalLink(
name="Test Link",
url="https://example.com",
icon="Link",
sort_order=0,
custom_icon=None, # No custom icon set
)
db_session.add(link)
await db_session.commit()
await db_session.refresh(link)
return link
@pytest.mark.asyncio
@pytest.mark.integration
async def test_external_link_icon_returns_404_when_no_icon(self, async_client: AsyncClient, link_with_icon):
"""Verify icon endpoint returns 404 (not 401) when no icon is set.
This confirms the endpoint doesn't require auth - a 401 would indicate
auth is being enforced, but 404 means the endpoint is accessible but
no icon exists.
"""
response = await async_client.get(f"/api/v1/external-links/{link_with_icon.id}/icon")
# Should be 404 (no icon set), not 401 (unauthorized)
assert response.status_code == 404
assert "No custom icon set" in response.json().get("detail", "")
class TestAuthenticationPatterns:
"""Tests for authentication helper functions and patterns."""
@pytest.mark.asyncio
@pytest.mark.integration
async def test_require_permission_if_auth_enabled_allows_access_when_disabled(self, async_client: AsyncClient):
"""Verify require_permission_if_auth_enabled allows access when auth disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
# Test a protected endpoint
response = await async_client.get("/api/v1/filament-catalog/")
assert response.status_code == 200
@pytest.mark.asyncio
@pytest.mark.integration
async def test_multiple_endpoints_accessible_when_auth_disabled(self, async_client: AsyncClient):
"""Verify multiple protected endpoints are accessible when auth is disabled."""
with patch("backend.app.core.auth.is_auth_enabled", return_value=False):
endpoints = [
"/api/v1/filament-catalog/",
"/api/v1/external-links/",
"/api/v1/notifications/",
"/api/v1/maintenance/types",
]
for endpoint in endpoints:
response = await async_client.get(endpoint)
assert response.status_code == 200, f"Endpoint {endpoint} should be accessible"