- US-001: mută queue_client.py în data_entry/services/ocr/ - US-002/003/004: oracle_receipt_writer + oracle_server_id în DB - US-005: receipt_handlers.py (PDF/photo/callback flow) - US-006: wire handlers în main.py, per-schema connect, seq_cod.nextval - US-007: .gitignore secrets/*.oracle_pass - US-008/009/010: teste unit + integration + E2E - setup-secrets.sh helper + template - docs/telegram/README.md actualizat cu arhitectura nouă Testat E2E pe DB live (MARIUSM_AUTO). COD din seq_cod.nextval. pypdfium2 fallback pentru PDF decode (fără poppler). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
230 lines
8.3 KiB
Python
230 lines
8.3 KiB
Python
"""Unit tests for US-004: oracle_server_id in telegram_users.
|
|
|
|
Tests cover:
|
|
- link_user_to_oracle() persists server_id
|
|
- link_user_to_oracle() works without server_id (backwards compat)
|
|
- get_user() returns oracle_server_id from stored row
|
|
- get_user_auth_data() includes server_id in returned dict
|
|
- Round-trip: link with server_id → auth data returns same server_id
|
|
"""
|
|
import asyncio
|
|
import sys
|
|
import os
|
|
import pytest
|
|
import aiosqlite
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../../'))
|
|
|
|
# python-telegram-bot is not installed in the test environment; mock it so that
|
|
# linking.py can be imported (it has `from telegram import User` at top-level).
|
|
from unittest.mock import MagicMock
|
|
sys.modules.setdefault('telegram', MagicMock())
|
|
import backend.modules.telegram.auth.linking # noqa: F401 — registers module for patch() resolution
|
|
|
|
from backend.modules.telegram.db.operations import (
|
|
create_or_update_user,
|
|
link_user_to_oracle,
|
|
get_user,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def _make_test_db(tmp_path: Path) -> Path:
|
|
"""Create a minimal telegram_users table in a temp SQLite file."""
|
|
db_file = tmp_path / "test_app.db"
|
|
async with aiosqlite.connect(db_file) as db:
|
|
await db.execute("""CREATE TABLE telegram_users (
|
|
telegram_user_id INTEGER PRIMARY KEY,
|
|
username TEXT, first_name TEXT NOT NULL, last_name TEXT,
|
|
oracle_username TEXT, oracle_server_id TEXT,
|
|
jwt_token TEXT, jwt_refresh_token TEXT,
|
|
token_expires_at TIMESTAMP, linked_at TIMESTAMP,
|
|
last_active_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
is_active BOOLEAN DEFAULT 1
|
|
)""")
|
|
await db.commit()
|
|
return db_file
|
|
|
|
|
|
async def _insert_user(db_path: Path, telegram_user_id: int):
|
|
async with aiosqlite.connect(db_path) as db:
|
|
await db.execute(
|
|
"INSERT INTO telegram_users (telegram_user_id, first_name) VALUES (?, ?)",
|
|
(telegram_user_id, "Test")
|
|
)
|
|
await db.commit()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: link_user_to_oracle persists server_id
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_link_user_persists_server_id(tmp_path):
|
|
db_file = await _make_test_db(tmp_path)
|
|
await _insert_user(db_file, 12345)
|
|
|
|
with patch("backend.modules.telegram.db.operations.DB_PATH", db_file):
|
|
result = await link_user_to_oracle(
|
|
telegram_user_id=12345,
|
|
oracle_username="TESTUSER",
|
|
jwt_token="tok",
|
|
jwt_refresh_token="rtok",
|
|
token_expires_at=datetime.now() + timedelta(minutes=30),
|
|
server_id="SERVER1"
|
|
)
|
|
|
|
assert result is True
|
|
|
|
async with aiosqlite.connect(db_file) as db:
|
|
db.row_factory = aiosqlite.Row
|
|
cursor = await db.execute(
|
|
"SELECT oracle_server_id FROM telegram_users WHERE telegram_user_id = ?",
|
|
(12345,)
|
|
)
|
|
row = await cursor.fetchone()
|
|
|
|
assert row["oracle_server_id"] == "SERVER1"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_link_user_without_server_id(tmp_path):
|
|
"""Backwards compat: omitting server_id stores NULL."""
|
|
db_file = await _make_test_db(tmp_path)
|
|
await _insert_user(db_file, 22222)
|
|
|
|
with patch("backend.modules.telegram.db.operations.DB_PATH", db_file):
|
|
result = await link_user_to_oracle(
|
|
telegram_user_id=22222,
|
|
oracle_username="TESTUSER2",
|
|
jwt_token="tok",
|
|
jwt_refresh_token="rtok",
|
|
token_expires_at=datetime.now() + timedelta(minutes=30),
|
|
)
|
|
|
|
assert result is True
|
|
|
|
async with aiosqlite.connect(db_file) as db:
|
|
db.row_factory = aiosqlite.Row
|
|
cursor = await db.execute(
|
|
"SELECT oracle_server_id FROM telegram_users WHERE telegram_user_id = ?",
|
|
(22222,)
|
|
)
|
|
row = await cursor.fetchone()
|
|
|
|
assert row["oracle_server_id"] is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: get_user returns oracle_server_id
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_returns_oracle_server_id(tmp_path):
|
|
db_file = await _make_test_db(tmp_path)
|
|
await _insert_user(db_file, 33333)
|
|
|
|
with patch("backend.modules.telegram.db.operations.DB_PATH", db_file):
|
|
await link_user_to_oracle(
|
|
telegram_user_id=33333,
|
|
oracle_username="TESTUSER3",
|
|
jwt_token="tok",
|
|
jwt_refresh_token="rtok",
|
|
token_expires_at=datetime.now() + timedelta(minutes=30),
|
|
server_id="PROD_SERVER"
|
|
)
|
|
user = await get_user(33333)
|
|
|
|
assert user is not None
|
|
assert user["oracle_server_id"] == "PROD_SERVER"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Tests: get_user_auth_data returns server_id (round-trip)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_auth_data_includes_server_id(tmp_path):
|
|
"""Round-trip: link with server_id → get_user_auth_data returns same server_id."""
|
|
db_file = await _make_test_db(tmp_path)
|
|
await _insert_user(db_file, 44444)
|
|
|
|
expires_at = datetime.now() + timedelta(hours=1)
|
|
|
|
with patch("backend.modules.telegram.db.operations.DB_PATH", db_file):
|
|
await link_user_to_oracle(
|
|
telegram_user_id=44444,
|
|
oracle_username="TESTUSER4",
|
|
jwt_token="valid_token",
|
|
jwt_refresh_token="rtoken",
|
|
token_expires_at=expires_at,
|
|
server_id="ALPHA_SERVER"
|
|
)
|
|
|
|
mock_companies = [{"id": 1, "name": "Test Co"}]
|
|
mock_backend = AsyncMock()
|
|
mock_backend.__aenter__ = AsyncMock(return_value=mock_backend)
|
|
mock_backend.__aexit__ = AsyncMock(return_value=False)
|
|
mock_backend.get_user_companies = AsyncMock(return_value=mock_companies)
|
|
|
|
with (
|
|
patch("backend.modules.telegram.db.operations.DB_PATH", db_file),
|
|
patch("backend.modules.telegram.auth.linking.get_user") as mock_get_user,
|
|
patch("backend.modules.telegram.auth.linking.get_backend_client", return_value=mock_backend),
|
|
):
|
|
# Return stored user row (simulating what get_user() would return)
|
|
mock_get_user.return_value = {
|
|
"telegram_user_id": 44444,
|
|
"oracle_username": "TESTUSER4",
|
|
"oracle_server_id": "ALPHA_SERVER",
|
|
"jwt_token": "valid_token",
|
|
"jwt_refresh_token": "rtoken",
|
|
"token_expires_at": expires_at.isoformat(),
|
|
}
|
|
|
|
from backend.modules.telegram.auth.linking import get_user_auth_data
|
|
auth_data = await get_user_auth_data(44444)
|
|
|
|
assert auth_data is not None
|
|
assert "server_id" in auth_data
|
|
assert auth_data["server_id"] == "ALPHA_SERVER"
|
|
assert auth_data["username"] == "TESTUSER4"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_user_auth_data_server_id_none_when_not_set(tmp_path):
|
|
"""server_id is None in auth data when user was linked without a server_id."""
|
|
mock_companies = []
|
|
mock_backend = AsyncMock()
|
|
mock_backend.__aenter__ = AsyncMock(return_value=mock_backend)
|
|
mock_backend.__aexit__ = AsyncMock(return_value=False)
|
|
mock_backend.get_user_companies = AsyncMock(return_value=mock_companies)
|
|
|
|
expires_at = datetime.now() + timedelta(hours=1)
|
|
|
|
with (
|
|
patch("backend.modules.telegram.auth.linking.get_user") as mock_get_user,
|
|
patch("backend.modules.telegram.auth.linking.get_backend_client", return_value=mock_backend),
|
|
):
|
|
mock_get_user.return_value = {
|
|
"telegram_user_id": 55555,
|
|
"oracle_username": "TESTUSER5",
|
|
"oracle_server_id": None,
|
|
"jwt_token": "valid_token",
|
|
"jwt_refresh_token": "rtoken",
|
|
"token_expires_at": expires_at.isoformat(),
|
|
}
|
|
|
|
from backend.modules.telegram.auth.linking import get_user_auth_data
|
|
auth_data = await get_user_auth_data(55555)
|
|
|
|
assert auth_data is not None
|
|
assert "server_id" in auth_data
|
|
assert auth_data["server_id"] is None
|