agnes-the-ai-analyst/tests/test_user_service.py
Petr c56905d34f Initial commit: OSS data distribution platform
Open-source AI data analyst platform extracted from internal repo.
Includes data sync engine, Keboola adapter, Flask web portal,
server deployment scripts, and configuration templates.
2026-03-08 23:31:28 +01:00

118 lines
4.2 KiB
Python

"""Tests for webapp.user_service SSH key validation and normalization."""
import pytest
from webapp.user_service import validate_ssh_key
# Valid SSH keys for testing
VALID_ED25519_KEY = (
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O"
" user@hostname"
)
VALID_RSA_KEY = (
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC7vbqajxMM8qR"
"ZortjjMq2bCxPkNW0KSBiAIae6MNcL+Kk6MFSwj3pvFOyEEBO4e"
"OE1JxHoOdSPsFhPeQisdit3MfG6E7CaJm3VE/ONxQat1M/V+55og"
" user@hostname"
)
class TestValidateSshKey:
"""Test SSH key validation."""
def test_valid_ed25519_key(self):
is_valid, error = validate_ssh_key(VALID_ED25519_KEY)
assert is_valid
assert error == ""
def test_valid_rsa_key(self):
is_valid, error = validate_ssh_key(VALID_RSA_KEY)
assert is_valid
assert error == ""
def test_empty_key(self):
is_valid, error = validate_ssh_key("")
assert not is_valid
assert "required" in error.lower()
def test_private_key_rejected(self):
is_valid, error = validate_ssh_key("-----BEGIN PRIVATE KEY-----\ndata\n-----END PRIVATE KEY-----")
assert not is_valid
def test_too_short_key(self):
is_valid, error = validate_ssh_key("ssh-ed25519 AAAA user@host")
assert not is_valid
assert "short" in error.lower()
def test_invalid_format(self):
is_valid, error = validate_ssh_key("not-a-valid-ssh-key-at-all")
assert not is_valid
class TestSshKeyNewlineNormalization:
"""Test that SSH keys with line breaks are normalized to single line.
This is the core fix for issue #139: webapp was writing keys with
line breaks to authorized_keys, causing SSH auth failures.
"""
def test_key_with_newlines_between_parts(self):
"""Key with newlines between type, data, and comment."""
broken_key = (
"ssh-ed25519\n"
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O\n"
"user@hostname"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key with newlines should be valid after normalization: {error}"
def test_key_with_carriage_returns(self):
"""Key with Windows-style \\r\\n line breaks."""
broken_key = (
"ssh-ed25519\r\n"
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O\r\n"
"user@hostname"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key with \\r\\n should be valid after normalization: {error}"
def test_key_with_tabs(self):
"""Key with tabs instead of spaces."""
broken_key = (
"ssh-ed25519\t"
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O\t"
"user@hostname"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key with tabs should be valid after normalization: {error}"
def test_key_with_multiple_spaces(self):
"""Key with extra spaces between parts."""
broken_key = (
"ssh-ed25519 "
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O "
"user@hostname"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key with extra spaces should be valid after normalization: {error}"
def test_key_with_leading_trailing_whitespace(self):
"""Key with leading/trailing whitespace and newlines."""
broken_key = (
"\n ssh-ed25519 "
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O "
"user@hostname \n"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key with surrounding whitespace should be valid: {error}"
def test_key_without_comment_with_newlines(self):
"""Key without comment part, with newlines."""
broken_key = (
"ssh-ed25519\n"
"AAAAC3NzaC1lZDI1NTE5AAAAIDIE6+wG3U019D4AUQVOr17xxX5enS1OUVfLZ4cHa4/O\n"
)
is_valid, error = validate_ssh_key(broken_key)
assert is_valid, f"Key without comment with newlines should be valid: {error}"