chore: finalize vite migration hardening and archive openspec changes
This commit is contained in:
@@ -1,159 +1,212 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Unit tests for auth_service module."""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from mes_dashboard.services import auth_service
|
||||
|
||||
|
||||
class TestAuthenticate:
|
||||
"""Tests for authenticate function via LDAP."""
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_success(self, mock_post):
|
||||
"""Test successful authentication via LDAP."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"success": True,
|
||||
"user": {
|
||||
"username": "92367",
|
||||
"displayName": "Test User",
|
||||
"mail": "test@panjit.com.tw",
|
||||
"department": "Test Dept"
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("92367", "password123")
|
||||
|
||||
assert result is not None
|
||||
assert result["username"] == "92367"
|
||||
assert result["mail"] == "test@panjit.com.tw"
|
||||
mock_post.assert_called_once()
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_invalid_credentials(self, mock_post):
|
||||
"""Test authentication with invalid credentials via LDAP."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"success": False}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("wrong", "wrong")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_timeout(self, mock_post):
|
||||
"""Test authentication timeout handling."""
|
||||
import requests
|
||||
mock_post.side_effect = requests.Timeout()
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_connection_error(self, mock_post):
|
||||
"""Test authentication connection error handling."""
|
||||
import requests
|
||||
mock_post.side_effect = requests.ConnectionError()
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_invalid_json(self, mock_post):
|
||||
"""Test authentication with invalid JSON response."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.side_effect = ValueError("Invalid JSON")
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestLocalAuthenticate:
|
||||
"""Tests for local authentication."""
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', True)
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_USERNAME', 'testuser')
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_PASSWORD', 'testpass')
|
||||
def test_local_auth_success(self):
|
||||
"""Test successful local authentication."""
|
||||
result = auth_service.authenticate("testuser", "testpass")
|
||||
|
||||
assert result is not None
|
||||
assert result["username"] == "testuser"
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', True)
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_USERNAME', 'testuser')
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_PASSWORD', 'testpass')
|
||||
def test_local_auth_wrong_password(self):
|
||||
"""Test local authentication with wrong password."""
|
||||
result = auth_service.authenticate("testuser", "wrongpass")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestIsAdmin:
|
||||
"""Tests for is_admin function."""
|
||||
|
||||
def test_is_admin_with_admin_email(self):
|
||||
"""Test admin check with admin email."""
|
||||
# Save original ADMIN_EMAILS
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "admin@panjit.com.tw"}
|
||||
assert auth_service.is_admin(user) is True
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_with_non_admin_email(self):
|
||||
"""Test admin check with non-admin email."""
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "user@panjit.com.tw"}
|
||||
assert auth_service.is_admin(user) is False
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_case_insensitive(self):
|
||||
"""Test admin check is case insensitive."""
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "ADMIN@PANJIT.COM.TW"}
|
||||
assert auth_service.is_admin(user) is True
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_with_missing_mail(self):
|
||||
"""Test admin check with missing mail field."""
|
||||
user = {}
|
||||
assert auth_service.is_admin(user) is False
|
||||
|
||||
def test_is_admin_with_empty_mail(self):
|
||||
"""Test admin check with empty mail field."""
|
||||
user = {"mail": ""}
|
||||
assert auth_service.is_admin(user) is False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Unit tests for auth_service module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from mes_dashboard.services import auth_service
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _ldap_defaults(monkeypatch):
|
||||
"""Keep LDAP auth tests deterministic regardless of host env vars."""
|
||||
monkeypatch.setattr(auth_service, "LDAP_API_BASE", "https://ldap.panjit.example")
|
||||
monkeypatch.setattr(auth_service, "LDAP_CONFIG_ERROR", None)
|
||||
|
||||
|
||||
class TestAuthenticate:
|
||||
"""Tests for authenticate function via LDAP."""
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_success(self, mock_post):
|
||||
"""Test successful authentication via LDAP."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {
|
||||
"success": True,
|
||||
"user": {
|
||||
"username": "92367",
|
||||
"displayName": "Test User",
|
||||
"mail": "test@panjit.com.tw",
|
||||
"department": "Test Dept"
|
||||
}
|
||||
}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("92367", "password123")
|
||||
|
||||
assert result is not None
|
||||
assert result["username"] == "92367"
|
||||
assert result["mail"] == "test@panjit.com.tw"
|
||||
mock_post.assert_called_once()
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_invalid_credentials(self, mock_post):
|
||||
"""Test authentication with invalid credentials via LDAP."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"success": False}
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("wrong", "wrong")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_timeout(self, mock_post):
|
||||
"""Test authentication timeout handling."""
|
||||
import requests
|
||||
mock_post.side_effect = requests.Timeout()
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_connection_error(self, mock_post):
|
||||
"""Test authentication connection error handling."""
|
||||
import requests
|
||||
mock_post.side_effect = requests.ConnectionError()
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_invalid_json(self, mock_post):
|
||||
"""Test authentication with invalid JSON response."""
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.side_effect = ValueError("Invalid JSON")
|
||||
mock_post.return_value = mock_response
|
||||
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', False)
|
||||
@patch('mes_dashboard.services.auth_service.requests.post')
|
||||
def test_authenticate_rejects_invalid_ldap_config_without_outbound_call(self, mock_post):
|
||||
"""Unsafe LDAP config should block auth and skip outbound request."""
|
||||
with patch.object(auth_service, "LDAP_CONFIG_ERROR", "invalid LDAP API URL"):
|
||||
result = auth_service.authenticate("user", "pass")
|
||||
|
||||
assert result is None
|
||||
mock_post.assert_not_called()
|
||||
|
||||
|
||||
class TestLdapConfigValidation:
|
||||
"""Validate LDAP base URL hardening rules."""
|
||||
|
||||
def test_accepts_https_allowlisted_host(self):
|
||||
api_base, error = auth_service._validate_ldap_api_url(
|
||||
"https://ldap.panjit.example",
|
||||
("ldap.panjit.example",),
|
||||
)
|
||||
|
||||
assert error is None
|
||||
assert api_base == "https://ldap.panjit.example"
|
||||
|
||||
def test_rejects_non_https_url(self):
|
||||
api_base, error = auth_service._validate_ldap_api_url(
|
||||
"http://ldap.panjit.example",
|
||||
("ldap.panjit.example",),
|
||||
)
|
||||
|
||||
assert api_base is None
|
||||
assert error is not None
|
||||
assert "HTTPS" in error
|
||||
|
||||
def test_rejects_non_allowlisted_host(self):
|
||||
api_base, error = auth_service._validate_ldap_api_url(
|
||||
"https://evil.example",
|
||||
("ldap.panjit.example",),
|
||||
)
|
||||
|
||||
assert api_base is None
|
||||
assert error is not None
|
||||
assert "allowlisted" in error
|
||||
|
||||
|
||||
class TestLocalAuthenticate:
|
||||
"""Tests for local authentication."""
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', True)
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_USERNAME', 'testuser')
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_PASSWORD', 'testpass')
|
||||
def test_local_auth_success(self):
|
||||
"""Test successful local authentication."""
|
||||
result = auth_service.authenticate("testuser", "testpass")
|
||||
|
||||
assert result is not None
|
||||
assert result["username"] == "testuser"
|
||||
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_ENABLED', True)
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_USERNAME', 'testuser')
|
||||
@patch('mes_dashboard.services.auth_service.LOCAL_AUTH_PASSWORD', 'testpass')
|
||||
def test_local_auth_wrong_password(self):
|
||||
"""Test local authentication with wrong password."""
|
||||
result = auth_service.authenticate("testuser", "wrongpass")
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
class TestIsAdmin:
|
||||
"""Tests for is_admin function."""
|
||||
|
||||
def test_is_admin_with_admin_email(self):
|
||||
"""Test admin check with admin email."""
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "admin@panjit.com.tw"}
|
||||
assert auth_service.is_admin(user) is True
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_with_non_admin_email(self):
|
||||
"""Test admin check with non-admin email."""
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "user@panjit.com.tw"}
|
||||
assert auth_service.is_admin(user) is False
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_case_insensitive(self):
|
||||
"""Test admin check is case insensitive."""
|
||||
original = auth_service.ADMIN_EMAILS
|
||||
|
||||
try:
|
||||
auth_service.ADMIN_EMAILS = ["admin@panjit.com.tw"]
|
||||
user = {"mail": "ADMIN@PANJIT.COM.TW"}
|
||||
assert auth_service.is_admin(user) is True
|
||||
finally:
|
||||
auth_service.ADMIN_EMAILS = original
|
||||
|
||||
def test_is_admin_with_missing_mail(self):
|
||||
"""Test admin check with missing mail field."""
|
||||
user = {}
|
||||
assert auth_service.is_admin(user) is False
|
||||
|
||||
def test_is_admin_with_empty_mail(self):
|
||||
"""Test admin check with empty mail field."""
|
||||
user = {"mail": ""}
|
||||
assert auth_service.is_admin(user) is False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
|
||||
Reference in New Issue
Block a user