""" Integration tests for the Config Templates API endpoints. Tests exercise: - GET /api/tenants/{tenant_id}/templates -- list templates - POST /api/tenants/{tenant_id}/templates -- create template - GET /api/tenants/{tenant_id}/templates/{id} -- get template - PUT /api/tenants/{tenant_id}/templates/{id} -- update template - DELETE /api/tenants/{tenant_id}/templates/{id} -- delete template - POST /api/tenants/{tenant_id}/templates/{id}/preview -- preview rendered template Push endpoints (POST .../push) require actual RouterOS connections, so we only test the preview endpoint which only needs a database device record. All tests run against real PostgreSQL. """ import uuid import pytest pytestmark = pytest.mark.integration TEMPLATE_CONTENT = """/ip address add address={{ ip_address }}/24 interface=ether1 /system identity set name={{ hostname }} """ TEMPLATE_VARIABLES = [ {"name": "ip_address", "type": "ip", "default": "192.168.1.1"}, {"name": "hostname", "type": "string", "default": "router"}, ] class TestTemplatesCRUD: """Template list, create, get, update, delete endpoints.""" async def test_list_templates_empty( self, client, auth_headers_factory, admin_session, ): """GET /api/tenants/{tenant_id}/templates returns 200 with empty list.""" auth = await auth_headers_factory(admin_session) tenant_id = auth["tenant_id"] resp = await client.get( f"/api/tenants/{tenant_id}/templates", headers=auth["headers"], ) assert resp.status_code == 200 data = resp.json() assert isinstance(data, list) async def test_create_template( self, client, auth_headers_factory, admin_session, ): """POST /api/tenants/{tenant_id}/templates creates a template.""" auth = await auth_headers_factory(admin_session, role="operator") tenant_id = auth["tenant_id"] template_data = { "name": f"Test Template {uuid.uuid4().hex[:6]}", "description": "A test config template", "content": TEMPLATE_CONTENT, "variables": TEMPLATE_VARIABLES, "tags": ["test", "integration"], } resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=template_data, headers=auth["headers"], ) assert resp.status_code == 201 data = resp.json() assert data["name"] == template_data["name"] assert data["description"] == "A test config template" assert "id" in data assert "content" in data assert data["content"] == TEMPLATE_CONTENT assert data["variable_count"] == 2 assert set(data["tags"]) == {"test", "integration"} async def test_get_template( self, client, auth_headers_factory, admin_session, ): """GET /api/tenants/{tenant_id}/templates/{id} returns full template with content.""" auth = await auth_headers_factory(admin_session, role="operator") tenant_id = auth["tenant_id"] # Create first create_data = { "name": f"Get Test {uuid.uuid4().hex[:6]}", "content": TEMPLATE_CONTENT, "variables": TEMPLATE_VARIABLES, "tags": [], } create_resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=create_data, headers=auth["headers"], ) assert create_resp.status_code == 201 template_id = create_resp.json()["id"] # Get it resp = await client.get( f"/api/tenants/{tenant_id}/templates/{template_id}", headers=auth["headers"], ) assert resp.status_code == 200 data = resp.json() assert data["id"] == template_id assert data["content"] == TEMPLATE_CONTENT assert "variables" in data assert len(data["variables"]) == 2 async def test_update_template( self, client, auth_headers_factory, admin_session, ): """PUT /api/tenants/{tenant_id}/templates/{id} updates template content.""" auth = await auth_headers_factory(admin_session, role="operator") tenant_id = auth["tenant_id"] # Create first create_data = { "name": f"Update Test {uuid.uuid4().hex[:6]}", "content": TEMPLATE_CONTENT, "variables": TEMPLATE_VARIABLES, "tags": ["original"], } create_resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=create_data, headers=auth["headers"], ) assert create_resp.status_code == 201 template_id = create_resp.json()["id"] # Update it updated_content = "/system identity set name={{ hostname }}-updated\n" update_data = { "name": create_data["name"], "content": updated_content, "variables": [{"name": "hostname", "type": "string"}], "tags": ["updated"], } resp = await client.put( f"/api/tenants/{tenant_id}/templates/{template_id}", json=update_data, headers=auth["headers"], ) assert resp.status_code == 200 data = resp.json() assert data["content"] == updated_content assert data["variable_count"] == 1 assert "updated" in data["tags"] async def test_delete_template( self, client, auth_headers_factory, admin_session, ): """DELETE /api/tenants/{tenant_id}/templates/{id} removes the template.""" auth = await auth_headers_factory(admin_session, role="operator") tenant_id = auth["tenant_id"] # Create first create_data = { "name": f"Delete Test {uuid.uuid4().hex[:6]}", "content": "/system identity set name=test\n", "variables": [], "tags": [], } create_resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=create_data, headers=auth["headers"], ) assert create_resp.status_code == 201 template_id = create_resp.json()["id"] # Delete it resp = await client.delete( f"/api/tenants/{tenant_id}/templates/{template_id}", headers=auth["headers"], ) assert resp.status_code == 204 # Verify it's gone get_resp = await client.get( f"/api/tenants/{tenant_id}/templates/{template_id}", headers=auth["headers"], ) assert get_resp.status_code == 404 async def test_get_template_not_found( self, client, auth_headers_factory, admin_session, ): """GET non-existent template returns 404.""" auth = await auth_headers_factory(admin_session) tenant_id = auth["tenant_id"] fake_id = str(uuid.uuid4()) resp = await client.get( f"/api/tenants/{tenant_id}/templates/{fake_id}", headers=auth["headers"], ) assert resp.status_code == 404 class TestTemplatePreview: """Template preview endpoint.""" async def test_template_preview( self, client, auth_headers_factory, admin_session, create_test_device, create_test_tenant, ): """POST /api/tenants/{tenant_id}/templates/{id}/preview renders template for device.""" tenant = await create_test_tenant(admin_session) auth = await auth_headers_factory( admin_session, existing_tenant_id=tenant.id, role="operator" ) tenant_id = auth["tenant_id"] # Create device for preview context device = await create_test_device( admin_session, tenant.id, hostname="preview-router", ip_address="10.0.1.1" ) await admin_session.commit() # Create template template_data = { "name": f"Preview Test {uuid.uuid4().hex[:6]}", "content": "/system identity set name={{ hostname }}\n", "variables": [], "tags": [], } create_resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=template_data, headers=auth["headers"], ) assert create_resp.status_code == 201 template_id = create_resp.json()["id"] # Preview it preview_resp = await client.post( f"/api/tenants/{tenant_id}/templates/{template_id}/preview", json={"device_id": str(device.id), "variables": {}}, headers=auth["headers"], ) assert preview_resp.status_code == 200 data = preview_resp.json() assert "rendered" in data assert "preview-router" in data["rendered"] assert data["device_hostname"] == "preview-router" async def test_template_preview_with_variables( self, client, auth_headers_factory, admin_session, create_test_device, create_test_tenant, ): """Preview with custom variables renders them into the template.""" tenant = await create_test_tenant(admin_session) auth = await auth_headers_factory( admin_session, existing_tenant_id=tenant.id, role="operator" ) tenant_id = auth["tenant_id"] device = await create_test_device(admin_session, tenant.id) await admin_session.commit() template_data = { "name": f"VarPreview {uuid.uuid4().hex[:6]}", "content": "/ip address add address={{ custom_ip }}/24 interface=ether1\n", "variables": [{"name": "custom_ip", "type": "ip", "default": "192.168.1.1"}], "tags": [], } create_resp = await client.post( f"/api/tenants/{tenant_id}/templates", json=template_data, headers=auth["headers"], ) assert create_resp.status_code == 201 template_id = create_resp.json()["id"] preview_resp = await client.post( f"/api/tenants/{tenant_id}/templates/{template_id}/preview", json={"device_id": str(device.id), "variables": {"custom_ip": "10.10.10.1"}}, headers=auth["headers"], ) assert preview_resp.status_code == 200 data = preview_resp.json() assert "10.10.10.1" in data["rendered"] async def test_templates_unauthenticated(self, client): """GET templates without auth returns 401.""" tenant_id = str(uuid.uuid4()) resp = await client.get(f"/api/tenants/{tenant_id}/templates") assert resp.status_code == 401