IHS-119: Fix upsert not working for numberpool and attribute in hfid#1014
Conversation
Deploying infrahub-sdk-python with
|
| Latest commit: |
5ac552f
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c450c72e.infrahub-sdk-python.pages.dev |
| Branch Preview URL: | https://sb-12052026-fix-upsert-numbe.infrahub-sdk-python.pages.dev |
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## stable #1014 +/- ##
==========================================
+ Coverage 81.56% 81.78% +0.21%
==========================================
Files 134 135 +1
Lines 11442 11643 +201
Branches 1730 1761 +31
==========================================
+ Hits 9333 9522 +189
- Misses 1564 1572 +8
- Partials 545 549 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 20 files with indirect coverage changes 🚀 New features to boost your workflow:
|
| attr = self._get_attribute(attr_name) | ||
| except ResourceNotDefinedError: | ||
| continue | ||
| if attr.is_from_pool_attribute(): |
There was a problem hiding this comment.
I'm wondering if we should forbid this all together if the attribute can come from a pool. It could be that we should have
if attr.value is not None and attr.is_from_pool_attribute():I.e. that this would be problematic for upsert mutations if a value hadn't been assigned from a pool already. Was that something that you considered?
There was a problem hiding this comment.
1 issue found across 5 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
ogenstad
left a comment
There was a problem hiding this comment.
Looks good overall, but as we're testing with mocked data it could be nice either have an integration test or at least validate against a real server.
| timeout: int | None = None, | ||
| request_context: RequestContext | None = None, | ||
| ) -> None: | ||
| if allow_upsert and not self.id: |
There was a problem hiding this comment.
The bug report specifically mentions Node.save() as a problem area but I think we can have a similar situation in Node.create(). One additional comment here is that the code looks exactly the same for the sync and async version, with that in mind I think we can create a helper method that can be called from both of these to ensure that we keep the code in sync in the future.
polmichel
left a comment
There was a problem hiding this comment.
The coverage is great and the fix is nicely implemented.
That said, there is a test that is just noisy and, in my opinion, does not bring any value to our test suite. Maybe it would be better to remove it.
Optional nitpick comments about test descriptions and test assertions have also been added.
| vlan_schema_with_pool_hfid: NodeSchemaAPI, | ||
| httpx_mock: HTTPXMock, | ||
| ) -> None: | ||
| """FAILS before the guard is added: ValidationError must be raised when HFID attr is NumberPool-sourced.""" |
There was a problem hiding this comment.
Nitpick and optional comment: test description should in my opinion describe what's the expected behavior from the system, and not stating what has been done in order to reach this state (like fixing an older bug)
| vlan_schema_with_pool_hfid: NodeSchemaAPI, | ||
| httpx_mock: HTTPXMock, | ||
| ) -> None: | ||
| """Guard is bypassed when an explicit node id is already set.""" |
There was a problem hiding this comment.
Nitpick and optional comment: I do not think knowing there is a "guard" is important. What's important is the use case that this test describes.
| with pytest.raises(ValidationError, match="vlan_id"): | ||
| await node.save(allow_upsert=True) |
There was a problem hiding this comment.
Optional comment: after some discussions with @gmazoyer and @ajtmccarty, we ended up agreeing that exact error message matches would create better assertions in our test suites.
| data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, | ||
| ) | ||
|
|
||
| with pytest.raises(ValidationError, match="vlan_id"): |
There was a problem hiding this comment.
Same optional comment about error message assertions
| @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) | ||
| async def test_save_upsert_no_error_before_fix( | ||
| client: InfrahubClient, | ||
| vlan_schema_with_pool_hfid: NodeSchemaAPI, | ||
| httpx_mock: HTTPXMock, | ||
| ) -> None: | ||
| """FAILS before the guard is added: ValidationError must be raised when HFID attr is NumberPool-sourced.""" | ||
| httpx_mock.add_response(method="POST", json=UPSERT_MOCK_RESPONSE) | ||
| node = InfrahubNode( | ||
| client=client, | ||
| schema=vlan_schema_with_pool_hfid, | ||
| data={"name": "Test VLAN", "vlan_id": {"from_pool": {"id": POOL_ID}}}, | ||
| ) | ||
|
|
||
| with pytest.raises(ValidationError, match="vlan_id"): | ||
| await node.save(allow_upsert=True) |
There was a problem hiding this comment.
I am not sure what's the purpose of this test: mocking the API behavior to make sure that if we remove the fix, the application is still failing?
There was a problem hiding this comment.
I think we should remove this test
| httpx_mock: HTTPXMock, | ||
| ) -> None: | ||
| """FAILS before the guard is added: ValidationError must be raised when HFID attr is NumberPool-sourced.""" | ||
| httpx_mock.add_response(method="POST", json=UPSERT_MOCK_RESPONSE) |
There was a problem hiding this comment.
The mock response is not even used -> @pytest.mark.httpx_mock(assert_all_responses_were_requested=False)
| vlan_schema: NodeSchemaAPI, | ||
| httpx_mock: HTTPXMock, | ||
| ) -> None: | ||
| """Guard does not fire when the pool-sourced attribute is not part of the HFID.""" |
There was a problem hiding this comment.
Same comment about test description
Why
node.save(allow_upsert=True)on a node whose human-friendly identifier (HFID) contains aCoreNumberPool-sourced attribute crashes with"invalid literal for int() with base 10: 'VLAN ID Pool - Test'". The backend upsert resolver tries to use the unresolved pool reference as an HFID component, but the integer value is not assigned until after the server creates the node — making the upsert semantically impossible.This PR adds a client-side guard that detects the pattern and raises a descriptive
ValidationErrorbefore any network call is made.Closes #339
How to test
Checklist
python_sdk/changelog/339.fixed.md)docs/docs/python-sdk/guides/resource-manager.mdx)Summary by cubic
Prevents crashes when upserting nodes whose HFID includes a
CoreNumberPoolattribute by failing fast with a clearValidationError. Addresses IHS-119._validate_upsert()and call it fromcreate()(async and sync) so bothsave(allow_upsert=True)andcreate(allow_upsert=True)raise early when any HFID component is an unresolved pool-backed attribute (afrom_pooldict with no value or a pool node object).idis set or when the pool-backed attribute is not in the HFID. Addedis_unresolved_pool_attribute(), tests for async/sync paths, updated docs with alternatives, and a changelog entry.Written for commit 5ac552f. Summary will update on new commits.