Files
connpy/connpy/tests/test_user_service.py
T
fluzzi32 0adaaad971 feat(multiuser): implementar sistema multiusuario gRPC y configuración compartida de IA/MCP
- Servidor gRPC: Agregar interceptores de autenticación y UserRegistry para aislar sesiones por usuario.
- Contexto de Hilos: Corregir propagación de ContextVar _current_user a hilos secundarios en ExecutionServicer.
- Configuración Compartida: Implementar herencia y deep merge de settings de IA ('ai') y servidores MCP en configfile.
- Hot-Reload: Recarga automática en caliente de la configuración compartida global ante cambios en disco.
- CLI: Agregar comandos e interfaces de usuario para autenticación (login) y administración de usuarios.
- Pruebas: Desarrollar tests unitarios completos (test_shared_ai.py) y resolver regresiones en la suite existente.
2026-05-28 09:27:54 -03:00

218 lines
8.8 KiB
Python

import os
import shutil
import pytest
import datetime
import jwt
import yaml
from pathlib import Path
from connpy.services.user_service import UserService
@pytest.fixture
def test_config_dir(tmp_path):
"""Creates a temporary config directory for testing user registry."""
config_dir = tmp_path / "conn_config"
config_dir.mkdir()
return config_dir
@pytest.fixture
def user_service(test_config_dir):
"""Initializes UserService pointing to a temporary directory."""
return UserService(str(test_config_dir))
class TestUserService:
def test_no_users(self, user_service):
"""Verifies that a new registry is empty by default."""
users = user_service.list_users()
assert users == []
def test_create_user_default(self, user_service):
"""Tests Mode A: fresh user config and key creation."""
username = "testuser"
res = user_service.create_user(username, "mypassword")
assert res["username"] == username
assert res["config_path"] is None
assert "created" in res
# Verify folder, config.yaml and .osk key are created
user_dir = os.path.join(user_service.users_dir, username)
assert os.path.isdir(user_dir)
assert os.path.isdir(os.path.join(user_dir, "plugins"))
assert os.path.isdir(os.path.join(user_dir, "ai_sessions"))
assert os.path.isfile(os.path.join(user_dir, "config.yaml"))
assert os.path.isfile(os.path.join(user_dir, ".osk"))
def test_create_user_custom_path(self, user_service, tmp_path):
"""Tests Mode B: using an existing valid config path."""
# Setup existing custom config directory
custom_dir = tmp_path / "custom_user_conn"
custom_dir.mkdir()
config_file = custom_dir / "config.yaml"
# Write basic config.yaml
config_data = {
"config": {"case": False, "idletime": 30, "fzf": False},
"connections": {},
"profiles": {}
}
with open(config_file, "w") as f:
yaml.dump(config_data, f)
res = user_service.create_user("fluzzi", "fluzzipass", config_path=str(custom_dir))
assert res["username"] == "fluzzi"
assert res["config_path"] == str(custom_dir)
# Verify no directory is created under the server's user folder
user_dir = os.path.join(user_service.users_dir, "fluzzi")
assert not os.path.exists(user_dir)
def test_create_user_custom_path_auto_init(self, user_service, tmp_path):
"""Ensures create_user automatically initializes a missing directory and default config.yaml."""
custom_dir = tmp_path / "new_custom_config"
# Test creation where the directory does not exist yet
res = user_service.create_user("john", "pass", config_path=str(custom_dir))
assert res["username"] == "john"
assert res["config_path"] == str(custom_dir)
# Verify custom path and subdirs/configs were created
assert os.path.isdir(custom_dir)
assert os.path.exists(os.path.join(custom_dir, "config.yaml"))
assert os.path.isdir(os.path.join(custom_dir, "plugins"))
assert os.path.isdir(os.path.join(custom_dir, "ai_sessions"))
def test_create_duplicate_user(self, user_service):
"""Ensures duplicate usernames are rejected."""
user_service.create_user("dupuser", "password")
with pytest.raises(ValueError, match="already exists"):
user_service.create_user("dupuser", "anotherpass")
def test_delete_user_default(self, user_service):
"""Tests Mode A: deleting a server-managed user cleans up directories."""
username = "deluser"
user_service.create_user(username, "password")
user_dir = os.path.join(user_service.users_dir, username)
assert os.path.isdir(user_dir)
user_service.delete_user(username)
# Directory should be cleaned up
assert not os.path.exists(user_dir)
# Registry should be updated
assert len(user_service.list_users()) == 0
def test_delete_user_custom_path(self, user_service, tmp_path):
"""Tests Mode B: deleting a custom-path user leaves files untouched."""
custom_dir = tmp_path / "fluzzi_custom"
custom_dir.mkdir()
config_file = custom_dir / "config.yaml"
with open(config_file, "w") as f:
yaml.dump({"config": {}, "connections": {}, "profiles": {}}, f)
username = "fluzzi"
user_service.create_user(username, "pass", config_path=str(custom_dir))
user_service.delete_user(username)
# Registry cleared
assert len(user_service.list_users()) == 0
# Files remain untouched
assert os.path.isdir(str(custom_dir))
assert os.path.isfile(str(config_file))
def test_list_users(self, user_service):
"""Tests listing all registered users with their metadata."""
user_service.create_user("user1", "pass1")
user_service.create_user("user2", "pass2")
users = user_service.list_users()
assert len(users) == 2
usernames = [u["username"] for u in users]
assert "user1" in usernames
assert "user2" in usernames
def test_get_user(self, user_service):
"""Tests retrieving a single user's configuration metadata."""
user_service.create_user("user1", "pass1")
user = user_service.get_user("user1")
assert user["username"] == "user1"
assert user["config_path"] is None
assert "created" in user
with pytest.raises(ValueError, match="not found"):
user_service.get_user("nonexistent")
def test_authenticate_valid(self, user_service):
"""Verifies successful authentication."""
user_service.create_user("john", "my-secure-password")
assert user_service.authenticate("john", "my-secure-password") is True
def test_authenticate_invalid(self, user_service):
"""Verifies unsuccessful authentication on incorrect or missing credentials."""
user_service.create_user("john", "my-secure-password")
assert user_service.authenticate("john", "wrong-password") is False
assert user_service.authenticate("nonexistent", "my-secure-password") is False
def test_jwt_roundtrip(self, user_service):
"""Tests generating a JWT token and verifying it back to the username."""
username = "jwttester"
user_service.create_user(username, "pass")
token = user_service.generate_jwt(username)
assert isinstance(token, str)
verified_user = user_service.verify_jwt(token)
assert verified_user == username
def test_jwt_expired(self, user_service):
"""Tests that expired JWT tokens are rejected and return None."""
username = "jwttester"
user_service.create_user(username, "pass")
# Manually generate an expired token by setting exp to the past
registry = user_service._load_registry()
expired_time = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=10)
payload = {
"sub": username,
"exp": expired_time
}
token = jwt.encode(payload, registry["jwt_secret"], algorithm="HS256")
if isinstance(token, bytes):
token = token.decode("utf-8")
verified_user = user_service.verify_jwt(token)
assert verified_user is None
def test_change_password(self, user_service):
"""Tests changing password for a user."""
username = "passchanger"
user_service.create_user(username, "oldpass")
# Old credentials authenticate
assert user_service.authenticate(username, "oldpass") is True
# Change password
user_service.change_password(username, "oldpass", "newpass")
# Old password fails, new password works
assert user_service.authenticate(username, "oldpass") is False
assert user_service.authenticate(username, "newpass") is True
# Change with invalid old password should fail
with pytest.raises(ValueError, match="Invalid credentials"):
user_service.change_password(username, "wrongold", "evennewer")
def test_admin_change_password(self, user_service):
"""Tests administrative password change (no old password required)."""
username = "adminpasschanger"
user_service.create_user(username, "oldpass")
# Admin changes password directly
user_service.admin_change_password(username, "newpass")
# Verify credentials
assert user_service.authenticate(username, "oldpass") is False
assert user_service.authenticate(username, "newpass") is True