Files
roa2web-service-auto/reports-app/telegram-bot/tests/test_auth.py
Marius Mutu a7a1bef375 Add missing test files and update .gitignore to allow test files
This commit addresses the overly restrictive .gitignore pattern that
was excluding all test files (test_*.py), including legitimate pytest
and unittest test suites essential for code quality and CI/CD.

Changes to .gitignore:
- Added negation patterns !**/tests/test_*.py and !**/test_*.py
  to allow proper test files while still blocking temporary scripts
- This enables pytest test suites to be tracked by git

Added test files (17 files):

Telegram Bot Tests (15 files):
- reports-app/telegram-bot/tests/test_auth.py
  Tests for authentication and account linking flow
- reports-app/telegram-bot/tests/test_callbacks.py
  Tests for callback query handlers
- reports-app/telegram-bot/tests/test_formatters.py
  Tests for message formatting utilities
- reports-app/telegram-bot/tests/test_formatters_extended.py
  Extended formatter tests
- reports-app/telegram-bot/tests/test_handlers_menu.py
  Tests for menu handlers
- reports-app/telegram-bot/tests/test_helpers.py
  Tests for helper functions
- reports-app/telegram-bot/tests/test_helpers_extended.py
  Extended helper tests
- reports-app/telegram-bot/tests/test_helpers_real.py
  Real integration tests for helpers
- reports-app/telegram-bot/tests/test_helpers_real_simple.py
  Simplified integration tests
- reports-app/telegram-bot/tests/test_login_flow.py
  Complete login flow integration tests
- reports-app/telegram-bot/tests/test_menus.py
  Menu system tests
- reports-app/telegram-bot/tests/test_session_company.py
  Session and company management tests
- reports-app/telegram-bot/test_claude_integration.py
  Manual integration test (Claude AI)
- reports-app/telegram-bot/test_claude_response.py
  Response formatting test
- reports-app/telegram-bot/test_db.py
  Database operations manual test

Shared Module Tests (2 files):
- shared/auth/test_auth.py
  Authentication system tests
- shared/database/test_pool.py
  Oracle connection pool tests

Security verification:
 All test files use mock objects, fixtures, and environment variables
 No hardcoded credentials or secrets found
 Safe for version control

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-25 15:09:43 +03:00

387 lines
12 KiB
Python

"""
Integration tests for Authentication and Linking Flow
Tests the complete authentication flow including:
- Linking Telegram accounts to Oracle accounts
- Token management and refresh
- User verification
- Account unlinking
"""
import pytest
import pytest_asyncio
import sys
import aiosqlite
import secrets
import string
from pathlib import Path
from unittest.mock import AsyncMock, Mock, patch
from datetime import datetime, timedelta
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from app.auth.linking import (
link_telegram_account,
get_user_auth_data,
check_user_linked,
get_user_companies,
unlink_user
)
from app.db.database import init_database, DB_PATH
from app.db.operations import (
create_auth_code,
create_or_update_user,
get_user
)
# ============================================================================
# HELPER FUNCTIONS
# ============================================================================
def generate_auth_code() -> str:
"""Generate an 8-character auth code"""
chars = string.ascii_uppercase + string.digits
chars = chars.replace('O', '').replace('0', '').replace('I', '').replace('1', '')
return ''.join(secrets.choice(chars) for _ in range(8))
# ============================================================================
# FIXTURES
# ============================================================================
@pytest_asyncio.fixture
async def clean_test_database():
"""Create a clean test database before each test"""
await init_database()
async with aiosqlite.connect(DB_PATH) as db:
await db.execute("DELETE FROM telegram_sessions")
await db.execute("DELETE FROM telegram_auth_codes")
await db.execute("DELETE FROM telegram_users")
await db.commit()
yield
@pytest.fixture
def mock_telegram_user():
"""Mock Telegram User object"""
user = Mock()
user.id = 123456789
user.username = "testuser"
user.first_name = "Test"
user.last_name = "User"
return user
@pytest.fixture
def mock_oracle_username():
"""Mock Oracle username"""
return "test_oracle_user"
@pytest.fixture
def mock_jwt_token():
"""Mock JWT token"""
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.test.token"
@pytest.fixture
def mock_companies_data():
"""Mock companies list"""
return [
{"id": 1, "nume_firma": "Test Company SRL", "cui": "12345678"},
{"id": 2, "nume_firma": "Another Corp SRL", "cui": "87654321"}
]
@pytest.fixture
def mock_backend_verify_response(mock_jwt_token, mock_companies_data):
"""Mock backend verify user response"""
return {
"jwt_token": mock_jwt_token,
"jwt_refresh_token": f"{mock_jwt_token}_refresh",
"companies": mock_companies_data,
"permissions": ["read", "write"]
}
async def create_test_auth_code(telegram_user_id: int, oracle_username: str) -> str:
"""Helper to create auth code for tests"""
code = generate_auth_code()
await create_auth_code(code, telegram_user_id, oracle_username)
return code
# ============================================================================
# TEST: link_telegram_account
# ============================================================================
@pytest.mark.asyncio
async def test_link_telegram_account_success(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response
):
"""Test successful linking of Telegram account to Oracle account"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
result = await link_telegram_account(mock_telegram_user, code)
assert result is not None
assert result["success"] is True
assert result["telegram_user_id"] == mock_telegram_user.id
assert result["username"] == mock_oracle_username
mock_client.verify_user.assert_called_once_with(mock_oracle_username)
@pytest.mark.asyncio
async def test_link_telegram_account_invalid_code(
clean_test_database,
mock_telegram_user
):
"""Test linking with invalid auth code"""
result = await link_telegram_account(mock_telegram_user, "INVALID1")
assert result is None
@pytest.mark.asyncio
async def test_link_telegram_account_expired_code(
clean_test_database,
mock_telegram_user,
mock_oracle_username
):
"""Test linking with expired auth code"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
# Manually expire the code
async with aiosqlite.connect(DB_PATH) as db:
expired_time = datetime.now() - timedelta(minutes=20)
await db.execute("""
UPDATE telegram_auth_codes
SET expires_at = ?
WHERE code = ?
""", (expired_time.isoformat(), code))
await db.commit()
result = await link_telegram_account(mock_telegram_user, code)
assert result is None
# ============================================================================
# TEST: get_user_auth_data
# ============================================================================
@pytest.mark.asyncio
async def test_get_user_auth_data_success(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response,
mock_companies_data
):
"""Test getting auth data for linked user"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.get_user_companies.return_value = mock_companies_data
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
await link_telegram_account(mock_telegram_user, code)
auth_data = await get_user_auth_data(mock_telegram_user.id)
assert auth_data is not None
assert auth_data["username"] == mock_oracle_username
assert len(auth_data["companies"]) == 2
@pytest.mark.asyncio
async def test_get_user_auth_data_not_linked(
clean_test_database,
mock_telegram_user
):
"""Test getting auth data for user who is not linked"""
await create_or_update_user(
telegram_user_id=mock_telegram_user.id,
username=mock_telegram_user.username,
first_name=mock_telegram_user.first_name,
last_name=mock_telegram_user.last_name
)
auth_data = await get_user_auth_data(mock_telegram_user.id)
assert auth_data is None
# ============================================================================
# TEST: check_user_linked
# ============================================================================
@pytest.mark.asyncio
async def test_check_user_linked_true(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response
):
"""Test checking if user is linked (linked user)"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
await link_telegram_account(mock_telegram_user, code)
is_linked = await check_user_linked(mock_telegram_user.id)
assert is_linked is True
@pytest.mark.asyncio
async def test_check_user_linked_false(
clean_test_database,
mock_telegram_user
):
"""Test checking if user is linked (not linked user)"""
await create_or_update_user(
telegram_user_id=mock_telegram_user.id,
username=mock_telegram_user.username,
first_name=mock_telegram_user.first_name,
last_name=mock_telegram_user.last_name
)
is_linked = await check_user_linked(mock_telegram_user.id)
assert is_linked is False
# ============================================================================
# TEST: get_user_companies
# ============================================================================
@pytest.mark.asyncio
async def test_get_user_companies_success(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response,
mock_companies_data
):
"""Test getting companies for linked user"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.get_user_companies.return_value = mock_companies_data
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
await link_telegram_account(mock_telegram_user, code)
companies = await get_user_companies(mock_telegram_user.id)
assert companies is not None
assert len(companies) == 2
# ============================================================================
# TEST: unlink_user
# ============================================================================
@pytest.mark.asyncio
async def test_unlink_user_success(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response
):
"""Test unlinking a linked user"""
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
await link_telegram_account(mock_telegram_user, code)
# Verify linked
assert await check_user_linked(mock_telegram_user.id) is True
# Unlink
result = await unlink_user(mock_telegram_user.id)
assert result is True
# Verify unlinked
assert await check_user_linked(mock_telegram_user.id) is False
# ============================================================================
# TEST: Complete Integration Workflow
# ============================================================================
@pytest.mark.asyncio
async def test_complete_auth_workflow(
clean_test_database,
mock_telegram_user,
mock_oracle_username,
mock_backend_verify_response,
mock_companies_data
):
"""Test complete authentication workflow from start to finish"""
with patch('app.auth.linking.get_backend_client') as mock_get_client:
mock_client = AsyncMock()
mock_client.verify_user.return_value = mock_backend_verify_response
mock_client.get_user_companies.return_value = mock_companies_data
mock_client.__aenter__.return_value = mock_client
mock_client.__aexit__.return_value = None
mock_get_client.return_value = mock_client
# Step 1: User is not linked initially
assert await check_user_linked(mock_telegram_user.id) is False
# Step 2: Create auth code and link
code = await create_test_auth_code(mock_telegram_user.id, mock_oracle_username)
link_result = await link_telegram_account(mock_telegram_user, code)
assert link_result["success"] is True
# Step 3: User is now linked
assert await check_user_linked(mock_telegram_user.id) is True
# Step 4: Get auth data
auth_data = await get_user_auth_data(mock_telegram_user.id)
assert auth_data["username"] == mock_oracle_username
# Step 5: Get companies
companies = await get_user_companies(mock_telegram_user.id)
assert len(companies) == 2
# Step 6: Unlink account
assert await unlink_user(mock_telegram_user.id) is True
# Step 7: User is no longer linked
assert await check_user_linked(mock_telegram_user.id) is False
if __name__ == "__main__":
pytest.main([__file__, "-v"])