Files
OpenHands/enterprise/tests/unit/test_org_service.py
2026-03-03 17:51:53 -07:00

2131 lines
62 KiB
Python

"""
Unit tests for OrgService.
Tests the organization creation workflow with compensation pattern,
including LiteLLM integration and cleanup on failures.
"""
import uuid
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from server.routes.org_models import (
LiteLLMIntegrationError,
OrgAuthorizationError,
OrgDatabaseError,
OrgNameExistsError,
OrgNotFoundError,
)
from storage.org import Org
from storage.org_member import OrgMember
from storage.org_service import OrgService
from storage.role import Role
from storage.user import User
@pytest.fixture
def mock_litellm_api():
"""Mock LiteLLM API for testing."""
api_key_patch = patch('storage.lite_llm_manager.LITE_LLM_API_KEY', 'test_key')
api_url_patch = patch(
'storage.lite_llm_manager.LITE_LLM_API_URL', 'http://test.url'
)
team_id_patch = patch('storage.lite_llm_manager.LITE_LLM_TEAM_ID', 'test_team')
client_patch = patch('httpx.AsyncClient')
with api_key_patch, api_url_patch, team_id_patch, client_patch as mock_client:
mock_response = AsyncMock()
mock_response.is_success = True
mock_response.status_code = 200
mock_response.json = MagicMock(
return_value={
'team_id': 'test-team-id',
'user_id': 'test-user-id',
'key': 'test-api-key',
}
)
mock_client.return_value.__aenter__.return_value.post.return_value = (
mock_response
)
mock_client.return_value.__aenter__.return_value.get.return_value = (
mock_response
)
yield mock_client
@pytest.fixture
def owner_role(session_maker):
"""Create owner role in database."""
with session_maker() as session:
role = Role(id=1, name='owner', rank=1)
session.add(role)
session.commit()
return role
@pytest.mark.asyncio
async def test_validate_name_uniqueness_with_unique_name(async_session_maker):
"""
GIVEN: A unique organization name
WHEN: validate_name_uniqueness is called
THEN: No exception is raised
"""
# Arrange
unique_name = 'unique-org-name'
# Act & Assert - should not raise
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker'),
patch('storage.role_store.a_session_maker'),
):
await OrgService.validate_name_uniqueness(unique_name)
@pytest.mark.asyncio
async def test_validate_name_uniqueness_with_duplicate_name():
"""
GIVEN: An organization name that already exists
WHEN: validate_name_uniqueness is called
THEN: OrgNameExistsError is raised
"""
# Arrange
existing_name = 'existing-org'
existing_org = Org(name=existing_name)
# Mock OrgStore.get_org_by_name to return the existing org
with patch(
'storage.org_service.OrgStore.get_org_by_name',
new_callable=AsyncMock,
return_value=existing_org,
):
# Act & Assert
with pytest.raises(OrgNameExistsError) as exc_info:
await OrgService.validate_name_uniqueness(existing_name)
assert existing_name in str(exc_info.value)
@pytest.mark.asyncio
async def test_create_org_with_owner_success(
session_maker, async_session_maker, owner_role, mock_litellm_api
):
"""
GIVEN: Valid organization data and user ID
WHEN: create_org_with_owner is called
THEN: Organization and owner membership are created successfully
"""
# Arrange
org_name = 'test-org'
contact_name = 'John Doe'
contact_email = 'john@example.com'
user_id = uuid.uuid4()
temp_org_id = uuid.uuid4()
# Create user in database first
with session_maker() as session:
user = User(id=user_id, current_org_id=temp_org_id)
session.add(user)
session.commit()
mock_settings = {'team_id': 'test-team', 'user_id': str(user_id)}
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.UserStore.create_default_settings',
AsyncMock(return_value=mock_settings),
),
patch(
'storage.org_service.OrgStore.get_kwargs_from_settings',
return_value={},
),
patch(
'storage.org_service.OrgMemberStore.get_kwargs_from_settings',
return_value={'llm_api_key': 'test-key'},
),
):
# Act
result = await OrgService.create_org_with_owner(
name=org_name,
contact_name=contact_name,
contact_email=contact_email,
user_id=str(user_id),
)
# Assert
assert result is not None
assert result.name == org_name
assert result.contact_name == contact_name
assert result.contact_email == contact_email
assert result.org_version > 0 # Should be set to ORG_SETTINGS_VERSION
assert result.default_llm_model is not None # Should be set
# Verify organization was persisted
with session_maker() as session:
persisted_org = session.get(Org, result.id)
assert persisted_org is not None
assert persisted_org.name == org_name
# Verify owner membership was created
org_member = (
session.query(OrgMember)
.filter_by(org_id=result.id, user_id=user_id)
.first()
)
assert org_member is not None
assert org_member.role_id == 1 # owner role id
assert org_member.status == 'active'
@pytest.mark.asyncio
async def test_create_org_with_owner_duplicate_name(
session_maker, async_session_maker, owner_role, mock_litellm_api
):
"""
GIVEN: An organization name that already exists
WHEN: create_org_with_owner is called
THEN: OrgNameExistsError is raised without creating LiteLLM resources
"""
# Arrange
existing_name = 'existing-org'
with session_maker() as session:
org = Org(name=existing_name)
session.add(org)
session.commit()
mock_create_settings = AsyncMock()
# Act & Assert
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.UserStore.create_default_settings',
mock_create_settings,
),
):
with pytest.raises(OrgNameExistsError):
await OrgService.create_org_with_owner(
name=existing_name,
contact_name='John Doe',
contact_email='john@example.com',
user_id='test-user-123',
)
# Verify no LiteLLM API calls were made (early exit)
mock_create_settings.assert_not_called()
@pytest.mark.asyncio
async def test_create_org_with_owner_litellm_failure(
session_maker, async_session_maker, owner_role, mock_litellm_api
):
"""
GIVEN: LiteLLM integration fails
WHEN: create_org_with_owner is called
THEN: LiteLLMIntegrationError is raised and no database records are created
"""
# Arrange
org_name = 'test-org'
# Mock LiteLLM failure
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.UserStore.create_default_settings',
AsyncMock(return_value=None),
),
):
# Act & Assert
with pytest.raises(LiteLLMIntegrationError):
await OrgService.create_org_with_owner(
name=org_name,
contact_name='John Doe',
contact_email='john@example.com',
user_id='test-user-123',
)
# Verify no organization was created in database
with session_maker() as session:
org = session.query(Org).filter_by(name=org_name).first()
assert org is None
@pytest.mark.asyncio
async def test_create_org_with_owner_database_failure_triggers_cleanup(
session_maker, async_session_maker, owner_role, mock_litellm_api
):
"""
GIVEN: Database persistence fails after LiteLLM integration succeeds
WHEN: create_org_with_owner is called
THEN: OrgDatabaseError is raised and LiteLLM cleanup is triggered
"""
# Arrange
org_name = 'test-org'
user_id = str(uuid.uuid4())
cleanup_called = False
def mock_cleanup(*args, **kwargs):
nonlocal cleanup_called
cleanup_called = True
return None
mock_settings = {'team_id': 'test-team', 'user_id': user_id}
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.UserStore.create_default_settings',
AsyncMock(return_value=mock_settings),
),
patch(
'storage.org_service.OrgStore.get_kwargs_from_settings',
return_value={},
),
patch(
'storage.org_service.OrgMemberStore.get_kwargs_from_settings',
return_value={'llm_api_key': 'test-key'},
),
patch(
'storage.org_service.OrgStore.persist_org_with_owner',
side_effect=Exception('Database connection failed'),
),
patch(
'storage.org_service.OrgService._cleanup_litellm_resources',
AsyncMock(side_effect=mock_cleanup),
),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.create_org_with_owner(
name=org_name,
contact_name='John Doe',
contact_email='john@example.com',
user_id=user_id,
)
# Verify cleanup was called
assert cleanup_called
assert 'Database connection failed' in str(exc_info.value)
@pytest.mark.asyncio
async def test_create_org_with_owner_entity_creation_failure_triggers_cleanup(
session_maker, async_session_maker, owner_role, mock_litellm_api
):
"""
GIVEN: Entity creation fails after LiteLLM integration succeeds
WHEN: create_org_with_owner is called
THEN: OrgDatabaseError is raised and LiteLLM cleanup is triggered
"""
# Arrange
org_name = 'test-org'
user_id = str(uuid.uuid4())
mock_settings = {'team_id': 'test-team', 'user_id': user_id}
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.UserStore.create_default_settings',
AsyncMock(return_value=mock_settings),
),
patch(
'storage.org_service.OrgStore.get_kwargs_from_settings',
return_value={},
),
patch(
'storage.org_service.OrgMemberStore.get_kwargs_from_settings',
return_value={'llm_api_key': 'test-key'},
),
patch(
'storage.org_service.OrgService.get_owner_role',
side_effect=Exception('Owner role not found'),
),
patch(
'storage.org_service.LiteLlmManager.delete_team',
AsyncMock(),
) as mock_delete,
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.create_org_with_owner(
name=org_name,
contact_name='John Doe',
contact_email='john@example.com',
user_id=user_id,
)
# Verify cleanup was called
mock_delete.assert_called_once()
assert 'Owner role not found' in str(exc_info.value)
@pytest.mark.asyncio
async def test_cleanup_litellm_resources_success(mock_litellm_api):
"""
GIVEN: Valid org_id and user_id
WHEN: _cleanup_litellm_resources is called
THEN: LiteLLM team is deleted successfully and None is returned
"""
# Arrange
org_id = uuid.uuid4()
user_id = 'test-user-123'
with patch(
'storage.org_service.LiteLlmManager.delete_team',
AsyncMock(),
) as mock_delete:
# Act
result = await OrgService._cleanup_litellm_resources(org_id, user_id)
# Assert
assert result is None
mock_delete.assert_called_once_with(str(org_id))
@pytest.mark.asyncio
async def test_cleanup_litellm_resources_failure_returns_exception(mock_litellm_api):
"""
GIVEN: LiteLLM delete_team fails
WHEN: _cleanup_litellm_resources is called
THEN: Exception is returned (not raised) for logging
"""
# Arrange
org_id = uuid.uuid4()
user_id = 'test-user-123'
expected_error = Exception('LiteLLM API unavailable')
with patch(
'storage.org_service.LiteLlmManager.delete_team',
AsyncMock(side_effect=expected_error),
):
# Act
result = await OrgService._cleanup_litellm_resources(org_id, user_id)
# Assert
assert result is expected_error
assert 'LiteLLM API unavailable' in str(result)
@pytest.mark.asyncio
async def test_handle_failure_with_cleanup_success():
"""
GIVEN: Original error and successful cleanup
WHEN: _handle_failure_with_cleanup is called
THEN: OrgDatabaseError is raised with original error message
"""
# Arrange
org_id = uuid.uuid4()
user_id = 'test-user-123'
original_error = Exception('Database write failed')
with patch(
'storage.org_service.OrgService._cleanup_litellm_resources',
AsyncMock(return_value=None),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService._handle_failure_with_cleanup(
org_id, user_id, original_error, 'Failed to create organization'
)
assert 'Database write failed' in str(exc_info.value)
assert 'Cleanup also failed' not in str(exc_info.value)
@pytest.mark.asyncio
async def test_handle_failure_with_cleanup_both_fail():
"""
GIVEN: Original error and cleanup also fails
WHEN: _handle_failure_with_cleanup is called
THEN: OrgDatabaseError is raised with both error messages
"""
# Arrange
org_id = uuid.uuid4()
user_id = 'test-user-123'
original_error = Exception('Database write failed')
cleanup_error = Exception('LiteLLM API unavailable')
with patch(
'storage.org_service.OrgService._cleanup_litellm_resources',
AsyncMock(return_value=cleanup_error),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService._handle_failure_with_cleanup(
org_id, user_id, original_error, 'Failed to create organization'
)
error_message = str(exc_info.value)
assert 'Database write failed' in error_message
assert 'Cleanup also failed' in error_message
assert 'LiteLLM API unavailable' in error_message
@pytest.mark.asyncio
async def test_get_org_credits_success(mock_litellm_api):
"""
GIVEN: Valid user_id and org_id with LiteLLM team info
WHEN: get_org_credits is called
THEN: Credits are calculated correctly (max_budget - spend)
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
max_budget = 100.0
spend = 25.0
mock_team_info = {
'max_budget_in_team': max_budget,
'spend': spend,
}
with patch(
'storage.org_service.LiteLlmManager.get_user_team_info',
AsyncMock(return_value=mock_team_info),
):
# Act
credits = await OrgService.get_org_credits(user_id, org_id)
# Assert
assert credits == 75.0 # 100 - 25
@pytest.mark.asyncio
async def test_get_org_credits_no_team_info(mock_litellm_api):
"""
GIVEN: LiteLLM returns no team info
WHEN: get_org_credits is called
THEN: None is returned
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
with patch(
'storage.org_service.LiteLlmManager.get_user_team_info',
AsyncMock(return_value=None),
):
# Act
credits = await OrgService.get_org_credits(user_id, org_id)
# Assert
assert credits is None
@pytest.mark.asyncio
async def test_get_org_credits_negative_credits_returns_zero(mock_litellm_api):
"""
GIVEN: Spend exceeds max_budget
WHEN: get_org_credits is called
THEN: Zero credits are returned (not negative)
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
max_budget = 100.0
spend = 150.0 # Over budget
mock_team_info = {
'litellm_budget_table': {'max_budget': max_budget},
'spend': spend,
}
with patch(
'storage.org_service.LiteLlmManager.get_user_team_info',
AsyncMock(return_value=mock_team_info),
):
# Act
credits = await OrgService.get_org_credits(user_id, org_id)
# Assert
assert credits == 0.0
@pytest.mark.asyncio
async def test_get_org_credits_api_failure_returns_none(mock_litellm_api):
"""
GIVEN: LiteLLM API call fails
WHEN: get_org_credits is called
THEN: None is returned and error is logged
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
with patch(
'storage.org_service.LiteLlmManager.get_user_team_info',
AsyncMock(side_effect=Exception('API error')),
):
# Act
credits = await OrgService.get_org_credits(user_id, org_id)
# Assert
assert credits is None
@pytest.mark.asyncio
async def test_get_org_by_id_success(session_maker, owner_role):
"""
GIVEN: Valid org_id and user_id where user is a member
WHEN: get_org_by_id is called
THEN: Organization is returned successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = uuid.uuid4()
org_name = 'Test Organization'
# Create mock objects
mock_org = Org(id=org_id, name=org_name)
mock_org_member = OrgMember(
org_id=org_id,
user_id=user_id,
role_id=1,
llm_api_key='test-key',
status='active',
)
with (
patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
) as mock_get_member,
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
) as mock_get_org,
):
mock_get_member.return_value = mock_org_member
mock_get_org.return_value = mock_org
# Act
result = await OrgService.get_org_by_id(org_id, str(user_id))
# Assert
assert result is not None
assert result.id == org_id
assert result.name == org_name
mock_get_member.assert_called_once()
mock_get_org.assert_called_once_with(org_id)
@pytest.mark.asyncio
async def test_get_org_by_id_user_not_member():
"""
GIVEN: User is not a member of the organization
WHEN: get_org_by_id is called
THEN: OrgNotFoundError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
return_value=None,
):
# Act & Assert
with pytest.raises(OrgNotFoundError) as exc_info:
await OrgService.get_org_by_id(org_id, user_id)
assert str(org_id) in str(exc_info.value)
@pytest.mark.asyncio
async def test_get_org_by_id_org_not_found():
"""
GIVEN: User is a member but organization doesn't exist (edge case)
WHEN: get_org_by_id is called
THEN: OrgNotFoundError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = uuid.uuid4()
# Create mock org member (but org doesn't exist)
mock_org_member = OrgMember(
org_id=org_id,
user_id=user_id,
role_id=1,
llm_api_key='test-key',
status='active',
)
with (
patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
return_value=mock_org_member,
),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=None,
),
):
# Act & Assert
with pytest.raises(OrgNotFoundError) as exc_info:
await OrgService.get_org_by_id(org_id, str(user_id))
assert str(org_id) in str(exc_info.value)
@pytest.mark.asyncio
async def test_get_user_orgs_paginated_success(
session_maker, async_session_maker, mock_litellm_api
):
"""
GIVEN: User has organizations in database
WHEN: get_user_orgs_paginated is called with valid user_id
THEN: Organizations are returned with pagination info
"""
# Arrange
user_id = uuid.uuid4()
org_id = uuid.uuid4()
with session_maker() as session:
org = Org(id=org_id, name='Test Org')
user = User(id=user_id, current_org_id=org_id)
role = Role(id=1, name='member', rank=2)
session.add_all([org, user, role])
session.flush()
member = OrgMember(
org_id=org_id, user_id=user_id, role_id=1, llm_api_key='key1'
)
session.add(member)
session.commit()
# Act
with patch('storage.org_store.a_session_maker', async_session_maker):
orgs, next_page_id = await OrgService.get_user_orgs_paginated(
user_id=str(user_id), page_id=None, limit=10
)
# Assert
assert len(orgs) == 1
assert orgs[0].name == 'Test Org'
assert next_page_id is None
@pytest.mark.asyncio
async def test_get_user_orgs_paginated_with_pagination(
session_maker, async_session_maker, mock_litellm_api
):
"""
GIVEN: User has multiple organizations
WHEN: get_user_orgs_paginated is called with page_id and limit
THEN: Paginated results are returned correctly
"""
# Arrange
user_id = uuid.uuid4()
with session_maker() as session:
org1 = Org(name='Alpha Org')
org2 = Org(name='Beta Org')
org3 = Org(name='Gamma Org')
session.add_all([org1, org2, org3])
session.flush()
user = User(id=user_id, current_org_id=org1.id)
role = Role(id=1, name='member', rank=2)
session.add_all([user, role])
session.flush()
member1 = OrgMember(
org_id=org1.id, user_id=user_id, role_id=1, llm_api_key='key1'
)
member2 = OrgMember(
org_id=org2.id, user_id=user_id, role_id=1, llm_api_key='key2'
)
member3 = OrgMember(
org_id=org3.id, user_id=user_id, role_id=1, llm_api_key='key3'
)
session.add_all([member1, member2, member3])
session.commit()
# Act
with patch('storage.org_store.a_session_maker', async_session_maker):
orgs, next_page_id = await OrgService.get_user_orgs_paginated(
user_id=str(user_id), page_id='0', limit=2
)
# Assert
assert len(orgs) == 2
assert orgs[0].name == 'Alpha Org'
assert orgs[1].name == 'Beta Org'
assert next_page_id == '2'
@pytest.mark.asyncio
async def test_get_user_orgs_paginated_empty_results(async_session_maker):
"""
GIVEN: User has no organizations
WHEN: get_user_orgs_paginated is called
THEN: Empty list and None next_page_id are returned
"""
# Arrange
user_id = str(uuid.uuid4())
# Act
with patch('storage.org_store.a_session_maker', async_session_maker):
orgs, next_page_id = await OrgService.get_user_orgs_paginated(
user_id=user_id, page_id=None, limit=10
)
# Assert
assert len(orgs) == 0
assert next_page_id is None
@pytest.mark.asyncio
async def test_get_user_orgs_paginated_invalid_user_id_format():
"""
GIVEN: Invalid user_id format (not a valid UUID string)
WHEN: get_user_orgs_paginated is called
THEN: ValueError is raised
"""
# Arrange
invalid_user_id = 'not-a-uuid'
# Act & Assert
with pytest.raises(ValueError):
await OrgService.get_user_orgs_paginated(
user_id=invalid_user_id, page_id=None, limit=10
)
@pytest.mark.asyncio
async def test_verify_owner_authorization_success(session_maker, owner_role):
"""
GIVEN: User is owner of the organization
WHEN: verify_owner_authorization is called
THEN: No exception is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Test Org',
contact_name='John',
contact_email='john@example.com',
)
mock_org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=1,
status='active',
llm_api_key='key',
)
# Create a mock role to avoid detached instance issues
mock_owner_role = MagicMock()
mock_owner_role.name = 'owner'
mock_owner_role.id = 1
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
return_value=mock_org_member,
),
patch(
'storage.org_service.RoleStore.get_role_by_id',
new_callable=AsyncMock,
return_value=mock_owner_role,
),
):
# Act & Assert - should not raise
await OrgService.verify_owner_authorization(user_id, org_id)
@pytest.mark.asyncio
async def test_verify_owner_authorization_org_not_found():
"""
GIVEN: Organization does not exist
WHEN: verify_owner_authorization is called
THEN: OrgNotFoundError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=None,
):
# Act & Assert
with pytest.raises(OrgNotFoundError) as exc_info:
await OrgService.verify_owner_authorization(user_id, org_id)
assert str(org_id) in str(exc_info.value)
@pytest.mark.asyncio
async def test_verify_owner_authorization_user_not_member(session_maker, owner_role):
"""
GIVEN: User is not a member of the organization
WHEN: verify_owner_authorization is called
THEN: OrgAuthorizationError is raised with member message
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Test Org',
contact_name='John',
contact_email='john@example.com',
)
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
return_value=None,
),
):
# Act & Assert
with pytest.raises(OrgAuthorizationError) as exc_info:
await OrgService.verify_owner_authorization(user_id, org_id)
assert 'not a member' in str(exc_info.value)
@pytest.mark.asyncio
async def test_verify_owner_authorization_user_not_owner(session_maker):
"""
GIVEN: User is member but not owner (admin role)
WHEN: verify_owner_authorization is called
THEN: OrgAuthorizationError is raised with owner message
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Test Org',
contact_name='John',
contact_email='john@example.com',
)
mock_org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
llm_api_key='key',
)
admin_role = Role(id=2, name='admin', rank=20)
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch(
'storage.org_service.OrgMemberStore.get_org_member',
new_callable=AsyncMock,
return_value=mock_org_member,
),
patch(
'storage.org_service.RoleStore.get_role_by_id',
new_callable=AsyncMock,
return_value=admin_role,
),
):
# Act & Assert
with pytest.raises(OrgAuthorizationError) as exc_info:
await OrgService.verify_owner_authorization(user_id, org_id)
assert 'Only organization owners' in str(exc_info.value)
@pytest.mark.asyncio
async def test_delete_org_with_cleanup_success(session_maker, owner_role):
"""
GIVEN: User is organization owner and deletion succeeds
WHEN: delete_org_with_cleanup is called
THEN: Organization is deleted and returned
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_deleted_org = Org(
id=org_id,
name='Deleted Organization',
contact_name='John Doe',
contact_email='john@example.com',
)
with (
patch('storage.org_service.OrgService.verify_owner_authorization'),
patch(
'storage.org_service.OrgStore.delete_org_cascade',
AsyncMock(return_value=mock_deleted_org),
),
):
# Act
result = await OrgService.delete_org_with_cleanup(user_id, org_id)
# Assert
assert result is not None
assert result.id == org_id
assert result.name == 'Deleted Organization'
@pytest.mark.asyncio
async def test_delete_org_with_cleanup_authorization_failure():
"""
GIVEN: User is not authorized to delete organization
WHEN: delete_org_with_cleanup is called
THEN: OrgAuthorizationError is raised and no deletion occurs
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with patch(
'storage.org_service.OrgService.verify_owner_authorization',
side_effect=OrgAuthorizationError('Not authorized'),
):
# Act & Assert
with pytest.raises(OrgAuthorizationError):
await OrgService.delete_org_with_cleanup(user_id, org_id)
@pytest.mark.asyncio
async def test_delete_org_with_cleanup_org_not_found():
"""
GIVEN: Organization does not exist
WHEN: delete_org_with_cleanup is called
THEN: OrgNotFoundError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with patch(
'storage.org_service.OrgService.verify_owner_authorization',
side_effect=OrgNotFoundError(str(org_id)),
):
# Act & Assert
with pytest.raises(OrgNotFoundError):
await OrgService.delete_org_with_cleanup(user_id, org_id)
@pytest.mark.asyncio
async def test_delete_org_with_cleanup_database_failure(session_maker, owner_role):
"""
GIVEN: Authorization succeeds but database deletion fails
WHEN: delete_org_with_cleanup is called
THEN: OrgDatabaseError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with (
patch('storage.org_service.OrgService.verify_owner_authorization'),
patch(
'storage.org_service.OrgStore.delete_org_cascade',
AsyncMock(side_effect=Exception('Database connection failed')),
),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.delete_org_with_cleanup(user_id, org_id)
assert 'Database connection failed' in str(exc_info.value)
@pytest.mark.asyncio
async def test_delete_org_with_cleanup_unexpected_none_result(
session_maker, owner_role
):
"""
GIVEN: Authorization succeeds but delete_org_cascade returns None
WHEN: delete_org_with_cleanup is called
THEN: OrgDatabaseError is raised with not found message
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with (
patch('storage.org_service.OrgService.verify_owner_authorization'),
patch(
'storage.org_service.OrgStore.delete_org_cascade',
AsyncMock(return_value=None),
),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.delete_org_with_cleanup(user_id, org_id)
assert 'not found during deletion' in str(exc_info.value)
@pytest.mark.asyncio
async def test_update_org_with_permissions_success_non_llm_fields(
async_session_maker, session_maker
):
"""
GIVEN: Valid organization update with non-LLM fields and user is a member
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization and user in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
role = Role(id=2, name='member', rank=2)
session.add(role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
contact_name='Jane Doe',
contact_email='jane@example.com',
conversation_expiration=30,
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.contact_name == 'Jane Doe'
assert result.contact_email == 'jane@example.com'
assert result.conversation_expiration == 30
@pytest.mark.asyncio
async def test_update_org_with_permissions_success_llm_fields_admin(
async_session_maker, session_maker
):
"""
GIVEN: Valid organization update with LLM fields and user has admin role
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization, user, and admin role in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
admin_role = Role(id=1, name='admin', rank=1)
session.add(admin_role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=1,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
default_llm_model='claude-opus-4-5-20251101',
default_llm_base_url='https://api.anthropic.com',
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.default_llm_model == 'claude-opus-4-5-20251101'
assert result.default_llm_base_url == 'https://api.anthropic.com'
@pytest.mark.asyncio
async def test_update_org_with_permissions_success_llm_fields_owner(
async_session_maker, session_maker
):
"""
GIVEN: Valid organization update with LLM fields and user has owner role
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization, user, and owner role in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
owner_role = Role(id=1, name='owner', rank=1)
session.add(owner_role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=1,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
default_llm_model='claude-opus-4-5-20251101',
security_analyzer='enabled',
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.default_llm_model == 'claude-opus-4-5-20251101'
assert result.security_analyzer == 'enabled'
@pytest.mark.asyncio
async def test_update_org_with_permissions_success_mixed_fields_admin(
async_session_maker, session_maker
):
"""
GIVEN: Valid organization update with both LLM and non-LLM fields and user has admin role
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization, user, and admin role in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
admin_role = Role(id=1, name='admin', rank=1)
session.add(admin_role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=1,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
contact_name='Jane Doe',
default_llm_model='claude-opus-4-5-20251101',
conversation_expiration=30,
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.contact_name == 'Jane Doe'
assert result.default_llm_model == 'claude-opus-4-5-20251101'
assert result.conversation_expiration == 30
@pytest.mark.asyncio
async def test_update_org_with_permissions_empty_update(
async_session_maker, session_maker
):
"""
GIVEN: Update request with no fields (all None)
WHEN: update_org_with_permissions is called
THEN: Original organization is returned unchanged
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization and user in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
role = Role(id=2, name='member', rank=2)
session.add(role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate() # All fields None
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.name == 'Test Organization'
assert result.contact_name == 'John Doe'
@pytest.mark.asyncio
async def test_update_org_with_permissions_org_not_found(
session_maker, async_session_maker
):
"""
GIVEN: Organization ID does not exist
WHEN: update_org_with_permissions is called
THEN: ValueError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(contact_name='Jane Doe')
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act & Assert
with pytest.raises(ValueError) as exc_info:
await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
assert 'not found' in str(exc_info.value).lower()
@pytest.mark.asyncio
async def test_update_org_with_permissions_non_member(
session_maker, async_session_maker
):
"""
GIVEN: User is not a member of the organization
WHEN: update_org_with_permissions is called
THEN: PermissionError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
other_user_id = str(uuid.uuid4())
# Create organization but user is not a member
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(other_user_id), current_org_id=org_id)
session.add(user)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(contact_name='Jane Doe')
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act & Assert
with pytest.raises(PermissionError) as exc_info:
await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
assert 'member' in str(exc_info.value).lower()
@pytest.mark.asyncio
async def test_update_org_with_permissions_llm_fields_insufficient_permission(
async_session_maker,
session_maker,
):
"""
GIVEN: User is a member but lacks admin/owner role and tries to update LLM settings
WHEN: update_org_with_permissions is called
THEN: PermissionError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization and user with member role (not admin/owner)
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
member_role = Role(id=2, name='member', rank=2)
session.add(member_role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(default_llm_model='claude-opus-4-5-20251101')
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act & Assert
with pytest.raises(PermissionError) as exc_info:
await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
assert (
'admin' in str(exc_info.value).lower()
or 'owner' in str(exc_info.value).lower()
)
@pytest.mark.asyncio
async def test_update_org_with_permissions_database_error(
async_session_maker, session_maker
):
"""
GIVEN: Database update operation fails
WHEN: update_org_with_permissions is called
THEN: OrgDatabaseError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization and user in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
role = Role(id=2, name='member', rank=2)
session.add(role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(contact_name='Jane Doe')
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.OrgStore.update_org',
new_callable=AsyncMock,
return_value=None, # Simulate database failure
),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
assert 'Failed to update organization' in str(exc_info.value)
@pytest.mark.asyncio
async def test_update_org_with_permissions_duplicate_name_raises_org_name_exists_error(
async_session_maker,
session_maker,
):
"""
GIVEN: User updates org name to a name already used by another organization
WHEN: update_org_with_permissions is called
THEN: OrgNameExistsError is raised with the conflicting name
"""
# Arrange
org_id = uuid.uuid4()
other_org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
duplicate_name = 'Existing Org Name'
mock_current_org = Org(
id=org_id,
name='My Org',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
mock_org_with_name = Org(
id=other_org_id,
name=duplicate_name,
contact_name='Jane Doe',
contact_email='jane@example.com',
)
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(name=duplicate_name)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_current_org,
),
patch('storage.org_service.OrgService.is_org_member', return_value=True),
patch(
'storage.org_service.OrgStore.get_org_by_name',
new_callable=AsyncMock,
return_value=mock_org_with_name,
),
):
# Act & Assert
with pytest.raises(OrgNameExistsError) as exc_info:
await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
assert duplicate_name in str(exc_info.value)
@pytest.mark.asyncio
async def test_update_org_with_permissions_same_name_allowed(
session_maker, async_session_maker
):
"""
GIVEN: User updates org with name unchanged (same as current org name)
WHEN: update_org_with_permissions is called
THEN: No OrgNameExistsError; update proceeds (name uniqueness allows same org)
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
current_name = 'My Org'
mock_org = Org(
id=org_id,
name=current_name,
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(name=current_name)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch('storage.org_service.OrgService.is_org_member', return_value=True),
patch(
'storage.org_service.OrgStore.get_org_by_name',
new_callable=AsyncMock,
return_value=mock_org,
),
patch(
'storage.org_service.OrgStore.update_org',
new_callable=AsyncMock,
return_value=mock_org,
),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.name == current_name
@pytest.mark.asyncio
async def test_update_org_with_permissions_only_llm_fields(
async_session_maker, session_maker
):
"""
GIVEN: Update request contains only LLM fields and user has admin role
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization, user, and admin role in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
admin_role = Role(id=1, name='admin', rank=1)
session.add(admin_role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=1,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
default_llm_model='claude-opus-4-5-20251101',
security_analyzer='enabled',
agent='agent-mode',
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.default_llm_model == 'claude-opus-4-5-20251101'
assert result.security_analyzer == 'enabled'
assert result.agent == 'agent-mode'
@pytest.mark.asyncio
async def test_update_org_with_permissions_only_non_llm_fields(
async_session_maker, session_maker
):
"""
GIVEN: Update request contains only non-LLM fields and user is a member
WHEN: update_org_with_permissions is called
THEN: Organization is updated successfully
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
# Create organization and user in database
with session_maker() as session:
org = Org(
id=org_id,
name='Test Organization',
contact_name='John Doe',
contact_email='john@example.com',
org_version=5,
)
session.add(org)
user = User(id=uuid.UUID(user_id), current_org_id=org_id)
session.add(user)
role = Role(id=2, name='member', rank=2)
session.add(role)
org_member = OrgMember(
org_id=org_id,
user_id=uuid.UUID(user_id),
role_id=2,
status='active',
_llm_api_key='test-key',
)
session.add(org_member)
session.commit()
from server.routes.org_models import OrgUpdate
update_data = OrgUpdate(
contact_name='Jane Doe',
conversation_expiration=60,
enable_proactive_conversation_starters=False,
)
with (
patch('storage.org_store.a_session_maker', async_session_maker),
patch('storage.org_member_store.a_session_maker', async_session_maker),
patch('storage.role_store.a_session_maker', async_session_maker),
):
# Act
result = await OrgService.update_org_with_permissions(
org_id=org_id,
update_data=update_data,
user_id=user_id,
)
# Assert
assert result is not None
assert result.contact_name == 'Jane Doe'
assert result.conversation_expiration == 60
assert result.enable_proactive_conversation_starters is False
@pytest.mark.asyncio
async def test_check_byor_export_enabled_returns_true_when_enabled():
"""
GIVEN: User has current_org with byor_export_enabled=True
WHEN: check_byor_export_enabled is called
THEN: Returns True
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
mock_user = MagicMock()
mock_user.current_org_id = org_id
mock_org = MagicMock()
mock_org.byor_export_enabled = True
with (
patch(
'storage.org_service.UserStore.get_user_by_id',
AsyncMock(return_value=mock_user),
),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
):
# Act
result = await OrgService.check_byor_export_enabled(user_id)
# Assert
assert result is True
@pytest.mark.asyncio
async def test_check_byor_export_enabled_returns_false_when_disabled():
"""
GIVEN: User has current_org with byor_export_enabled=False
WHEN: check_byor_export_enabled is called
THEN: Returns False
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
mock_user = MagicMock()
mock_user.current_org_id = org_id
mock_org = MagicMock()
mock_org.byor_export_enabled = False
with (
patch(
'storage.org_service.UserStore.get_user_by_id',
AsyncMock(return_value=mock_user),
),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
):
# Act
result = await OrgService.check_byor_export_enabled(user_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_check_byor_export_enabled_returns_false_when_user_not_found():
"""
GIVEN: User does not exist
WHEN: check_byor_export_enabled is called
THEN: Returns False
"""
# Arrange
user_id = 'nonexistent-user'
with patch(
'storage.org_service.UserStore.get_user_by_id',
AsyncMock(return_value=None),
):
# Act
result = await OrgService.check_byor_export_enabled(user_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_check_byor_export_enabled_returns_false_when_no_current_org():
"""
GIVEN: User exists but has no current_org_id
WHEN: check_byor_export_enabled is called
THEN: Returns False
"""
# Arrange
user_id = 'test-user-123'
mock_user = MagicMock()
mock_user.current_org_id = None
with patch(
'storage.org_service.UserStore.get_user_by_id',
AsyncMock(return_value=mock_user),
):
# Act
result = await OrgService.check_byor_export_enabled(user_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_check_byor_export_enabled_returns_false_when_org_not_found():
"""
GIVEN: User has current_org_id but org does not exist
WHEN: check_byor_export_enabled is called
THEN: Returns False
"""
# Arrange
user_id = 'test-user-123'
org_id = uuid.uuid4()
mock_user = MagicMock()
mock_user.current_org_id = org_id
with (
patch(
'storage.org_service.UserStore.get_user_by_id',
AsyncMock(return_value=mock_user),
),
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=None,
),
):
# Act
result = await OrgService.check_byor_export_enabled(user_id)
# Assert
assert result is False
@pytest.mark.asyncio
async def test_switch_org_success():
"""
GIVEN: Valid org_id and user_id where user is a member
WHEN: switch_org is called
THEN: User's current_org_id is updated and org is returned
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Target Organization',
contact_name='John Doe',
contact_email='john@example.com',
)
mock_updated_user = User(id=uuid.UUID(user_id), current_org_id=org_id)
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch('storage.org_service.OrgService.is_org_member', return_value=True),
patch(
'storage.org_service.UserStore.update_current_org',
new_callable=AsyncMock,
return_value=mock_updated_user,
),
):
# Act
result = await OrgService.switch_org(user_id, org_id)
# Assert
assert result is not None
assert result.id == org_id
assert result.name == 'Target Organization'
@pytest.mark.asyncio
async def test_switch_org_org_not_found():
"""
GIVEN: Organization does not exist
WHEN: switch_org is called
THEN: OrgNotFoundError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
with patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=None,
):
# Act & Assert
with pytest.raises(OrgNotFoundError) as exc_info:
await OrgService.switch_org(user_id, org_id)
assert str(org_id) in str(exc_info.value)
@pytest.mark.asyncio
async def test_switch_org_user_not_member():
"""
GIVEN: User is not a member of the organization
WHEN: switch_org is called
THEN: OrgAuthorizationError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Target Organization',
contact_name='John Doe',
contact_email='john@example.com',
)
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch('storage.org_service.OrgService.is_org_member', return_value=False),
):
# Act & Assert
with pytest.raises(OrgAuthorizationError) as exc_info:
await OrgService.switch_org(user_id, org_id)
assert 'member' in str(exc_info.value).lower()
@pytest.mark.asyncio
async def test_switch_org_user_not_found():
"""
GIVEN: User does not exist in database
WHEN: switch_org is called
THEN: OrgDatabaseError is raised
"""
# Arrange
org_id = uuid.uuid4()
user_id = str(uuid.uuid4())
mock_org = Org(
id=org_id,
name='Target Organization',
contact_name='John Doe',
contact_email='john@example.com',
)
with (
patch(
'storage.org_service.OrgStore.get_org_by_id',
new_callable=AsyncMock,
return_value=mock_org,
),
patch('storage.org_service.OrgService.is_org_member', return_value=True),
patch(
'storage.org_service.UserStore.update_current_org',
new_callable=AsyncMock,
return_value=None,
),
):
# Act & Assert
with pytest.raises(OrgDatabaseError) as exc_info:
await OrgService.switch_org(user_id, org_id)
assert 'User not found' in str(exc_info.value)