Backend: - Add setup-backend.sh/bat for one-click backend setup - Fix test_auth.py mock settings (JWT_EXPIRE_HOURS) - Fix test_excel_export.py TEMPLATE_DIR reference Frontend: - Add config.json for runtime API URL configuration - Add init.js and settings.js for config loading - Update main.js to load config from external file - Update api.js to use dynamic API_BASE_URL - Update all pages to initialize config before API calls - Update package.json with extraResources for config Build: - Add build-client.sh/bat for packaging Electron + Sidecar - Add build-all.ps1 PowerShell script with -ApiUrl parameter - Add GitHub Actions workflow for Windows builds - Add scripts/README.md documentation This allows IT to configure backend URL without rebuilding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
144 lines
4.9 KiB
Python
144 lines
4.9 KiB
Python
"""
|
|
Unit tests for authentication functionality.
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
from fastapi.testclient import TestClient
|
|
from jose import jwt
|
|
|
|
pytestmark = pytest.mark.asyncio
|
|
|
|
|
|
class TestAdminRoleDetection:
|
|
"""Tests for admin role detection."""
|
|
|
|
def test_admin_email_gets_admin_role(self):
|
|
"""Test that admin email is correctly identified."""
|
|
from app.config import settings
|
|
|
|
admin_email = settings.ADMIN_EMAIL
|
|
test_email = "regular@example.com"
|
|
|
|
# Admin email should be set (either from env or default)
|
|
assert admin_email is not None
|
|
assert len(admin_email) > 0
|
|
assert test_email != admin_email
|
|
|
|
@patch("app.routers.auth.settings")
|
|
def test_create_token_includes_role(self, mock_settings):
|
|
"""Test that created tokens include the role."""
|
|
mock_settings.JWT_SECRET = "test-secret"
|
|
mock_settings.JWT_EXPIRE_HOURS = 24
|
|
mock_settings.ADMIN_EMAIL = "admin@test.com"
|
|
|
|
from app.routers.auth import create_token
|
|
|
|
# Test admin token
|
|
admin_token = create_token("admin@test.com", "admin")
|
|
admin_payload = jwt.decode(admin_token, "test-secret", algorithms=["HS256"])
|
|
assert admin_payload["role"] == "admin"
|
|
|
|
# Test user token
|
|
user_token = create_token("user@test.com", "user")
|
|
user_payload = jwt.decode(user_token, "test-secret", algorithms=["HS256"])
|
|
assert user_payload["role"] == "user"
|
|
|
|
|
|
class TestTokenValidation:
|
|
"""Tests for JWT token validation."""
|
|
|
|
@patch("app.routers.auth.settings")
|
|
def test_decode_valid_token(self, mock_settings):
|
|
"""Test decoding a valid token."""
|
|
mock_settings.JWT_SECRET = "test-secret"
|
|
mock_settings.JWT_EXPIRE_HOURS = 24
|
|
|
|
from app.routers.auth import create_token, decode_token
|
|
|
|
token = create_token("test@example.com", "user")
|
|
payload = decode_token(token)
|
|
|
|
assert payload.email == "test@example.com"
|
|
assert payload.role == "user"
|
|
|
|
@patch("app.routers.auth.settings")
|
|
def test_decode_invalid_token_raises_error(self, mock_settings):
|
|
"""Test that invalid tokens raise an error."""
|
|
mock_settings.JWT_SECRET = "test-secret"
|
|
|
|
from app.routers.auth import decode_token
|
|
from fastapi import HTTPException
|
|
|
|
with pytest.raises(HTTPException) as exc_info:
|
|
decode_token("invalid-token")
|
|
|
|
assert exc_info.value.status_code == 401
|
|
|
|
|
|
class TestLoginEndpoint:
|
|
"""Tests for the login endpoint."""
|
|
|
|
@pytest.fixture
|
|
def client(self):
|
|
"""Create test client."""
|
|
from app.main import app
|
|
|
|
# Skip lifespan for tests
|
|
app.router.lifespan_context = None
|
|
return TestClient(app, raise_server_exceptions=False)
|
|
|
|
@patch("app.routers.auth.httpx.AsyncClient")
|
|
@patch("app.routers.auth.settings")
|
|
async def test_login_success(self, mock_settings, mock_client_class):
|
|
"""Test successful login."""
|
|
mock_settings.AUTH_API_URL = "https://auth.test.com/login"
|
|
mock_settings.ADMIN_EMAIL = "admin@test.com"
|
|
mock_settings.JWT_SECRET = "test-secret"
|
|
mock_settings.JWT_EXPIRE_HOURS = 24
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"token": "external-token", "success": True}
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.post.return_value = mock_response
|
|
mock_client.__aenter__.return_value = mock_client
|
|
mock_client.__aexit__.return_value = None
|
|
mock_client_class.return_value = mock_client
|
|
|
|
from app.routers.auth import login
|
|
from app.models import LoginRequest
|
|
|
|
result = await login(LoginRequest(email="user@test.com", password="password"))
|
|
|
|
assert result.email == "user@test.com"
|
|
assert result.role == "user"
|
|
assert result.token is not None
|
|
|
|
@patch("app.routers.auth.httpx.AsyncClient")
|
|
@patch("app.routers.auth.settings")
|
|
async def test_login_admin_gets_admin_role(self, mock_settings, mock_client_class):
|
|
"""Test that admin email gets admin role."""
|
|
mock_settings.AUTH_API_URL = "https://auth.test.com/login"
|
|
mock_settings.ADMIN_EMAIL = "admin@test.com"
|
|
mock_settings.JWT_SECRET = "test-secret"
|
|
mock_settings.JWT_EXPIRE_HOURS = 24
|
|
|
|
mock_response = MagicMock()
|
|
mock_response.status_code = 200
|
|
mock_response.json.return_value = {"token": "external-token", "success": True}
|
|
|
|
mock_client = AsyncMock()
|
|
mock_client.post.return_value = mock_response
|
|
mock_client.__aenter__.return_value = mock_client
|
|
mock_client.__aexit__.return_value = None
|
|
mock_client_class.return_value = mock_client
|
|
|
|
from app.routers.auth import login
|
|
from app.models import LoginRequest
|
|
|
|
result = await login(LoginRequest(email="admin@test.com", password="password"))
|
|
|
|
assert result.role == "admin"
|