Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
318 lines
13 KiB
Python
318 lines
13 KiB
Python
"""
|
|
Tests pour les routes de gestion des playbooks.
|
|
|
|
Couvre:
|
|
- Lecture du contenu d'un playbook
|
|
- Sauvegarde du contenu d'un playbook
|
|
- Validation YAML
|
|
- Sécurité des chemins
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock
|
|
from pathlib import Path
|
|
from httpx import AsyncClient
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
class TestGetPlaybookContent:
|
|
"""Tests pour GET /api/playbooks/{filename}/content."""
|
|
|
|
async def test_get_playbook_content_success(self, client: AsyncClient, tmp_path):
|
|
"""Lecture du contenu d'un playbook."""
|
|
playbook_content = "---\n- name: Test\n hosts: all\n tasks: []"
|
|
playbook_file = tmp_path / "test.yml"
|
|
playbook_file.write_text(playbook_content)
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.get("/api/playbooks/test.yml/content")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["filename"] == "test.yml"
|
|
assert data["content"] == playbook_content
|
|
assert "size" in data
|
|
assert "modified" in data
|
|
|
|
async def test_get_playbook_invalid_extension(self, client: AsyncClient):
|
|
"""Erreur si extension invalide."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = Path("/tmp")
|
|
|
|
response = await client.get("/api/playbooks/test.txt/content")
|
|
|
|
assert response.status_code == 400
|
|
assert "Extension" in response.json()["detail"]
|
|
|
|
async def test_get_playbook_not_found(self, client: AsyncClient, tmp_path):
|
|
"""Erreur si playbook non trouvé."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.get("/api/playbooks/nonexistent.yml/content")
|
|
|
|
assert response.status_code == 404
|
|
|
|
async def test_get_playbook_path_traversal_blocked(self, client: AsyncClient, tmp_path):
|
|
"""Bloque les tentatives de path traversal."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
# Create a file outside playbooks dir
|
|
(tmp_path.parent / "secret.yml").write_text("secret")
|
|
|
|
response = await client.get("/api/playbooks/../secret.yml/content")
|
|
|
|
# Should be blocked (404 or 403)
|
|
assert response.status_code in [403, 404]
|
|
|
|
|
|
class TestSavePlaybookContent:
|
|
"""Tests pour PUT /api/playbooks/{filename}/content."""
|
|
|
|
async def test_save_playbook_success(self, client: AsyncClient, tmp_path):
|
|
"""Sauvegarde du contenu d'un playbook."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible, \
|
|
patch("app.routes.playbooks.db") as mock_db:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
mock_db.get_next_id.return_value = 1
|
|
mock_db.logs = []
|
|
|
|
response = await client.put(
|
|
"/api/playbooks/new-playbook.yml/content",
|
|
json={"content": "---\n- name: New\n hosts: all\n tasks: []"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "créé" in data["message"]
|
|
|
|
async def test_save_playbook_update_existing(self, client: AsyncClient, tmp_path):
|
|
"""Mise à jour d'un playbook existant."""
|
|
existing = tmp_path / "existing.yml"
|
|
existing.write_text("---\n- name: Old\n hosts: all")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible, \
|
|
patch("app.routes.playbooks.db") as mock_db:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
mock_db.get_next_id.return_value = 1
|
|
mock_db.logs = []
|
|
|
|
response = await client.put(
|
|
"/api/playbooks/existing.yml/content",
|
|
json={"content": "---\n- name: Updated\n hosts: all\n tasks: []"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "sauvegardé" in data["message"]
|
|
|
|
async def test_save_playbook_invalid_extension(self, client: AsyncClient):
|
|
"""Erreur si extension invalide."""
|
|
response = await client.put(
|
|
"/api/playbooks/test.txt/content",
|
|
json={"content": "test"}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
async def test_save_playbook_invalid_filename(self, client: AsyncClient):
|
|
"""Erreur si nom de fichier invalide."""
|
|
response = await client.put(
|
|
"/api/playbooks/../../etc/passwd.yml/content",
|
|
json={"content": "---\n- name: Test"}
|
|
)
|
|
|
|
# Should be blocked (400 or 404)
|
|
assert response.status_code in [400, 404]
|
|
|
|
async def test_save_playbook_invalid_yaml(self, client: AsyncClient, tmp_path):
|
|
"""Erreur si YAML invalide."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.put(
|
|
"/api/playbooks/invalid.yml/content",
|
|
json={"content": "not: valid: yaml: ["}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "YAML" in response.json()["detail"]
|
|
|
|
async def test_save_playbook_empty_yaml(self, client: AsyncClient, tmp_path):
|
|
"""Erreur si YAML vide."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.put(
|
|
"/api/playbooks/empty.yml/content",
|
|
json={"content": ""}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
|
|
|
|
class TestDeletePlaybook:
|
|
"""Tests pour DELETE /api/playbooks/{filename}."""
|
|
|
|
async def test_delete_playbook_success(self, client: AsyncClient, tmp_path):
|
|
"""Suppression d'un playbook."""
|
|
playbook_file = tmp_path / "to-delete.yml"
|
|
playbook_file.write_text("---\n- name: Test\n hosts: all")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible, \
|
|
patch("app.routes.playbooks.db") as mock_db:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
mock_db.get_next_id.return_value = 1
|
|
mock_db.logs = []
|
|
|
|
response = await client.delete("/api/playbooks/to-delete.yml")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert "supprimé" in data["message"]
|
|
assert not playbook_file.exists()
|
|
|
|
async def test_delete_playbook_invalid_extension(self, client: AsyncClient):
|
|
"""Erreur si extension invalide."""
|
|
response = await client.delete("/api/playbooks/test.txt")
|
|
|
|
assert response.status_code == 400
|
|
assert "Extension" in response.json()["detail"]
|
|
|
|
async def test_delete_playbook_not_found(self, client: AsyncClient, tmp_path):
|
|
"""Erreur si playbook non trouvé."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.delete("/api/playbooks/nonexistent.yml")
|
|
|
|
assert response.status_code == 404
|
|
|
|
async def test_delete_playbook_path_traversal_blocked(self, client: AsyncClient, tmp_path):
|
|
"""Bloque les tentatives de path traversal lors de la suppression."""
|
|
# Create a file outside playbooks dir
|
|
secret_file = tmp_path.parent / "secret.yml"
|
|
secret_file.write_text("secret content")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.delete("/api/playbooks/../secret.yml")
|
|
|
|
# Should be blocked (403 or 404)
|
|
assert response.status_code in [403, 404]
|
|
# File should still exist
|
|
assert secret_file.exists()
|
|
|
|
|
|
class TestPlaybookContentReadError:
|
|
"""Tests pour les erreurs de lecture de playbook."""
|
|
|
|
async def test_get_playbook_read_error(self, client: AsyncClient, tmp_path):
|
|
"""Erreur lors de la lecture du fichier."""
|
|
playbook_file = tmp_path / "unreadable.yml"
|
|
playbook_file.write_text("content")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
# Mock read_text to raise an exception
|
|
with patch.object(Path, 'read_text', side_effect=PermissionError("Access denied")):
|
|
response = await client.get("/api/playbooks/unreadable.yml/content")
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur lecture" in response.json()["detail"]
|
|
|
|
|
|
class TestPlaybookContentWriteError:
|
|
"""Tests pour les erreurs d'écriture de playbook."""
|
|
|
|
async def test_save_playbook_write_error(self, client: AsyncClient, tmp_path):
|
|
"""Erreur lors de l'écriture du fichier."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
# Mock write_text to raise an exception
|
|
with patch.object(Path, 'write_text', side_effect=PermissionError("Access denied")):
|
|
response = await client.put(
|
|
"/api/playbooks/error.yml/content",
|
|
json={"content": "---\n- name: Test\n hosts: all"}
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur sauvegarde" in response.json()["detail"]
|
|
|
|
|
|
class TestPlaybookDeleteError:
|
|
"""Tests pour les erreurs de suppression de playbook."""
|
|
|
|
async def test_delete_playbook_unlink_error(self, client: AsyncClient, tmp_path):
|
|
"""Erreur lors de la suppression du fichier."""
|
|
playbook_file = tmp_path / "error-delete.yml"
|
|
playbook_file.write_text("content")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
# Mock unlink to raise an exception
|
|
with patch.object(Path, 'unlink', side_effect=PermissionError("Access denied")):
|
|
response = await client.delete("/api/playbooks/error-delete.yml")
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur suppression" in response.json()["detail"]
|
|
|
|
|
|
class TestPlaybookYamlExtension:
|
|
"""Tests pour les extensions .yaml."""
|
|
|
|
async def test_get_playbook_yaml_extension(self, client: AsyncClient, tmp_path):
|
|
"""Lecture d'un playbook avec extension .yaml."""
|
|
playbook_content = "---\n- name: Test\n hosts: all"
|
|
playbook_file = tmp_path / "test.yaml"
|
|
playbook_file.write_text(playbook_content)
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
|
|
response = await client.get("/api/playbooks/test.yaml/content")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["filename"] == "test.yaml"
|
|
|
|
async def test_save_playbook_yaml_extension(self, client: AsyncClient, tmp_path):
|
|
"""Sauvegarde d'un playbook avec extension .yaml."""
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible, \
|
|
patch("app.routes.playbooks.db") as mock_db:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
mock_db.get_next_id.return_value = 1
|
|
mock_db.logs = []
|
|
|
|
response = await client.put(
|
|
"/api/playbooks/new-playbook.yaml/content",
|
|
json={"content": "---\n- name: New\n hosts: all"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
async def test_delete_playbook_yaml_extension(self, client: AsyncClient, tmp_path):
|
|
"""Suppression d'un playbook avec extension .yaml."""
|
|
playbook_file = tmp_path / "delete.yaml"
|
|
playbook_file.write_text("---\n- name: Test")
|
|
|
|
with patch("app.routes.playbooks.ansible_service") as mock_ansible, \
|
|
patch("app.routes.playbooks.db") as mock_db:
|
|
mock_ansible.playbooks_dir = tmp_path
|
|
mock_db.get_next_id.return_value = 1
|
|
mock_db.logs = []
|
|
|
|
response = await client.delete("/api/playbooks/delete.yaml")
|
|
|
|
assert response.status_code == 200
|