|
11 | 11 |
|
12 | 12 | from __future__ import annotations |
13 | 13 |
|
| 14 | +import re |
14 | 15 | from typing import TYPE_CHECKING, Any |
15 | 16 |
|
| 17 | +import pytest |
| 18 | + |
| 19 | +from infrahub_sdk.exceptions import ValidationError |
16 | 20 | from infrahub_sdk.node import InfrahubNode, InfrahubNodeSync |
17 | 21 |
|
18 | 22 | if TYPE_CHECKING: |
| 23 | + from pytest_httpx import HTTPXMock |
| 24 | + |
19 | 25 | from infrahub_sdk import InfrahubClient, InfrahubClientSync |
20 | 26 | from infrahub_sdk.schema import NodeSchemaAPI |
21 | 27 |
|
@@ -204,3 +210,126 @@ async def test_attribute_with_pool_node_generates_mutation_query( |
204 | 210 | mutation_query = vlan._generate_mutation_query() |
205 | 211 |
|
206 | 212 | assert mutation_query["object"]["vlan_id"] == {"value": None} |
| 213 | + |
| 214 | + |
| 215 | +UPSERT_MOCK_RESPONSE = { |
| 216 | + "data": { |
| 217 | + "InfraVLANUpsert": { |
| 218 | + "ok": True, |
| 219 | + "object": {"id": "mock-vlan-uuid", "vlan_id": {"value": 100}}, |
| 220 | + } |
| 221 | + } |
| 222 | +} |
| 223 | + |
| 224 | + |
| 225 | +async def test_save_upsert_raises_when_numberpool_attr_in_hfid( |
| 226 | + client: InfrahubClient, |
| 227 | + vlan_schema_with_pool_hfid: NodeSchemaAPI, |
| 228 | +) -> None: |
| 229 | + """save(allow_upsert=True) raises ValidationError naming the pool-sourced HFID attribute.""" |
| 230 | + node = InfrahubNode( |
| 231 | + client=client, |
| 232 | + schema=vlan_schema_with_pool_hfid, |
| 233 | + data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, |
| 234 | + ) |
| 235 | + |
| 236 | + with pytest.raises(ValidationError, match=re.escape("Attribute 'vlan_id' is sourced from a CoreNumberPool")): |
| 237 | + await node.save(allow_upsert=True) |
| 238 | + |
| 239 | + |
| 240 | +async def test_save_upsert_proceeds_when_explicit_id_set( |
| 241 | + client: InfrahubClient, |
| 242 | + vlan_schema_with_pool_hfid: NodeSchemaAPI, |
| 243 | + httpx_mock: HTTPXMock, |
| 244 | +) -> None: |
| 245 | + """Upsert proceeds when an explicit node id is already set.""" |
| 246 | + httpx_mock.add_response(method="POST", json=UPSERT_MOCK_RESPONSE) |
| 247 | + node = InfrahubNode( |
| 248 | + client=client, |
| 249 | + schema=vlan_schema_with_pool_hfid, |
| 250 | + data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, |
| 251 | + ) |
| 252 | + node.id = "existing-node-uuid" |
| 253 | + |
| 254 | + await node.save(allow_upsert=True) |
| 255 | + |
| 256 | + assert node.id == "mock-vlan-uuid" |
| 257 | + |
| 258 | + |
| 259 | +async def test_save_upsert_proceeds_when_numberpool_attr_not_in_hfid( |
| 260 | + client: InfrahubClient, |
| 261 | + vlan_schema: NodeSchemaAPI, |
| 262 | + httpx_mock: HTTPXMock, |
| 263 | +) -> None: |
| 264 | + """Upsert proceeds when the pool-sourced attribute is not part of the HFID.""" |
| 265 | + httpx_mock.add_response(method="POST", json=UPSERT_MOCK_RESPONSE) |
| 266 | + node = InfrahubNode( |
| 267 | + client=client, |
| 268 | + schema=vlan_schema, |
| 269 | + data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, |
| 270 | + ) |
| 271 | + |
| 272 | + await node.save(allow_upsert=True) |
| 273 | + |
| 274 | + assert node.id == "mock-vlan-uuid" |
| 275 | + |
| 276 | + |
| 277 | +async def test_save_upsert_raises_when_pool_node_object_in_hfid( |
| 278 | + client: InfrahubClient, |
| 279 | + vlan_schema_with_pool_hfid: NodeSchemaAPI, |
| 280 | + ipaddress_pool_schema: NodeSchemaAPI, |
| 281 | + ipam_ipprefix_schema: NodeSchemaAPI, |
| 282 | + ipam_ipprefix_data: dict[str, Any], |
| 283 | +) -> None: |
| 284 | + """save(allow_upsert=True) raises ValidationError when vlan_id is set to a pool node object (not a from_pool dict).""" |
| 285 | + ip_prefix = InfrahubNode(client=client, schema=ipam_ipprefix_schema, data=ipam_ipprefix_data) |
| 286 | + ip_pool = InfrahubNode( |
| 287 | + client=client, |
| 288 | + schema=ipaddress_pool_schema, |
| 289 | + data={ |
| 290 | + "id": NODE_POOL_ID, |
| 291 | + "name": "Core loopbacks", |
| 292 | + "default_address_type": "IpamIPAddress", |
| 293 | + "default_prefix_length": 32, |
| 294 | + "ip_namespace": "ip_namespace", |
| 295 | + "resources": [ip_prefix], |
| 296 | + }, |
| 297 | + ) |
| 298 | + node = InfrahubNode( |
| 299 | + client=client, |
| 300 | + schema=vlan_schema_with_pool_hfid, |
| 301 | + data={"name": "Test VLAN", "vlan_id": ip_pool}, |
| 302 | + ) |
| 303 | + |
| 304 | + with pytest.raises(ValidationError, match=re.escape("Attribute 'vlan_id' is sourced from a CoreNumberPool")): |
| 305 | + await node.save(allow_upsert=True) |
| 306 | + |
| 307 | + |
| 308 | +async def test_create_upsert_raises_when_numberpool_attr_in_hfid( |
| 309 | + client: InfrahubClient, |
| 310 | + vlan_schema_with_pool_hfid: NodeSchemaAPI, |
| 311 | +) -> None: |
| 312 | + """create(allow_upsert=True) raises ValidationError directly, not only through save().""" |
| 313 | + node = InfrahubNode( |
| 314 | + client=client, |
| 315 | + schema=vlan_schema_with_pool_hfid, |
| 316 | + data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, |
| 317 | + ) |
| 318 | + |
| 319 | + with pytest.raises(ValidationError, match=re.escape("Attribute 'vlan_id' is sourced from a CoreNumberPool")): |
| 320 | + await node.create(allow_upsert=True) |
| 321 | + |
| 322 | + |
| 323 | +def test_create_upsert_raises_when_numberpool_attr_in_hfid_sync( |
| 324 | + client_sync: InfrahubClientSync, |
| 325 | + vlan_schema_with_pool_hfid: NodeSchemaAPI, |
| 326 | +) -> None: |
| 327 | + """Sync create(allow_upsert=True) raises ValidationError directly, not only through save().""" |
| 328 | + node = InfrahubNodeSync( |
| 329 | + client=client_sync, |
| 330 | + schema=vlan_schema_with_pool_hfid, |
| 331 | + data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, |
| 332 | + ) |
| 333 | + |
| 334 | + with pytest.raises(ValidationError, match=re.escape("Attribute 'vlan_id' is sourced from a CoreNumberPool")): |
| 335 | + node.create(allow_upsert=True) |
0 commit comments