test: 补充知识处理、检索与 Agent 回归用例
覆盖切块、向量检索、工具调用和历史预算;模型相关用例使用桩。
This commit is contained in:
1 parent
362d79a5c2
commit
f4d8019829
123 files changed
+29005
No files matched your search
@@ -0,0 +1,880 @@
|
||||
"""
|
||||
Copyright 2024-2026 ChatterMate
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock, AsyncMock
|
||||
from fastapi import HTTPException, Request, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from uuid import uuid4
|
||||
|
||||
from app.core.auth import (
|
||||
check_permissions,
|
||||
get_current_user,
|
||||
get_current_organization,
|
||||
require_permissions,
|
||||
require_permission,
|
||||
require_subscription_management,
|
||||
require_unified_permissions,
|
||||
require_any_unified_permission
|
||||
)
|
||||
from app.models.user import User
|
||||
from app.models.organization import Organization
|
||||
from app.models.permission import Permission
|
||||
from app.models.role import Role
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db():
|
||||
"""Create a mock database session"""
|
||||
db = MagicMock(spec=Session)
|
||||
return db
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_user():
|
||||
"""Create a mock user with role and permissions"""
|
||||
user_id = uuid4()
|
||||
org_id = uuid4()
|
||||
|
||||
# Create permissions
|
||||
view_chats_permission = Permission(id=1, name="view_chats")
|
||||
manage_agents_permission = Permission(id=2, name="manage_agents")
|
||||
|
||||
# Create role with permissions
|
||||
role = Role(id=1, name="Agent", permissions=[view_chats_permission, manage_agents_permission])
|
||||
|
||||
# Create user with role
|
||||
user = User(
|
||||
id=user_id,
|
||||
email="test@example.com",
|
||||
hashed_password="hashed_password",
|
||||
is_active=True,
|
||||
organization_id=org_id,
|
||||
full_name="Test User",
|
||||
role=role
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_super_admin():
|
||||
"""Create a mock user with super_admin permission"""
|
||||
user_id = uuid4()
|
||||
org_id = uuid4()
|
||||
|
||||
# Create super_admin permission
|
||||
super_admin_permission = Permission(id=3, name="super_admin")
|
||||
|
||||
# Create role with super_admin permission
|
||||
role = Role(id=2, name="Super Admin", permissions=[super_admin_permission])
|
||||
|
||||
# Create user with role
|
||||
user = User(
|
||||
id=user_id,
|
||||
email="admin@example.com",
|
||||
hashed_password="hashed_password",
|
||||
is_active=True,
|
||||
organization_id=org_id,
|
||||
full_name="Admin User",
|
||||
role=role
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_organization():
|
||||
"""Create a mock organization"""
|
||||
org_id = uuid4()
|
||||
return Organization(
|
||||
id=org_id,
|
||||
name="Test Organization",
|
||||
domain="test.com",
|
||||
is_active=True
|
||||
)
|
||||
|
||||
|
||||
def test_check_permissions_with_required_permissions(mock_user):
|
||||
"""Test check_permissions when user has the required permissions"""
|
||||
result = check_permissions(mock_user, ["view_chats"])
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_check_permissions_without_required_permissions(mock_user):
|
||||
"""Test check_permissions when user doesn't have the required permissions"""
|
||||
result = check_permissions(mock_user, ["delete_organization"])
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_check_permissions_with_super_admin(mock_super_admin):
|
||||
"""Test check_permissions with super_admin permission"""
|
||||
result = check_permissions(mock_super_admin, ["delete_organization"])
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_check_permissions_without_role():
|
||||
"""Test check_permissions when user has no role"""
|
||||
user = User(
|
||||
id=uuid4(),
|
||||
email="noauth@example.com",
|
||||
hashed_password="hashed_password",
|
||||
is_active=True
|
||||
)
|
||||
result = check_permissions(user, ["view_chats"])
|
||||
assert result is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_with_cookie(mock_db, mock_user):
|
||||
"""Test get_current_user with access token in cookie"""
|
||||
# Mock request with cookie
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "valid_access_token"
|
||||
|
||||
# Mock live_payload
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
|
||||
# Mock db query
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_current_user(request, access_token, mock_db)
|
||||
assert result == mock_user
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_with_auth_header(mock_db, mock_user):
|
||||
"""Test get_current_user with access token in Authorization header"""
|
||||
# Mock request with Authorization header
|
||||
request = MagicMock(spec=Request)
|
||||
request.headers.get.return_value = "Bearer valid_access_token"
|
||||
|
||||
# Mock live_payload
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
|
||||
# Mock db query
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_current_user(request, None, mock_db)
|
||||
assert result == mock_user
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_no_token(mock_db):
|
||||
"""Test get_current_user with no access token"""
|
||||
# Mock request with no token
|
||||
request = MagicMock(spec=Request)
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, None, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "Not authenticated"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_invalid_token(mock_db):
|
||||
"""Test get_current_user with invalid token"""
|
||||
# Mock request
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "invalid_token"
|
||||
|
||||
# Mock live_payload
|
||||
with patch('app.core.auth.live_payload', return_value=None):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, access_token, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "Invalid authentication token"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_invalid_payload(mock_db):
|
||||
"""Test get_current_user with invalid payload"""
|
||||
# Mock request
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "valid_token_invalid_payload"
|
||||
|
||||
# Mock live_payload
|
||||
payload = {"not_sub": "invalid"}
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, access_token, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "Invalid token payload"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_user_not_found(mock_db):
|
||||
"""Test get_current_user when user not found"""
|
||||
# Mock request
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "valid_token"
|
||||
|
||||
# Mock live_payload
|
||||
payload = {"sub": str(uuid4())}
|
||||
|
||||
# Mock db query - user not found
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = None
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, access_token, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "User not found"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_inactive_user(mock_db, mock_user):
|
||||
"""Test get_current_user with inactive user"""
|
||||
# Mock request
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "valid_token"
|
||||
|
||||
# Set user as inactive
|
||||
mock_user.is_active = False
|
||||
|
||||
# Mock live_payload
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
|
||||
# Mock db query
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, access_token, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "User is inactive"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_exception(mock_db):
|
||||
"""Test get_current_user with exception"""
|
||||
# Mock request
|
||||
request = MagicMock(spec=Request)
|
||||
access_token = "valid_token"
|
||||
|
||||
# Mock live_payload to raise exception
|
||||
with patch('app.core.auth.live_payload', side_effect=Exception("Test exception")):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_user(request, access_token, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "Could not validate credentials"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_organization_success(mock_db, mock_user, mock_organization):
|
||||
"""Test get_current_organization success"""
|
||||
# Mock db query
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_organization
|
||||
|
||||
result = await get_current_organization(mock_user, mock_db)
|
||||
assert result == mock_organization
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_organization_no_org_id(mock_db):
|
||||
"""Test get_current_organization with no organization_id"""
|
||||
# Create user with no organization_id
|
||||
user = User(
|
||||
id=uuid4(),
|
||||
email="noorg@example.com",
|
||||
hashed_password="hashed_password",
|
||||
is_active=True,
|
||||
organization_id=None
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_organization(user, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "User is not associated with any organization"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_organization_not_found(mock_db, mock_user):
|
||||
"""Test get_current_organization when organization not found"""
|
||||
# Mock db query - organization not found
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = None
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_current_organization(mock_user, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 404
|
||||
assert exc_info.value.detail == "Organization not found or inactive"
|
||||
|
||||
|
||||
# Test the require_permissions function indirectly through check_permissions
|
||||
def test_require_permissions_check_with_permissions(mock_user):
|
||||
"""Test the check used in require_permissions when user has the required permissions"""
|
||||
result = check_permissions(mock_user, ["view_chats"])
|
||||
assert result is True
|
||||
|
||||
|
||||
def test_require_permissions_check_without_permissions(mock_user):
|
||||
"""Test the check used in require_permissions when user doesn't have the required permissions"""
|
||||
result = check_permissions(mock_user, ["delete_organization"])
|
||||
assert result is False
|
||||
|
||||
|
||||
# Test the require_permission function indirectly through role.has_permission
|
||||
def test_require_permission_check_with_permission(mock_user):
|
||||
"""Test the check used in require_permission when user has the required permission"""
|
||||
# Mock the has_permission method
|
||||
mock_user.role.has_permission = MagicMock(return_value=True)
|
||||
|
||||
# Check if the role has the permission
|
||||
result = mock_user.role.has_permission("view_chats")
|
||||
assert result is True
|
||||
|
||||
# Verify the has_permission method was called with the correct permission
|
||||
mock_user.role.has_permission.assert_called_once_with("view_chats")
|
||||
|
||||
|
||||
def test_require_permission_check_without_permission(mock_user):
|
||||
"""Test the check used in require_permission when user doesn't have the required permission"""
|
||||
# Mock the has_permission method
|
||||
mock_user.role.has_permission = MagicMock(return_value=False)
|
||||
|
||||
# Check if the role has the permission
|
||||
result = mock_user.role.has_permission("delete_organization")
|
||||
assert result is False
|
||||
|
||||
# Verify the has_permission method was called with the correct permission
|
||||
mock_user.role.has_permission.assert_called_once_with("delete_organization")
|
||||
|
||||
|
||||
# Test the require_subscription_management function indirectly through role.can_manage_subscription
|
||||
def test_require_subscription_management_check_with_permission(mock_user):
|
||||
"""Test the check used in require_subscription_management when user has the permission"""
|
||||
# Mock the can_manage_subscription method
|
||||
mock_user.role.can_manage_subscription = MagicMock(return_value=True)
|
||||
|
||||
# Check if the role can manage subscriptions
|
||||
result = mock_user.role.can_manage_subscription()
|
||||
assert result is True
|
||||
|
||||
# Verify the can_manage_subscription method was called
|
||||
mock_user.role.can_manage_subscription.assert_called_once()
|
||||
|
||||
|
||||
def test_require_subscription_management_check_without_permission(mock_user):
|
||||
"""Test the check used in require_subscription_management when user doesn't have the permission"""
|
||||
# Mock the can_manage_subscription method
|
||||
mock_user.role.can_manage_subscription = MagicMock(return_value=False)
|
||||
|
||||
# Check if the role can manage subscriptions
|
||||
result = mock_user.role.can_manage_subscription()
|
||||
assert result is False
|
||||
|
||||
# Verify the can_manage_subscription method was called
|
||||
mock_user.role.can_manage_subscription.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_permissions_inner_function(mock_user):
|
||||
"""Test the inner function of require_permissions directly"""
|
||||
# Create the inner function
|
||||
permission_checker = require_permissions("view_chats")
|
||||
|
||||
# Mock Depends to return our user
|
||||
with patch('fastapi.Depends', return_value=mock_user):
|
||||
# Call the inner function
|
||||
result = await permission_checker(mock_user)
|
||||
assert result == mock_user
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_permissions_inner_function_forbidden(mock_user):
|
||||
"""Test the inner function of require_permissions with insufficient permissions"""
|
||||
# Create the inner function
|
||||
permission_checker = require_permissions("delete_organization")
|
||||
|
||||
# Call the inner function and expect an exception
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await permission_checker(mock_user)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "Not enough permissions"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_permission_inner_function(mock_user):
|
||||
"""Test the inner function of require_permission directly"""
|
||||
# Mock the role's has_permission method
|
||||
mock_user.role.has_permission = MagicMock(return_value=True)
|
||||
|
||||
# Create the inner function
|
||||
permission_dependency = require_permission("view_chats")
|
||||
|
||||
# Call the inner function
|
||||
result = await permission_dependency(mock_user)
|
||||
assert result == mock_user
|
||||
mock_user.role.has_permission.assert_called_once_with("view_chats")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_permission_inner_function_forbidden(mock_user):
|
||||
"""Test the inner function of require_permission with insufficient permissions"""
|
||||
# Mock the role's has_permission method
|
||||
mock_user.role.has_permission = MagicMock(return_value=False)
|
||||
|
||||
# Create the inner function
|
||||
permission_dependency = require_permission("delete_organization")
|
||||
|
||||
# Call the inner function and expect an exception
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await permission_dependency(mock_user)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "User does not have required permission: delete_organization"
|
||||
mock_user.role.has_permission.assert_called_once_with("delete_organization")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_subscription_management_with_permission(mock_user):
|
||||
"""Test require_subscription_management with sufficient permissions"""
|
||||
# Mock the role's can_manage_subscription method
|
||||
mock_user.role.can_manage_subscription = MagicMock(return_value=True)
|
||||
|
||||
# Call the function
|
||||
result = require_subscription_management(mock_user)
|
||||
assert result == mock_user
|
||||
mock_user.role.can_manage_subscription.assert_called_once()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_subscription_management_without_permission(mock_user):
|
||||
"""Test require_subscription_management with insufficient permissions"""
|
||||
# Mock the role's can_manage_subscription method
|
||||
mock_user.role.can_manage_subscription = MagicMock(return_value=False)
|
||||
|
||||
# Call the function and expect an exception
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
require_subscription_management(mock_user)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "User does not have permission to manage subscriptions"
|
||||
mock_user.role.can_manage_subscription.assert_called_once()
|
||||
|
||||
|
||||
# ==================== Tests for get_auth_info_from_request ====================
|
||||
|
||||
from app.core.auth import get_auth_info_from_request
|
||||
|
||||
def test_get_auth_info_from_request_shopify_url():
|
||||
"""Test get_auth_info_from_request with Shopify URL"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/shopify/products"
|
||||
|
||||
result = get_auth_info_from_request(request)
|
||||
assert result["is_shopify"] is True
|
||||
|
||||
|
||||
def test_get_auth_info_from_request_non_shopify_url():
|
||||
"""Test get_auth_info_from_request with non-Shopify URL"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/users"
|
||||
|
||||
result = get_auth_info_from_request(request)
|
||||
assert result["is_shopify"] is False
|
||||
|
||||
|
||||
# ==================== Tests for get_unified_auth ====================
|
||||
|
||||
from app.core.auth import get_unified_auth
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_jwt_success(mock_db, mock_user):
|
||||
"""Test get_unified_auth with valid JWT token"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
# Add manage_agents permission
|
||||
manage_agents_perm = Permission(id=2, name="manage_agents")
|
||||
mock_user.role.permissions = [manage_agents_perm]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_unified_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
assert result["organization_id"] == mock_user.organization_id
|
||||
assert result["current_user"] == mock_user
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_jwt_no_token(mock_db):
|
||||
"""Test get_unified_auth without token"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = None
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_jwt_invalid_token(mock_db):
|
||||
"""Test get_unified_auth with invalid JWT token"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "invalid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=None):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "Invalid authentication token"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_jwt_user_not_found(mock_db):
|
||||
"""Test get_unified_auth when user not found"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
payload = {"sub": str(uuid4())}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = None
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "User not found"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_jwt_inactive_user(mock_db, mock_user):
|
||||
"""Test get_unified_auth with inactive user"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
mock_user.is_active = False
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
assert exc_info.value.detail == "User is inactive"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_authenticates_without_permissions(mock_db, mock_user):
|
||||
"""get_unified_auth authenticates; it does not authorize.
|
||||
|
||||
It used to hardcode a manage_agents check, so every route sharing the helper
|
||||
inherited it — including reading your own org's subscription, which locked
|
||||
every non-admin out of the entire app.
|
||||
"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
mock_user.role.permissions = [Permission(id=1, name="view_only")]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_unified_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
assert result["current_user"] is mock_user
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_unified_permissions_rejects_missing_permission(mock_user):
|
||||
"""The wrapper is what enforces the route's requirement."""
|
||||
mock_user.role.permissions = [Permission(id=1, name="view_only")]
|
||||
|
||||
checker = require_unified_permissions("manage_agents")
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await checker(auth_info={"auth_type": "jwt", "current_user": mock_user})
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "Not enough permissions"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_unified_permissions_allows_holder(mock_user):
|
||||
"""A user holding the permission passes through unchanged."""
|
||||
mock_user.role.permissions = [Permission(id=1, name="manage_agents")]
|
||||
auth_info = {"auth_type": "jwt", "current_user": mock_user}
|
||||
|
||||
checker = require_unified_permissions("manage_agents")
|
||||
|
||||
assert await checker(auth_info=auth_info) is auth_info
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_require_any_unified_permission_is_or_semantics(mock_user):
|
||||
"""Any one of the listed permissions is enough."""
|
||||
mock_user.role.permissions = [Permission(id=1, name="view_agents")]
|
||||
|
||||
checker = require_any_unified_permission("view_agents", "manage_agents")
|
||||
result = await checker(auth_info={"auth_type": "jwt", "current_user": mock_user})
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unified_permissions_pass_through_shop_session():
|
||||
"""A Shopify shop session authenticates a store, not a person.
|
||||
|
||||
It carries no current_user and no role, so there is nothing to check — it
|
||||
must pass through exactly as it did before authorization moved out.
|
||||
"""
|
||||
auth_info = {"auth_type": "shopify", "organization_id": uuid4()}
|
||||
|
||||
assert await require_unified_permissions("manage_agents")(auth_info=auth_info) is auth_info
|
||||
assert await require_any_unified_permission("manage_agents")(auth_info=auth_info) is auth_info
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unified_permissions_tolerate_absent_current_user_key():
|
||||
"""Callers that omit the key entirely must not blow up with a KeyError."""
|
||||
auth_info = {"auth_type": "jwt", "organization_id": uuid4()}
|
||||
|
||||
assert await require_unified_permissions("manage_agents")(auth_info=auth_info) is auth_info
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_shopify_context(mock_db):
|
||||
"""Test get_unified_auth with Shopify context"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/shopify/agents"
|
||||
request.query_params = {}
|
||||
|
||||
mock_auth_result = {
|
||||
"auth_type": "shopify",
|
||||
"organization_id": uuid4(),
|
||||
"user_id": uuid4(),
|
||||
"current_user": None
|
||||
}
|
||||
|
||||
with patch('app.services.shopify_session.require_shopify_or_jwt_auth', new_callable=AsyncMock) as mock_shopify_auth:
|
||||
mock_shopify_auth.return_value = mock_auth_result
|
||||
result = await get_unified_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "shopify"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_with_auth_header(mock_db, mock_user):
|
||||
"""Test get_unified_auth with Authorization header"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = None
|
||||
request.headers.get.return_value = "Bearer valid_token"
|
||||
|
||||
manage_agents_perm = Permission(id=2, name="manage_agents")
|
||||
mock_user.role.permissions = [manage_agents_perm]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_unified_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
assert result["current_user"] == mock_user
|
||||
|
||||
|
||||
# ==================== Tests for get_unified_chat_auth ====================
|
||||
|
||||
from app.core.auth import get_unified_chat_auth
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_jwt_success(mock_db, mock_user):
|
||||
"""Test get_unified_chat_auth with valid JWT token"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/chats"
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
# Add chat permissions
|
||||
view_all_chats = Permission(id=10, name="view_all_chats")
|
||||
mock_user.role.permissions = [view_all_chats]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
assert result["can_view_all"] is True
|
||||
assert result["can_view_assigned"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_view_assigned(mock_db, mock_user):
|
||||
"""Test get_unified_chat_auth with view_assigned_chats permission"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/chats"
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
# Add assigned chats permission only
|
||||
view_assigned_chats = Permission(id=11, name="view_assigned_chats")
|
||||
mock_user.role.permissions = [view_assigned_chats]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "jwt"
|
||||
assert result["can_view_all"] is False
|
||||
assert result["can_view_assigned"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_no_permissions(mock_db, mock_user):
|
||||
"""Test get_unified_chat_auth without chat permissions"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/chats"
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
# No chat permissions
|
||||
other_perm = Permission(id=1, name="other_permission")
|
||||
mock_user.role.permissions = [other_perm]
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 403
|
||||
assert exc_info.value.detail == "Not enough permissions"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_shopify_context(mock_db):
|
||||
"""Test get_unified_chat_auth with Shopify context"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/shopify/chats"
|
||||
|
||||
mock_auth_result = {
|
||||
"auth_type": "shopify",
|
||||
"organization_id": uuid4(),
|
||||
"user_id": uuid4(),
|
||||
"current_user": None
|
||||
}
|
||||
|
||||
with patch('app.services.shopify_session.require_shopify_or_jwt_auth', new_callable=AsyncMock) as mock_shopify_auth:
|
||||
mock_shopify_auth.return_value = mock_auth_result
|
||||
result = await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert result["auth_type"] == "shopify"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_no_token(mock_db):
|
||||
"""Test get_unified_chat_auth without token"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/chats"
|
||||
request.cookies.get.return_value = None
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_chat_auth_exception(mock_db):
|
||||
"""Test get_unified_chat_auth with exception"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/chats"
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with patch('app.core.auth.live_payload', side_effect=Exception("Test error")):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_chat_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_unified_auth_exception(mock_db):
|
||||
"""Test get_unified_auth with unexpected exception"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.url = "https://example.com/api/agents"
|
||||
request.query_params = {}
|
||||
request.cookies.get.return_value = "valid_token"
|
||||
request.headers.get.return_value = None
|
||||
|
||||
with patch('app.core.auth.live_payload', side_effect=Exception("Unexpected error")):
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await get_unified_auth(request, mock_db)
|
||||
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user_from_cookie_directly(mock_db, mock_user):
|
||||
"""Test get_current_user when token is read directly from cookies"""
|
||||
request = MagicMock(spec=Request)
|
||||
request.cookies = {"access_token": "valid_cookie_token"}
|
||||
request.headers.get.return_value = None
|
||||
|
||||
payload = {"sub": str(mock_user.id)}
|
||||
mock_db.query.return_value.filter.return_value.first.return_value = mock_user
|
||||
|
||||
with patch('app.core.auth.live_payload', return_value=payload):
|
||||
result = await get_current_user(request, None, mock_db)
|
||||
assert result == mock_user
|
||||
Reference in new issue
Block a user