Тесты и CI были добавлены

Каркас pytest с изоляцией путей в tmp: 40 тестов на валидаторы имён,
защиту от path traversal, парсеры virsh-вывода, cloud-init генерацию,
ротацию бэкапов, CSRF, rate-limit и рендеринг ключевых страниц.
GitHub Actions прогоняет compileall, bash -n всех скриптов и pytest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PKUoZwHMCHnytfswFVRUHw
Этот коммит содержится в:
Claude
2026-08-15 19:29:53 +00:00
родитель 02a6d544cf
Коммит 5cf0a6b640
6 изменённых файлов: 411 добавлений и 0 удалений
+80
Просмотреть файл
@@ -0,0 +1,80 @@
import cloud_images
def test_catalog_entries_consistent():
keys = [entry["key"] for entry in cloud_images.CATALOG]
assert len(keys) == len(set(keys))
for entry in cloud_images.CATALOG:
assert entry["arch"] in ("x86_64", "aarch64")
assert entry["url"].startswith("https://")
assert cloud_images.safe_image_filename(entry["filename"])
def test_catalog_entry_lookup():
assert cloud_images.catalog_entry("ubuntu-24.04-x86_64") is not None
assert cloud_images.catalog_entry("nope") is None
def test_catalog_for_arch_puts_native_first():
entries = cloud_images.catalog_for_arch("aarch64")
natives = [e["native"] for e in entries]
assert natives == sorted(natives, reverse=True)
assert entries[0]["arch"] == "aarch64"
entries_x86 = cloud_images.catalog_for_arch("x86_64")
assert entries_x86[0]["arch"] == "x86_64"
def test_safe_image_filename():
assert cloud_images.safe_image_filename("ubuntu-24.04-x86_64.qcow2")
assert cloud_images.safe_image_filename("img.img")
assert cloud_images.safe_image_filename("../../etc/passwd") is None
assert cloud_images.safe_image_filename("../escape.qcow2") == "escape.qcow2" # basename-санитизация
assert cloud_images.safe_image_filename("evil.iso") is None
def test_image_path_traversal_guard(tmp_path, monkeypatch):
monkeypatch.setattr(cloud_images, "CLOUD_IMAGES_DIR", tmp_path)
assert cloud_images.image_path_by_name("ok.qcow2") == tmp_path / "ok.qcow2"
# Каталоги отбрасываются до basename — результат всегда внутри CLOUD_IMAGES_DIR
assert cloud_images.image_path_by_name("../escape.qcow2") == tmp_path / "escape.qcow2"
assert cloud_images.image_path_by_name("bad name.qcow2") is None
def test_valid_cloud_username():
assert cloud_images.valid_cloud_username("admin")
assert cloud_images.valid_cloud_username("_svc-user")
assert not cloud_images.valid_cloud_username("Admin")
assert not cloud_images.valid_cloud_username("1abc")
assert not cloud_images.valid_cloud_username("")
def test_valid_ssh_key():
assert cloud_images.valid_ssh_key("") # ключ необязателен
assert cloud_images.valid_ssh_key("ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample user@host")
assert cloud_images.valid_ssh_key("ssh-rsa AAAAB3NzaC1yc2E=")
assert not cloud_images.valid_ssh_key("not a key")
assert not cloud_images.valid_ssh_key("ssh-ed25519")
def test_build_user_data_with_password():
data = cloud_images.build_user_data("vm1", "admin", 'p"ss', "")
assert data.startswith("#cloud-config")
assert "hostname: vm1" in data
assert "name: admin" in data
assert "ssh_pwauth: true" in data
assert '"p\\"ss"' in data # пароль экранирован как JSON/YAML-строка
assert "ssh_authorized_keys" not in data
def test_build_user_data_with_key_only():
key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExample user@host"
data = cloud_images.build_user_data("vm1", "admin", "", key)
assert "ssh_pwauth: false" in data
assert key in data
assert "chpasswd" not in data
def test_build_meta_data():
meta = cloud_images.build_meta_data("vm1")
assert "instance-id: virtuality-vm1" in meta
assert "local-hostname: vm1" in meta