tests: fix unit test failure when auto_populated_fields is set#16944
tests: fix unit test failure when auto_populated_fields is set#16944
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements auto-population for the request_id field in CreateJob, CancelJob, and DeleteJob methods, as specified in the service configuration and verified in unit tests. Feedback indicates that the auto-population logic should be moved from the sample code into the generated RPC methods to fully comply with AIP-4235. Furthermore, the unit tests should be updated to use re.fullmatch for more robust UUID validation and to use empty strings instead of None when resetting proto3 string fields.
| if not request.request_id: | ||
| request.request_id = str(uuid.uuid4()) |
There was a problem hiding this comment.
According to AIP-4235, auto-population of fields should be handled by the client library's RPC methods. If the generator is updated to support auto_populated_fields, this logic should ideally be placed within the generated RPC methods rather than the sample code. This keeps the samples clean and ensures the feature is available to all users of the library, not just those following the samples.
References
- According to AIP-4235, auto-population of fields should be handled by the client library's RPC methods. (link)
| assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) | ||
| # clear UUID field so that the check below succeeds | ||
| args[0].request_id = None |
There was a problem hiding this comment.
There are two improvements that can be made here for better robustness and adherence to proto3 standards:
- Use
re.fullmatchinstead ofre.matchto ensure the entirerequest_idstring conforms to the UUID v4 format, preventing false positives from longer strings. - Set the field to an empty string
""instead ofNone. In proto3, string fields default to"". Whileproto-plusmight handleNoneby converting it to the default, it is not valid for standardgoogle.protobufmessages and can lead toTypeErroror unexpected behavior in equality checks.
| assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) | |
| # clear UUID field so that the check below succeeds | |
| args[0].request_id = None | |
| assert re.fullmatch(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) | |
| # clear UUID field so that the check below succeeds | |
| args[0].request_id = "" |
References
- In proto3, string fields default to an empty string (""). Using None is not valid for standard google.protobuf messages. (link)
|
The error in https://github.com/googleapis/google-cloud-python/actions/runs/25388575659/job/74456652962?pr=16944 matches what we have in b/508597813 |
| call.assert_called() | ||
| _, args, _ = call.mock_calls[0] | ||
| # Ensure that the uuid4 field is set according to AIP 4235 | ||
| assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) |
There was a problem hiding this comment.
NIT:
If this is generated by template, then all the match patterns should be identical. But it seems that the template could be better designed to have only one copy of the match pattern and have all match calls ref that pattern.
There was a problem hiding this comment.
get_uuid4_re() is a macro in the template and is used in this PR. Can you share more details if this isn't what you meant?
main...repro-508597813#diff-a241fd9719e65e65cf1c9221d8c4704c7e5d7487f86b68d341d5055fde12d2a2R1231
| if not request.request_id: | ||
| request.request_id = str(uuid.uuid4()) | ||
|
|
There was a problem hiding this comment.
nit: can this be introduced as a helper once in the client which can simply be called in each methods?
| call.assert_called() | ||
| _, args, _ = call.mock_calls[0] | ||
| # Ensure that the uuid4 field is set according to AIP 4235 | ||
| assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", args[0].request_id) |
There was a problem hiding this comment.
nit: can the regex be defined once and re-used everywhere?
| if isinstance(request, dict): | ||
| request['request_id'] = "explicit value for autopopulate-able field" | ||
| else: | ||
| request.request_id = "explicit value for autopopulate-able field" |
There was a problem hiding this comment.
nit: Can this be included as part of the parametrize above?
| found_field = None | ||
| for i, (key, value) in enumerate(req.call_args.kwargs['params']): | ||
| if key == "requestId": | ||
| assert re.match(r"[a-f0-9]{8}-?[a-f0-9]{4}-?4[a-f0-9]{3}-?[89ab][a-f0-9]{3}-?[a-f0-9]{12}", value) | ||
| found_field = i | ||
| break | ||
| if found_field is not None: | ||
| del req.call_args.kwargs['params'][found_field] |
There was a problem hiding this comment.
I don't know how I feel about deleting a field in a mock test.
A couple of options:
- We include
request_idwithinexpected_paramsand have the value set tomock.ANYsince we're already asserting what the value should look like against the regex later on. - We can filter the params we want to compare against the expected_params.
Fixes b/508597813