Skip to content

Commit

Permalink
Merge branch 'master' into ads-rest
Browse files Browse the repository at this point in the history
  • Loading branch information
software-dov authored Dec 10, 2021
2 parents e9f1b0c + 4e5fe2d commit fef1f0d
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -1309,7 +1309,130 @@ def test_{{ method.name|snake_case }}_rest(request_type):
{% endif %}


def test_{{ method_name }}_rest_bad_request(transport: str = 'rest', request_type={{ method.input.ident }}):
{% if method.input.required_fields %}
def test_{{ method_name }}_rest_required_fields(request_type={{ method.input.ident }}):
transport_class = transports.{{ service.rest_transport_name }}

request_init = {}
{% for req_field in method.input.required_fields if req_field.is_primitive %}
{% if req_field.field_pb.default_value is string %}
request_init["{{ req_field.name }}"] = "{{ req_field.field_pb.default_value }}"
{% else %}
request_init["{{ req_field.name }}"] = {{ req_field.field_pb.default_value }}
{% endif %}{# default is str #}
{% endfor %}
request = request_type(request_init)
jsonified_request = json.loads(request_type.to_json(
request,
including_default_value_fields=False,
use_integers_for_enums=False
))

# verify fields with default values are dropped
{% for req_field in method.input.required_fields if req_field.is_primitive %}
{% set field_name = req_field.name | camel_case %}
assert "{{ field_name }}" not in jsonified_request
{% endfor %}

unset_fields = transport_class._{{ method.name | snake_case }}_get_unset_required_fields(jsonified_request)
jsonified_request.update(unset_fields)

# verify required fields with default values are now present
{% for req_field in method.input.required_fields if req_field.is_primitive %}
{% set field_name = req_field.name | camel_case %}
assert "{{ field_name }}" in jsonified_request
assert jsonified_request["{{ field_name }}"] == request_init["{{ req_field.name }}"]
{% endfor %}

{% for req_field in method.input.required_fields if req_field.is_primitive %}
{% set field_name = req_field.name | camel_case %}
{% set mock_value = req_field.primitive_mock_as_str() %}
jsonified_request["{{ field_name }}"] = {{ mock_value }}
{% endfor %}

unset_fields = transport_class._{{ method.name | snake_case }}_get_unset_required_fields(jsonified_request)
jsonified_request.update(unset_fields)

# verify required fields with non-default values are left alone
{% for req_field in method.input.required_fields if req_field.is_primitive %}
{% set field_name = req_field.name | camel_case %}
{% set mock_value = req_field.primitive_mock_as_str() %}
assert "{{ field_name }}" in jsonified_request
assert jsonified_request["{{ field_name }}"] == {{ mock_value }}
{% endfor %}


client = {{ service.client_name }}(
credentials=ga_credentials.AnonymousCredentials(),
transport='rest',
)
request = request_type(request_init)

# Designate an appropriate value for the returned response.
{% if method.void %}
return_value = None
{% elif method.lro %}
return_value = operations_pb2.Operation(name='operations/spam')
{% elif method.server_streaming %}
return_value = iter([{{ method.output.ident }}()])
{% else %}
return_value = {{ method.output.ident }}()
{% endif %}
# Mock the http request call within the method and fake a response.
with mock.patch.object(Session, 'request') as req:
# We need to mock transcode() because providing default values
# for required fields will fail the real version if the http_options
# expect actual values for those fields.
with mock.patch.object(path_template, 'transcode') as transcode:
# A uri without fields and an empty body will force all the
# request fields to show up in the query_params.
transcode_result = {
'uri': 'v1/sample_method',
'method': "{{ method.http_options[0].method }}",
'query_params': request_init,
}
{% if method.http_options[0].body %}
transcode_result['body'] = {}
{% endif %}
transcode.return_value = transcode_result

response_value = Response()
response_value.status_code = 200
{% if method.void %}
json_return_value = ''
{% elif method.lro %}
json_return_value = json_format.MessageToJson(return_value)
{% else %}
json_return_value = {{ method.output.ident }}.to_json(return_value)
{% endif %}
response_value._content = json_return_value.encode('UTF-8')
req.return_value = response_value

{% if method.client_streaming %}
response = client.{{ method.name|snake_case }}(iter(requests))
{% else %}
response = client.{{ method_name }}(request)
{% endif %}

expected_params = [
{% for req_field in method.input.required_fields if req_field.is_primitive %}
(
"{{ req_field.name }}",
{% if req_field.field_pb.default_value is string %}
"{{ req_field.field_pb.default_value }}",
{% else %}
{{ req_field.field_pb.default_value }},
{% endif %}{# default is str #}
),
{% endfor %}
]
actual_params = req.call_args.kwargs['params']
assert expected_params == actual_params


{% endif %}{# required_fields #}

/def test_{{ method_name }}_rest_bad_request(transport: str = 'rest', request_type={{ method.input.ident }}):
client = {{ service.client_name }}(
credentials=ga_credentials.AnonymousCredentials(),
transport=transport,
Expand Down
90 changes: 90 additions & 0 deletions tests/fragments/google/api/field_behavior.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package google.api;

import "google/protobuf/descriptor.proto";

option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
option java_multiple_files = true;
option java_outer_classname = "FieldBehaviorProto";
option java_package = "com.google.api";
option objc_class_prefix = "GAPI";

extend google.protobuf.FieldOptions {
// A designation of a specific field behavior (required, output only, etc.)
// in protobuf messages.
//
// Examples:
//
// string name = 1 [(google.api.field_behavior) = REQUIRED];
// State state = 1 [(google.api.field_behavior) = OUTPUT_ONLY];
// google.protobuf.Duration ttl = 1
// [(google.api.field_behavior) = INPUT_ONLY];
// google.protobuf.Timestamp expire_time = 1
// [(google.api.field_behavior) = OUTPUT_ONLY,
// (google.api.field_behavior) = IMMUTABLE];
repeated google.api.FieldBehavior field_behavior = 1052;
}

// An indicator of the behavior of a given field (for example, that a field
// is required in requests, or given as output but ignored as input).
// This **does not** change the behavior in protocol buffers itself; it only
// denotes the behavior and may affect how API tooling handles the field.
//
// Note: This enum **may** receive new values in the future.
enum FieldBehavior {
// Conventional default for enums. Do not use this.
FIELD_BEHAVIOR_UNSPECIFIED = 0;

// Specifically denotes a field as optional.
// While all fields in protocol buffers are optional, this may be specified
// for emphasis if appropriate.
OPTIONAL = 1;

// Denotes a field as required.
// This indicates that the field **must** be provided as part of the request,
// and failure to do so will cause an error (usually `INVALID_ARGUMENT`).
REQUIRED = 2;

// Denotes a field as output only.
// This indicates that the field is provided in responses, but including the
// field in a request does nothing (the server *must* ignore it and
// *must not* throw an error as a result of the field's presence).
OUTPUT_ONLY = 3;

// Denotes a field as input only.
// This indicates that the field is provided in requests, and the
// corresponding field is not included in output.
INPUT_ONLY = 4;

// Denotes a field as immutable.
// This indicates that the field may be set once in a request to create a
// resource, but may not be changed thereafter.
IMMUTABLE = 5;

// Denotes that a (repeated) field is an unordered list.
// This indicates that the service may provide the elements of the list
// in any arbitrary order, rather than the order the user originally
// provided. Additionally, the list's order may or may not be stable.
UNORDERED_LIST = 6;

// Denotes that this field returns a non-empty default value if not set.
// This indicates that if the user provides the empty value in a request,
// a non-empty value will be returned. The user will not be aware of what
// non-empty value to expect.
NON_EMPTY_DEFAULT = 7;
}
43 changes: 43 additions & 0 deletions tests/fragments/test_multiple_required_fields.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (C) 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

package google.fragment;

import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/annotations.proto";

service MultipleRequiredFields {
option (google.api.default_host) = "my.example.com";

rpc MyMethod(MethodRequest) returns (MethodResponse) {
option (google.api.http) = {
get: "/nomenclature/v1/kingdom/{kingdom}/phylum/{phylum}"
};
}
}

message Description {
string description = 1;
}

message MethodRequest {
string kingdom = 1 [(google.api.field_behavior) = REQUIRED];
string phylum = 2 [(google.api.field_behavior) = REQUIRED];
Description description = 3 [(google.api.field_behavior) = REQUIRED];
}

message MethodResponse{}

0 comments on commit fef1f0d

Please sign in to comment.