Files
GitMa/backend/tests/test_services/test_server_service.py
panw 960056c88c feat: implement Server service layer with encrypted API tokens
Implement business logic for Gitea server management:
- create_server(): Create servers with encrypted API tokens
- list_servers(): List all servers ordered by creation time
- get_server(): Retrieve server by ID
- update_server(): Update server configuration with token re-encryption
- delete_server(): Delete servers
- get_decrypted_token(): Decrypt API tokens for operations

Features:
- API token encryption using AES-256-GCM
- Automatic local_path generation based on server name
- SSH key validation before server creation
- Name uniqueness enforcement
- Timestamp tracking (created_at, updated_at)
- Repos directory auto-creation

Tests:
- 24 comprehensive test cases covering all scenarios
- Encryption verification tests
- Edge case handling (duplicates, not found, invalid references)
- All tests passing (63/63 total)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 16:13:50 +08:00

518 lines
15 KiB
Python

"""
Tests for Server Service.
"""
import base64
import pytest
import time
from pathlib import Path
from app.models.server import Server
from app.models.ssh_key import SshKey
from app.services.server_service import ServerService
from app.config import get_settings
# Valid test SSH key
VALID_SSH_KEY = """-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACB/pDNwjNcznNaRlLNF5G9hCQNjbqNZ7QeKyLIy/nvHAAAAJi/vqmQv6pk
AAAAAtzc2gtZWQyNTUxOQAAACB/pDNwjNcznNaRlLNF5G9hCQNjbqNZ7QeKyLIy/nvHAA
AAAEBD0cWNQnpLDUYEGNMSgVIApVJfCFuRfGG3uxJZRKLvqH+kM3CM1zOc1pGUssXkb2E
JA2uuo1ntB4rIsjL+e8cAAAADm1lc3NlbmdlckBrZW50cm9zBAgMEBQ=
-----END OPENSSH PRIVATE KEY-----
"""
def create_test_ssh_key(db_session, name="test-ssh-key"):
"""Helper to create a test SSH key."""
from app.services.ssh_key_service import SshKeyService
ssh_service = SshKeyService(db_session)
return ssh_service.create_ssh_key(name=name, private_key=VALID_SSH_KEY)
def test_create_server_success(db_session, test_env_vars):
"""Test successful server creation with token encryption."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="gitea-server",
url="https://gitea.example.com",
api_token="test-api-token-123",
ssh_key_id=ssh_key.id,
sync_enabled=True,
schedule_cron="0 0 * * *"
)
assert server.id is not None
assert server.name == "gitea-server"
assert server.url == "https://gitea.example.com"
assert server.api_token != "test-api-token-123" # Should be encrypted
assert server.ssh_key_id == ssh_key.id
assert server.sync_enabled is True
assert server.schedule_cron == "0 0 * * *"
assert server.local_path is not None
assert server.status == "untested"
assert server.created_at is not None
assert server.updated_at is not None
def test_create_server_with_duplicate_name(db_session, test_env_vars):
"""Test that duplicate server names are not allowed."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
service.create_server(
name="duplicate-server",
url="https://gitea1.example.com",
api_token="token1",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
with pytest.raises(ValueError, match="already exists"):
service.create_server(
name="duplicate-server",
url="https://gitea2.example.com",
api_token="token2",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
def test_create_server_with_invalid_ssh_key_id(db_session, test_env_vars):
"""Test that invalid SSH key ID is rejected."""
service = ServerService(db_session)
with pytest.raises(ValueError, match="SSH key not found"):
service.create_server(
name="test-server",
url="https://gitea.example.com",
api_token="test-token",
ssh_key_id=99999,
sync_enabled=False,
schedule_cron=None
)
def test_create_server_generates_local_path(db_session, test_env_vars):
"""Test that local_path is generated correctly based on server name."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="my-gitea",
url="https://gitea.example.com",
api_token="token123",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
settings = get_settings()
expected_path = settings.repos_dir / "my-gitea"
assert server.local_path == str(expected_path)
def test_create_server_without_schedule(db_session, test_env_vars):
"""Test creating a server without sync schedule."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="no-schedule-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
assert server.schedule_cron is None
assert server.sync_enabled is False
def test_list_servers_empty(db_session, test_env_vars):
"""Test listing servers when none exist."""
service = ServerService(db_session)
servers = service.list_servers()
assert servers == []
def test_list_servers_multiple(db_session, test_env_vars):
"""Test listing multiple servers."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
service.create_server(
name="server-1",
url="https://gitea1.example.com",
api_token="token1",
ssh_key_id=ssh_key.id,
sync_enabled=True,
schedule_cron="0 0 * * *"
)
service.create_server(
name="server-2",
url="https://gitea2.example.com",
api_token="token2",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
servers = service.list_servers()
assert len(servers) == 2
assert any(s.name == "server-1" for s in servers)
assert any(s.name == "server-2" for s in servers)
def test_get_server_by_id(db_session, test_env_vars):
"""Test getting a server by ID."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
created_server = service.create_server(
name="get-test-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
retrieved_server = service.get_server(created_server.id)
assert retrieved_server is not None
assert retrieved_server.id == created_server.id
assert retrieved_server.name == "get-test-server"
def test_get_server_not_found(db_session, test_env_vars):
"""Test getting a non-existent server."""
service = ServerService(db_session)
server = service.get_server(99999)
assert server is None
def test_update_server_name(db_session, test_env_vars):
"""Test updating server name."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="old-name",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
updated_server = service.update_server(server.id, name="new-name")
assert updated_server.name == "new-name"
assert updated_server.id == server.id
def test_update_server_url(db_session, test_env_vars):
"""Test updating server URL."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="test-server",
url="https://old-url.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
updated_server = service.update_server(
server.id,
url="https://new-url.example.com"
)
assert updated_server.url == "https://new-url.example.com"
def test_update_server_api_token(db_session, test_env_vars):
"""Test updating server API token with encryption."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="test-server",
url="https://gitea.example.com",
api_token="old-token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
updated_server = service.update_server(
server.id,
api_token="new-token"
)
# The token should be encrypted (different from original)
assert updated_server.api_token != "new-token"
def test_update_server_sync_settings(db_session, test_env_vars):
"""Test updating server sync settings."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="test-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
updated_server = service.update_server(
server.id,
sync_enabled=True,
schedule_cron="*/5 * * * *"
)
assert updated_server.sync_enabled is True
assert updated_server.schedule_cron == "*/5 * * * *"
def test_update_server_not_found(db_session, test_env_vars):
"""Test updating a non-existent server."""
service = ServerService(db_session)
with pytest.raises(ValueError, match="Server not found"):
service.update_server(99999, name="new-name")
def test_update_server_duplicate_name(db_session, test_env_vars):
"""Test that updating to duplicate name is rejected."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server1 = service.create_server(
name="server-1",
url="https://gitea1.example.com",
api_token="token1",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
server2 = service.create_server(
name="server-2",
url="https://gitea2.example.com",
api_token="token2",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
with pytest.raises(ValueError, match="already exists"):
service.update_server(server2.id, name="server-1")
def test_delete_server_success(db_session, test_env_vars):
"""Test successful server deletion."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="delete-test-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
result = service.delete_server(server.id)
assert result is True
# Verify the server is deleted
retrieved_server = service.get_server(server.id)
assert retrieved_server is None
def test_delete_server_not_found(db_session, test_env_vars):
"""Test deleting a non-existent server."""
service = ServerService(db_session)
result = service.delete_server(99999)
assert result is False
def test_get_decrypted_token(db_session, test_env_vars):
"""Test getting decrypted API token."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
original_token = "my-secret-api-token"
server = service.create_server(
name="decrypt-test-server",
url="https://gitea.example.com",
api_token=original_token,
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
decrypted_token = service.get_decrypted_token(server)
assert decrypted_token == original_token
assert decrypted_token != server.api_token # Should differ from encrypted value
def test_get_decrypted_token_with_server_object(db_session, test_env_vars):
"""Test getting decrypted token using server object from database."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
original_token = "another-secret-token"
server = service.create_server(
name="token-test-server",
url="https://gitea.example.com",
api_token=original_token,
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
# Retrieve server from DB (simulates real usage)
db_server = db_session.query(Server).filter_by(name="token-test-server").first()
decrypted_token = service.get_decrypted_token(db_server)
assert decrypted_token == original_token
def test_create_server_creates_repos_directory(db_session, test_env_vars, tmp_path):
"""Test that repos directory is created when adding a server."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="dir-test-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
settings = get_settings()
repos_dir = settings.repos_dir
assert repos_dir.exists()
def test_server_default_status(db_session, test_env_vars):
"""Test that new servers have default 'untested' status."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="status-test-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
assert server.status == "untested"
def test_update_server_updates_timestamp(db_session, test_env_vars):
"""Test that updating server updates the updated_at timestamp."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server = service.create_server(
name="timestamp-server",
url="https://gitea.example.com",
api_token="token",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
original_updated_at = server.updated_at
# Small delay to ensure timestamp difference
time.sleep(0.1)
updated_server = service.update_server(server.id, url="https://new-url.example.com")
# Verify the URL was updated (which means update_server was called)
assert updated_server.url == "https://new-url.example.com"
# The updated_at should be >= original (may be equal on fast systems)
assert updated_server.updated_at >= original_updated_at
def test_encryption_is_different(db_session, test_env_vars):
"""Test that API tokens are encrypted and different from plaintext."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
original_token = "plaintext-token-12345"
service.create_server(
name="encryption-test-server",
url="https://gitea.example.com",
api_token=original_token,
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
# Get the raw database record
db_server = db_session.query(Server).filter_by(name="encryption-test-server").first()
# The stored token should be encrypted
assert db_server.api_token != original_token
# Should be base64 encoded (longer)
assert len(db_server.api_token) > len(original_token)
def test_list_servers_ordered_by_creation(db_session, test_env_vars):
"""Test that servers are listed in creation order."""
ssh_key = create_test_ssh_key(db_session)
service = ServerService(db_session)
server1 = service.create_server(
name="first-server",
url="https://gitea1.example.com",
api_token="token1",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
server2 = service.create_server(
name="second-server",
url="https://gitea2.example.com",
api_token="token2",
ssh_key_id=ssh_key.id,
sync_enabled=False,
schedule_cron=None
)
servers = service.list_servers()
assert servers[0].id == server1.id
assert servers[1].id == server2.id