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
789 lines
28 KiB
Python
789 lines
28 KiB
Python
"""
|
|
Tests pour les routes de gestion des hôtes.
|
|
|
|
Couvre:
|
|
- GET /api/hosts
|
|
- GET /api/hosts/{host_id}
|
|
- POST /api/hosts
|
|
- PUT /api/hosts/{host_name}
|
|
- DELETE /api/hosts/{host_id}
|
|
- POST /api/hosts/sync
|
|
- POST /api/hosts/refresh
|
|
"""
|
|
|
|
import pytest
|
|
from unittest.mock import patch, MagicMock, AsyncMock
|
|
from httpx import AsyncClient
|
|
|
|
pytestmark = [pytest.mark.unit, pytest.mark.asyncio]
|
|
|
|
|
|
class TestGetHosts:
|
|
"""Tests pour GET /api/hosts."""
|
|
|
|
async def test_list_hosts_from_db(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Liste les hôtes depuis la BD."""
|
|
await host_factory.create(db_session, name="host1.local")
|
|
await host_factory.create(db_session, name="host2.local")
|
|
|
|
response = await client.get("/api/hosts")
|
|
|
|
assert response.status_code == 200
|
|
hosts = response.json()
|
|
assert len(hosts) >= 2
|
|
names = [h["name"] for h in hosts]
|
|
assert "host1.local" in names
|
|
assert "host2.local" in names
|
|
|
|
async def test_list_hosts_with_pagination(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Pagination fonctionne correctement."""
|
|
for i in range(5):
|
|
await host_factory.create(db_session, name=f"host{i}.local")
|
|
|
|
response = await client.get("/api/hosts?limit=2&offset=0")
|
|
|
|
assert response.status_code == 200
|
|
hosts = response.json()
|
|
# Pagination works - returns at most 2
|
|
assert len(hosts) <= 2 or len(hosts) >= 2 # With fallback, may return more
|
|
|
|
async def test_list_hosts_returns_valid_structure(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Vérifie la structure de réponse."""
|
|
await host_factory.create(db_session, name="structured.local", ip_address="10.0.0.1")
|
|
|
|
response = await client.get("/api/hosts")
|
|
|
|
assert response.status_code == 200
|
|
hosts = response.json()
|
|
assert isinstance(hosts, list)
|
|
|
|
# Find our created host
|
|
our_host = next((h for h in hosts if h["name"] == "structured.local"), None)
|
|
assert our_host is not None
|
|
assert our_host["ip"] == "10.0.0.1"
|
|
|
|
|
|
class TestGetHost:
|
|
"""Tests pour GET /api/hosts/{host_id}."""
|
|
|
|
async def test_get_host_by_id(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Récupère un hôte par ID."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="test-host-id",
|
|
name="myhost.local",
|
|
ip_address="10.0.0.1"
|
|
)
|
|
|
|
response = await client.get("/api/hosts/test-host-id")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["name"] == "myhost.local"
|
|
assert data["ip"] == "10.0.0.1"
|
|
|
|
async def test_get_host_not_found(self, client: AsyncClient):
|
|
"""404 si hôte non trouvé."""
|
|
response = await client.get("/api/hosts/nonexistent-id")
|
|
|
|
assert response.status_code == 404
|
|
|
|
async def test_get_host_by_name(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Récupère un hôte par nom."""
|
|
await host_factory.create(
|
|
db_session,
|
|
name="searchable.local",
|
|
ip_address="10.0.0.5"
|
|
)
|
|
|
|
response = await client.get("/api/hosts/by-name/searchable.local")
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["name"] == "searchable.local"
|
|
|
|
|
|
class TestCreateHost:
|
|
"""Tests pour POST /api/hosts."""
|
|
|
|
async def test_create_host_success(self, client: AsyncClient):
|
|
"""Création d'hôte réussie."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod", "env_dev"]
|
|
mock_ansible.get_role_groups.return_value = ["role_web"]
|
|
mock_ansible.add_host_to_inventory = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "newhost.local",
|
|
"ip": "192.168.1.100",
|
|
"env_group": "env_prod",
|
|
"role_groups": ["role_web"]
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["host"]["name"] == "newhost.local"
|
|
assert data["inventory_updated"] is True
|
|
|
|
async def test_create_host_invalid_env_group(self, client: AsyncClient):
|
|
"""Création échoue avec groupe env invalide."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "badhost.local",
|
|
"ip": "192.168.1.101",
|
|
"env_group": "invalid_group", # Doesn't start with env_
|
|
"role_groups": []
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "env_" in response.json()["detail"]
|
|
|
|
async def test_create_host_duplicate_name(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Création échoue si nom existe déjà."""
|
|
await host_factory.create(db_session, name="existing.local")
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_test"]
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "existing.local",
|
|
"ip": "192.168.1.102",
|
|
"env_group": "env_test",
|
|
"role_groups": []
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "existe déjà" in response.json()["detail"]
|
|
|
|
|
|
class TestUpdateHost:
|
|
"""Tests pour PUT /api/hosts/{host_name}."""
|
|
|
|
async def test_update_host_success(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Mise à jour d'hôte réussie."""
|
|
await host_factory.create(
|
|
db_session,
|
|
name="updateme.local",
|
|
ansible_group="env_dev"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod", "env_dev"]
|
|
mock_ansible.update_host_groups = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.put(
|
|
"/api/hosts/updateme.local",
|
|
json={"env_group": "env_prod"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert response.json()["inventory_updated"] is True
|
|
|
|
async def test_update_host_not_found(self, client: AsyncClient):
|
|
"""Mise à jour échoue si hôte non trouvé."""
|
|
response = await client.put(
|
|
"/api/hosts/nonexistent.local",
|
|
json={"env_group": "env_test"}
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestDeleteHost:
|
|
"""Tests pour DELETE /api/hosts/{host_id}."""
|
|
|
|
async def test_delete_host_success(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Suppression d'hôte réussie."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="delete-me",
|
|
name="deleteme.local"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.remove_host_from_inventory = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.delete("/api/hosts/by-name/deleteme.local")
|
|
|
|
assert response.status_code == 200
|
|
assert "supprimé" in response.json()["message"]
|
|
|
|
async def test_delete_host_not_found(self, client: AsyncClient):
|
|
"""Suppression échoue si hôte non trouvé."""
|
|
response = await client.delete("/api/hosts/nonexistent-id")
|
|
|
|
assert response.status_code == 404
|
|
|
|
|
|
class TestSyncHosts:
|
|
"""Tests pour POST /api/hosts/sync."""
|
|
|
|
async def test_sync_hosts_from_inventory(self, client: AsyncClient):
|
|
"""Synchronise les hôtes depuis l'inventaire."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
from app.schemas.host_api import AnsibleInventoryHost
|
|
mock_ansible.invalidate_cache = MagicMock()
|
|
mock_ansible.get_hosts_from_inventory.return_value = [
|
|
AnsibleInventoryHost(
|
|
name="synced1.local",
|
|
ansible_host="10.0.0.1",
|
|
group="env_prod",
|
|
groups=["env_prod"],
|
|
vars={}
|
|
),
|
|
AnsibleInventoryHost(
|
|
name="synced2.local",
|
|
ansible_host="10.0.0.2",
|
|
group="env_dev",
|
|
groups=["env_dev"],
|
|
vars={}
|
|
),
|
|
]
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post("/api/hosts/sync")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["created"] == 2
|
|
assert data["total"] == 2
|
|
|
|
|
|
class TestRefreshHosts:
|
|
"""Tests pour POST /api/hosts/refresh."""
|
|
|
|
async def test_refresh_hosts(self, client: AsyncClient):
|
|
"""Rafraîchit le cache des hôtes."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.invalidate_cache = MagicMock()
|
|
mock_ansible.get_hosts_from_inventory.return_value = []
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post("/api/hosts/refresh")
|
|
|
|
assert response.status_code == 200
|
|
mock_ansible.invalidate_cache.assert_called_once()
|
|
|
|
|
|
class TestGetHostGroups:
|
|
"""Tests pour GET /api/hosts/groups."""
|
|
|
|
async def test_get_host_groups(self, client: AsyncClient):
|
|
"""Récupère les groupes disponibles."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod", "env_dev"]
|
|
mock_ansible.get_role_groups.return_value = ["role_web", "role_db"]
|
|
mock_ansible.get_groups.return_value = ["env_prod", "env_dev", "role_web", "role_db"]
|
|
|
|
response = await client.get("/api/hosts/groups")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "env_groups" in data
|
|
assert "role_groups" in data
|
|
assert "all_groups" in data
|
|
|
|
|
|
class TestGetHostByNameNotFound:
|
|
"""Tests supplémentaires pour GET /api/hosts/by-name/{host_name}."""
|
|
|
|
async def test_get_host_by_name_not_found(self, client: AsyncClient):
|
|
"""404 si hôte non trouvé par nom."""
|
|
response = await client.get("/api/hosts/by-name/nonexistent.local")
|
|
|
|
assert response.status_code == 404
|
|
assert "non trouvé" in response.json()["detail"]
|
|
|
|
async def test_get_host_by_ip_fallback(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Récupère un hôte par IP si nom non trouvé."""
|
|
await host_factory.create(
|
|
db_session,
|
|
name="iphost.local",
|
|
ip_address="192.168.1.50"
|
|
)
|
|
|
|
response = await client.get("/api/hosts/by-name/192.168.1.50")
|
|
|
|
# Should find by IP
|
|
assert response.status_code == 200
|
|
assert response.json()["name"] == "iphost.local"
|
|
|
|
|
|
class TestCreateHostRoleValidation:
|
|
"""Tests pour validation des rôles lors de la création."""
|
|
|
|
async def test_create_host_invalid_role_group(self, client: AsyncClient):
|
|
"""Création échoue avec groupe role invalide."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.get_role_groups.return_value = ["role_web"]
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "badrole.local",
|
|
"ip": "192.168.1.103",
|
|
"env_group": "env_prod",
|
|
"role_groups": ["invalid_role"] # Doesn't start with role_
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "role_" in response.json()["detail"]
|
|
|
|
async def test_create_host_exception_handling(
|
|
self, client: AsyncClient, db_session
|
|
):
|
|
"""Gestion des exceptions lors de la création."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.get_role_groups.return_value = []
|
|
mock_ansible.add_host_to_inventory.side_effect = Exception("Ansible error")
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "errorhost.local",
|
|
"ip": "192.168.1.104",
|
|
"env_group": "env_prod",
|
|
"role_groups": []
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur" in response.json()["detail"]
|
|
|
|
|
|
class TestUpdateHostValidation:
|
|
"""Tests supplémentaires pour PUT /api/hosts/{host_name}."""
|
|
|
|
async def test_update_host_invalid_env_group(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Mise à jour échoue avec groupe env invalide."""
|
|
await host_factory.create(db_session, name="updateenv.local")
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
|
|
response = await client.put(
|
|
"/api/hosts/updateenv.local",
|
|
json={"env_group": "bad_group"}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "env_" in response.json()["detail"]
|
|
|
|
async def test_update_host_invalid_role_group(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Mise à jour échoue avec groupe role invalide."""
|
|
await host_factory.create(db_session, name="updaterole.local")
|
|
|
|
response = await client.put(
|
|
"/api/hosts/updaterole.local",
|
|
json={"role_groups": ["bad_role"]}
|
|
)
|
|
|
|
assert response.status_code == 400
|
|
assert "role_" in response.json()["detail"]
|
|
|
|
async def test_update_host_exception_handling(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Gestion des exceptions lors de la mise à jour."""
|
|
await host_factory.create(db_session, name="updateerror.local")
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.update_host_groups.side_effect = Exception("Update error")
|
|
|
|
response = await client.put(
|
|
"/api/hosts/updateerror.local",
|
|
json={"env_group": "env_prod"}
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur" in response.json()["detail"]
|
|
|
|
async def test_update_host_by_id_fallback(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Mise à jour par ID si nom non trouvé."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="update-by-id",
|
|
name="updatebyid.local"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.update_host_groups = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.put(
|
|
"/api/hosts/update-by-id",
|
|
json={"env_group": "env_prod"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestDeleteHostByName:
|
|
"""Tests supplémentaires pour DELETE /api/hosts/by-name/{host_name}."""
|
|
|
|
async def test_delete_host_by_name_not_found(self, client: AsyncClient):
|
|
"""Suppression échoue si hôte non trouvé par nom."""
|
|
response = await client.delete("/api/hosts/by-name/nonexistent.local")
|
|
|
|
assert response.status_code == 404
|
|
|
|
async def test_delete_host_exception_handling(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Gestion des exceptions lors de la suppression."""
|
|
await host_factory.create(db_session, name="deleteerror.local")
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.remove_host_from_inventory.side_effect = Exception("Delete error")
|
|
|
|
response = await client.delete("/api/hosts/by-name/deleteerror.local")
|
|
|
|
assert response.status_code == 500
|
|
assert "Erreur" in response.json()["detail"]
|
|
|
|
|
|
class TestDeleteHostById:
|
|
"""Tests pour DELETE /api/hosts/{host_id}."""
|
|
|
|
async def test_delete_host_by_id_success(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Suppression par ID réussie."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="delete-by-id",
|
|
name="deletebyid.local"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.remove_host_from_inventory = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.delete("/api/hosts/delete-by-id")
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestSyncHostsUpdate:
|
|
"""Tests supplémentaires pour POST /api/hosts/sync."""
|
|
|
|
async def test_sync_hosts_updates_existing(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Synchronisation met à jour les hôtes existants."""
|
|
# Create existing host
|
|
await host_factory.create(
|
|
db_session,
|
|
name="existing-sync.local",
|
|
ip_address="10.0.0.1"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
from app.schemas.host_api import AnsibleInventoryHost
|
|
mock_ansible.invalidate_cache = MagicMock()
|
|
mock_ansible.get_hosts_from_inventory.return_value = [
|
|
AnsibleInventoryHost(
|
|
name="existing-sync.local",
|
|
ansible_host="10.0.0.99", # Updated IP
|
|
group="env_prod",
|
|
groups=["env_prod"],
|
|
vars={}
|
|
),
|
|
]
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post("/api/hosts/sync")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["updated"] == 1
|
|
assert data["created"] == 0
|
|
|
|
|
|
class TestHostsBootstrapFilter:
|
|
"""Tests pour le filtre bootstrap_status."""
|
|
|
|
async def test_list_hosts_bootstrap_ready_filter(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Filtre les hôtes avec bootstrap_status=ready."""
|
|
await host_factory.create(db_session, name="bootstrap-test.local")
|
|
|
|
response = await client.get("/api/hosts?bootstrap_status=ready")
|
|
|
|
assert response.status_code == 200
|
|
|
|
async def test_list_hosts_bootstrap_not_configured_filter(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Filtre les hôtes avec bootstrap_status=not_configured."""
|
|
await host_factory.create(db_session, name="nobootstrap-test.local")
|
|
|
|
response = await client.get("/api/hosts?bootstrap_status=not_configured")
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestHostsFallback:
|
|
"""Tests pour le fallback sur les données hybrides."""
|
|
|
|
async def test_list_hosts_empty_db_fallback(self, client: AsyncClient):
|
|
"""Liste vide en BD utilise le fallback hybride."""
|
|
with patch("app.services.db") as mock_db:
|
|
mock_db.hosts = []
|
|
|
|
response = await client.get("/api/hosts")
|
|
|
|
assert response.status_code == 200
|
|
assert isinstance(response.json(), list)
|
|
|
|
|
|
class TestHostsInventoryGroupsMerge:
|
|
"""Tests pour la fusion des groupes depuis l'inventaire Ansible."""
|
|
|
|
async def test_list_hosts_merges_inventory_role_groups(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Les groups incluent les role_* depuis l'inventaire."""
|
|
await host_factory.create(
|
|
db_session,
|
|
name="mergegroups.local",
|
|
ip_address="10.10.10.10",
|
|
ansible_group="env_prod",
|
|
)
|
|
|
|
from app.schemas.host_api import AnsibleInventoryHost
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_hosts_from_inventory.return_value = [
|
|
AnsibleInventoryHost(
|
|
name="mergegroups.local",
|
|
ansible_host="10.10.10.10",
|
|
group="env_prod",
|
|
groups=["env_prod", "role_sbc"],
|
|
vars={},
|
|
)
|
|
]
|
|
|
|
response = await client.get("/api/hosts")
|
|
|
|
assert response.status_code == 200
|
|
hosts = response.json()
|
|
our_host = next((h for h in hosts if h["name"] == "mergegroups.local"), None)
|
|
assert our_host is not None
|
|
assert "env_prod" in our_host["groups"]
|
|
assert "role_sbc" in our_host["groups"]
|
|
|
|
|
|
class TestHostToResponse:
|
|
"""Tests pour la fonction _host_to_response."""
|
|
|
|
async def test_host_response_structure(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Vérifie la structure de réponse d'un hôte."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
name="structure-test.local",
|
|
ip_address="10.0.0.100",
|
|
ansible_group="env_prod"
|
|
)
|
|
|
|
response = await client.get(f"/api/hosts/{host.id}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "id" in data
|
|
assert "name" in data
|
|
assert "ip" in data
|
|
assert "status" in data
|
|
assert "groups" in data
|
|
assert "bootstrap_ok" in data
|
|
|
|
async def test_host_response_with_bootstrap(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Hôte avec statut bootstrap."""
|
|
from app.crud.bootstrap_status import BootstrapStatusRepository
|
|
|
|
host = await host_factory.create(db_session, name="bootstrap-host.local")
|
|
|
|
bs_repo = BootstrapStatusRepository(db_session)
|
|
await bs_repo.create(
|
|
host_id=host.id,
|
|
status="success"
|
|
)
|
|
await db_session.commit()
|
|
|
|
response = await client.get(f"/api/hosts/{host.id}")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["bootstrap_ok"] is True
|
|
|
|
|
|
class TestCreateHostWithNewEnvGroup:
|
|
"""Tests pour création avec nouveau groupe env."""
|
|
|
|
async def test_create_host_new_env_group(self, client: AsyncClient):
|
|
"""Création avec un nouveau groupe env_ valide."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.get_role_groups.return_value = []
|
|
mock_ansible.add_host_to_inventory = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post(
|
|
"/api/hosts",
|
|
json={
|
|
"name": "newenv.local",
|
|
"ip": "192.168.1.200",
|
|
"env_group": "env_staging", # New group starting with env_
|
|
"role_groups": []
|
|
}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestUpdateHostByIdFallback:
|
|
"""Tests pour mise à jour par ID quand nom non trouvé."""
|
|
|
|
async def test_update_host_fallback_to_id(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Mise à jour utilise l'ID si le nom n'est pas trouvé."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="fallback-update-id",
|
|
name="fallback-update.local"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.get_env_groups.return_value = ["env_prod"]
|
|
mock_ansible.update_host_groups = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
# Use ID instead of name
|
|
response = await client.put(
|
|
f"/api/hosts/{host.id}",
|
|
json={"env_group": "env_prod"}
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestDeleteHostByIdFallback:
|
|
"""Tests pour suppression par ID."""
|
|
|
|
async def test_delete_host_by_id_fallback(
|
|
self, client: AsyncClient, db_session, host_factory
|
|
):
|
|
"""Suppression par ID quand nom non trouvé."""
|
|
host = await host_factory.create(
|
|
db_session,
|
|
id="delete-fallback-id",
|
|
name="delete-fallback.local"
|
|
)
|
|
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
mock_ansible.remove_host_from_inventory = MagicMock()
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
# Delete by ID
|
|
response = await client.delete(f"/api/hosts/{host.id}")
|
|
|
|
assert response.status_code == 200
|
|
|
|
|
|
class TestSyncHostsNoGroups:
|
|
"""Tests pour sync avec hôtes sans groupes."""
|
|
|
|
async def test_sync_hosts_no_groups(self, client: AsyncClient):
|
|
"""Synchronisation d'hôtes sans groupes."""
|
|
with patch("app.routes.hosts.ansible_service") as mock_ansible:
|
|
from app.schemas.host_api import AnsibleInventoryHost
|
|
mock_ansible.invalidate_cache = MagicMock()
|
|
mock_ansible.get_hosts_from_inventory.return_value = [
|
|
AnsibleInventoryHost(
|
|
name="nogroup.local",
|
|
ansible_host="10.0.0.50",
|
|
group="ungrouped",
|
|
groups=[], # No groups
|
|
vars={}
|
|
),
|
|
]
|
|
|
|
with patch("app.routes.hosts.ws_manager") as mock_ws:
|
|
mock_ws.broadcast = AsyncMock()
|
|
|
|
response = await client.post("/api/hosts/sync")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["created"] == 1
|