"""Host inventory ingest and API.""" import uuid from datetime import datetime, timezone from sqlalchemy import select from app.models import Event, Host from app.services.host_inventory import diff_hardware, hardware_snapshot, inventory_fingerprint def test_hardware_diff_detects_memory_change(): old = {"memory_gb": 16.0, "processor": [{"name": "CPU A", "cores": 4, "logical_processors": 8}]} new = {"memory_gb": 32.0, "processor": [{"name": "CPU A", "cores": 4, "logical_processors": 8}]} changes = diff_hardware(old, new) assert len(changes) == 1 assert changes[0]["field"] == "memory_gb" def test_hardware_diff_ignores_first_snapshot(): new = {"memory_gb": 16.0} assert diff_hardware(None, new) == [] def test_ingest_inventory_stores_host_and_info(client, auth_headers, db_session): event_id = str(uuid.uuid4()) payload = { "schema_version": "1.0", "event_id": event_id, "occurred_at": "2026-06-04T10:00:00+03:00", "source": {"product": "rdp-login-monitor", "product_version": "2.0.21-SAC"}, "host": {"hostname": "inv-pc", "os_family": "windows"}, "category": "agent", "type": "agent.inventory", "severity": "info", "title": "Host inventory", "summary": "Inventory snapshot", "details": { "inventory": { "schema_version": 1, "computer_name": "INV-PC", "memory_gb": 32.0, "processor": [{"name": "Intel Xeon", "cores": 8, "logical_processors": 16}], "windows": {"product_name": "Windows Server 2019", "version": "1809"}, } }, } r = client.post("/api/v1/events", json=payload, headers=auth_headers) assert r.status_code == 201 host = db_session.scalar(select(Host).where(Host.hostname == "inv-pc")) assert host is not None assert host.inventory is not None assert host.inventory["memory_gb"] == 32.0 assert host.inventory_updated_at is not None assert host.os_version == "Windows Server 2019 (1809)" def test_ingest_inventory_hardware_change_warning(client, auth_headers, db_session): base = { "schema_version": "1.0", "occurred_at": "2026-06-04T10:00:00+03:00", "source": {"product": "rdp-login-monitor", "product_version": "2.0.21-SAC"}, "host": {"hostname": "inv-change", "os_family": "windows"}, "category": "agent", "type": "agent.inventory", "severity": "info", "title": "Host inventory", "summary": "Inventory snapshot", } inv_v1 = { "inventory": { "memory_gb": 16.0, "processor": [{"name": "CPU A", "cores": 4, "logical_processors": 8}], "disks": [{"friendly_name": "Disk0", "media_type": "SSD", "size_gb": 500.0}], } } assert ( client.post( "/api/v1/events", json={**base, "event_id": str(uuid.uuid4()), "details": inv_v1}, headers=auth_headers, ).status_code == 201 ) inv_v2 = { "inventory": { "memory_gb": 32.0, "processor": [{"name": "CPU A", "cores": 4, "logical_processors": 8}], "disks": [{"friendly_name": "Disk0", "media_type": "SSD", "size_gb": 500.0}], } } r = client.post( "/api/v1/events", json={**base, "event_id": str(uuid.uuid4()), "details": inv_v2}, headers=auth_headers, ) assert r.status_code == 201 ev = db_session.scalar(select(Event).order_by(Event.id.desc())) assert ev is not None assert ev.severity == "warning" assert ev.details.get("hardware_changes") def test_get_host_detail(client, db_session, jwt_headers): h = Host( hostname="detail-host", display_name="Detail Host", os_family="windows", product="rdp-login-monitor", product_version="2.0.21-SAC", inventory={"memory_gb": 64.0}, inventory_updated_at=datetime.now(timezone.utc), last_seen_at=datetime.now(timezone.utc), ) db_session.add(h) db_session.commit() r = client.get(f"/api/v1/hosts/{h.id}", headers=jwt_headers) assert r.status_code == 200 body = r.json() assert body["hostname"] == "detail-host" assert body["inventory"]["memory_gb"] == 64.0 assert body["last_inventory_at"] is not None def test_inventory_fingerprint_stable(): inv = {"memory_gb": 16.0, "processor": [{"name": "X", "cores": 2, "logical_processors": 4}]} assert inventory_fingerprint(inv) == inventory_fingerprint(inv) assert hardware_snapshot(inv)["memory_gb"] == 16.0