Skip to content

Commit 36e3236

Browse files
authored
fix: handle message bodies (#1117)
Some methods with http annotations have body fields that are message types. Previously, generated unit tests did not handle this well. This is a fix for that: generate a reasonable mock value for the message, represented as a dict.
1 parent d2ab9da commit 36e3236

File tree

3 files changed

+96
-4
lines changed

3 files changed

+96
-4
lines changed

gapic/schema/wrappers.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,20 @@ def map(self) -> bool:
9494
return bool(self.repeated and self.message and self.message.map)
9595

9696
@utils.cached_property
97-
def mock_value_original_type(self) -> Union[bool, str, bytes, int, float, List[Any], None]:
97+
def mock_value_original_type(self) -> Union[bool, str, bytes, int, float, Dict[str, Any], List[Any], None]:
98+
# Return messages as dicts and let the message ctor handle the conversion.
99+
if self.message:
100+
if self.map:
101+
# Not worth the hassle, just return an empty map.
102+
return {}
103+
104+
msg_dict = {
105+
f.name: f.mock_value_original_type
106+
for f in self.message.fields.values()
107+
}
108+
109+
return [msg_dict] if self.repeated else msg_dict
110+
98111
answer = self.primitive_mock() or None
99112

100113
# If this is a repeated field, then the mock answer should
@@ -173,7 +186,7 @@ def primitive_mock(self, suffix: int = 0) -> Union[bool, str, bytes, int, float,
173186
answer: Union[bool, str, bytes, int, float, List[Any], None] = None
174187

175188
if not isinstance(self.type, PrimitiveType):
176-
raise TypeError(f"'inner_mock_as_original_type' can only be used for"
189+
raise TypeError(f"'primitive_mock' can only be used for "
177190
f"PrimitiveType, but type is {self.type}")
178191

179192
else:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (C) 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
syntax = "proto3";
16+
17+
package google.fragment;
18+
19+
import "google/api/client.proto";
20+
import "google/api/annotations.proto";
21+
import "google/api/field_behavior.proto";
22+
23+
service SmallCompute {
24+
option (google.api.default_host) = "my.example.com";
25+
26+
rpc MyMethod(MethodRequest) returns (MethodResponse) {
27+
option (google.api.http) = {
28+
body: "method_body"
29+
post: "/computation/v1/first_name/{first_name}/last_name/{last_name}"
30+
};
31+
};
32+
}
33+
34+
message SerialNumber {
35+
int32 number = 1;
36+
}
37+
38+
message MethodRequest {
39+
message MethodBody {
40+
int32 mass_kg = 1;
41+
int32 length_cm = 2;
42+
repeated SerialNumber serial_numbers = 3;
43+
map<string, SerialNumber> word_associations = 4;
44+
}
45+
46+
string first_name = 1 [(google.api.field_behavior) = REQUIRED];
47+
string last_name = 2 [(google.api.field_behavior) = REQUIRED];
48+
MethodBody method_body = 3 [(google.api.field_behavior) = REQUIRED];
49+
}
50+
51+
message MethodResponse {
52+
string name = 1;
53+
}

tests/unit/schema/wrappers/test_field.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ def test_mock_value_map():
241241
label=3,
242242
type='TYPE_MESSAGE',
243243
)
244+
244245
assert field.mock_value == "{'key_value': 'value_value'}"
245246

246247

@@ -290,7 +291,7 @@ def test_mock_value_message():
290291
assert field.mock_value == 'bogus.Message(foo=324)'
291292

292293

293-
def test_mock_value_original_type_message_errors():
294+
def test_mock_value_original_type_message():
294295
subfields = collections.OrderedDict((
295296
('foo', make_field(name='foo', type='TYPE_INT32')),
296297
('bar', make_field(name='bar', type='TYPE_STRING'))
@@ -307,14 +308,39 @@ def test_mock_value_original_type_message_errors():
307308
nested_enums={},
308309
nested_messages={},
309310
)
311+
310312
field = make_field(
311313
type='TYPE_MESSAGE',
312314
type_name='bogus.Message',
313315
message=message,
314316
)
315317

318+
mock = field.mock_value_original_type
319+
320+
assert mock == {"foo": 324, "bar": "bar_value"}
321+
322+
# Messages by definition aren't primitive
316323
with pytest.raises(TypeError):
317-
mock = field.mock_value_original_type
324+
field.primitive_mock()
325+
326+
# Special case for map entries
327+
entry_msg = make_message(
328+
name='MessageEntry',
329+
fields=(
330+
make_field(name='key', type='TYPE_STRING'),
331+
make_field(name='value', type='TYPE_STRING'),
332+
),
333+
options=descriptor_pb2.MessageOptions(map_entry=True),
334+
)
335+
entry_field = make_field(
336+
name="messages",
337+
type_name="stuff.MessageEntry",
338+
message=entry_msg,
339+
label=3,
340+
type='TYPE_MESSAGE',
341+
)
342+
343+
assert entry_field.mock_value_original_type == {}
318344

319345

320346
def test_mock_value_recursive():

0 commit comments

Comments
 (0)